Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Input modifiers #3937

Closed
Rich-Harris opened this issue Nov 16, 2019 · 49 comments · Fixed by #14307
Closed

Input modifiers #3937

Rich-Harris opened this issue Nov 16, 2019 · 49 comments · Fixed by #14307
Labels
feature request popular more than 20 upthumbs

Comments

@Rich-Harris
Copy link
Member

#3527 (comment)

Is your feature request related to a problem? Please describe.
Date inputs have string bindings, which are rarely helpful.

Describe the solution you'd like
It should be possible to specify the type of binding:

<input type="date" bind:value|date={date_as_date}>
<input type="date" bind:value|number={date_as_number}>
<input type="date" bind:value|string={date_as_string}>

In v4, we can switch the default over to the value|date behaviour.

Describe alternatives you've considered
Adding valueAsNumber and valueAsDate bindings. There are a couple of reasons not to do this:

  • It would introduce an inconsistency with how number and range inputs are currently handled
  • Resolving that inconsistency would mean expecting people to type valueAsNumber instead of just value. It's unergonomic
  • Having multiple binding options makes it easy for someone to use them all simultaneously, which could lead to subtle bugs from not having a single source of truth

How important is this feature to you?
Medium

@Conduitry
Copy link
Member

Might it be nice to add |number and |string to type='number' as well?

@Deskbot
Copy link

Deskbot commented Nov 17, 2019

Would be good for <select> which doesn't have a type attribute.

@jimmywarting
Copy link
Contributor

jimmywarting commented Nov 27, 2019

I still would prefer bind : valueAsDate/valueAsNumber

with the reasoning being: "Don't make me learn how to use another framework"
use what the browser provides so that i can apply same knowledge in tomorrows new frameworks

It would just be easier to use the built tools then to write redundant wrappers around something that already exist and can do it for you

@antony
Copy link
Member

antony commented Sep 16, 2020

I also like bind:value|date syntax, as I think it's a lot cleaner than the messy API exposed by browsers.

@cupcakearmy
Copy link

Is there a workaround for now?

@antony
Copy link
Member

antony commented Sep 18, 2020

@cupcakearmy

<input type="date" bind:value={date}>

$: dateAsDate= date && new Date(...date.split('-'))

Rendered

@cupcakearmy
Copy link

@antony Thanks for the answer, but I needed 2 way binding.
Got it working though :)
https://svelte.dev/repl/dc963bbead384b69aad17824149d6d27?version=3.25.1

@pal03377
Copy link

pal03377 commented Feb 20, 2021

Just an idea: Maybe this concept could work in a more general way: I'd imagine having a way to change the internal representation of an input would be very nice. This isn't very well thought out yet, but what about something like

<script>
	
	let date = new Date();
	
	const dateModifier = {
            parse: (dateString) => new Date(Date.parse(dateString))
        };
	
</script>

<input type="date" bind:value|dateModifier={ date }>

The nice thing about this would be that you could also use this with other kinds of inputs well. For example, you could automatically have a localized input like this with less code, for example:

<script>
	
	let number = 1234567;
	
	const numberModifier = {
            parse: (s) => parseInt(s.replace(/\,/g, "")), 
            stringify: (n) => n.toLocaleString("en-US")
        };
	
</script>

<input type="text" bind:value|numberModifier={ number }>

...and you would not need the temporary internal variable any more.

I personally find my own code suggestion a bit ugly here, but would love the general idea of being able to have a variable contain something different than the exact input value given by HTML.

@stale
Copy link

stale bot commented Jun 26, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale-bot label Jun 26, 2021
@stale stale bot removed the stale-bot label Jun 26, 2021
@stale stale bot removed the stale-bot label Jun 27, 2021
@CindyKee
Copy link

Is there any update on this issue? I have run into this problem with <input type="date"/> multiple times in various apps I have written. What is the recommended workaround if this isn't going to be addressed anytime soon?

@stale
Copy link

stale bot commented Dec 24, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale-bot label Dec 24, 2021
@dummdidumm
Copy link
Member

Thought about the same recently, too, but in a more extendable way: what if the modifier is an object you can provide which has two methods transforming the value on the way in/out?

@stale stale bot removed the stale-bot label Dec 24, 2021
@dummdidumm dummdidumm added the popular more than 20 upthumbs label Dec 24, 2021
@harvey-k
Copy link

harvey-k commented Feb 3, 2022

Similar to what dummdidumm proposed, I liked the value converter approach Aurelia used for this problem.

E.g.:

export class RgbToHexValueConverter {
  toView(rgb) {
    return "#" + (
      (1 << 24) + (rgb.r << 16) + (rgb.g << 8) + rgb.b
    ).toString(16).slice(1);
  }
  
  fromView(hex) {
    let exp = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,
        result = exp.exec(hex);
    return {
      r: parseInt(result[1], 16),
      g: parseInt(result[2], 16),
      b: parseInt(result[3], 16)
    };
  }
}
  
export class Color {
  rgb = { r: 146, g: 39, b: 143 };
}

<template>
  <require from="./rgb-to-hex"></require>
  
  <label for="color">Select Color:</label>
  <input id="color" type="color" value.bind="rgb | rgbToHex">
  <br> r: ${rgb.r}, g:${rgb.g}, b:${rgb.b}
</template>

@Prinzhorn
Copy link
Contributor

Prinzhorn commented Mar 3, 2022

I think this is way to specific and might be missing the big picture, as I've outlined here #7265 (comment) . The two comments above mine don't go far enough to solve a greater problem.

What I imagine is some sort of Proxy but for reactivity. Stores give us that level of control (hooking into declarative assignments with imperative code), regular reactivity does not.

In the most verbose way this would look like this:

<script>
  import proxy from 'svelte/proxy';
  import { format } from 'date-fns';

  let value = new Date();

  $: console.log(value);

  // The proxy specification.
  const dateTimeAsString = {
    get(d) {
      return format(d, "yyyy-MM-dd'T'HH:mm");
    },
    set(s) {
      return new Date(s);
    },
  };

  // This reactively marries `value` and `proxiedValue`.
  // Everytime `proxiedValue` is written, it is syncted with `value` through `set`.
  // Everytime `value` is written, it is syncted with `proxiedValue` through `get`.
  // Both `value` and `proxiedValue` can be used just like any variable.
  // This gives us the $store declarative magic for any variable.
  let proxiedValue = proxy(value, dateTimeAsString);
</script>

<input type="datetime-local" bind:value="{proxiedValue}" />

However, the pipe shorthand allows using a proxy implicitly:

<script>
  import { format } from 'date-fns';

  let value = new Date();

  $: console.log(value);

  const dateTimeAsString = {
    get(d) {
      return format(d, "yyyy-MM-dd'T'HH:mm");
    },
    set(s) {
      return new Date(s);
    },
  };
</script>

<input type="datetime-local" bind:value|dateTimeAsString />

And of course Svelte comes with a set of build-in proxies:

<script>
  import { dateTimeAsString } from 'svelte/proxy';

  let value = new Date();

  $: console.log(value);
</script>

<input type="datetime-local" bind:value|dateTimeAsString />

Edit: a few words on what I mean by "big picture": This is not limited to bind at all. You can use it to hook into any reactive variable. In contrast to actual Proxy this is not limited to objects, because Svelte is a compiler. It just works with every type of variable. If you use an identitiy-proxy ({get(v){return v;}, set(v){return v;}}) you essentially get an event/hook you can use to imperatively react to the change of a variable or binding. In the same way you can override store.set simply to do something with the value, e.g. writing it to localStorage.

I also want to point out that my example above makes it look like proxy is a function you can use. But no, it's meant to be a compiler instruction so maybe an import is not the right way for that.

I'm sure there is a solution to #4933 and related problems in there as well.

@WHenderson
Copy link

I was playing around with store transforms to achieve a similar result (you can see here: https://svelte.dev/repl/8d60f27d3183493d8ee2264f535167f4?version=3.48.0).
I think this is modeling what a lot of people are asking for, but it does require the use of stores.
It would be nice to hear what people think.

Something I noticed comes up which may not be immediately obvious, is that not all transformations are symmetrical. That is, not every mapping of A to B has a mapping from B to A. Sometimes a mapping may be ambiguous and sometimes there may just be no mapping.

If the suggestion is to have a fixed list of "out of the box" conversions than this issue may be avoidable, but if the list of conversions is to be extensible it would be worth discussing.

For the library I wrote, I chose to have the read and write transforms offer two forms:
(value: INPUT) => OUTPUT and (value: INPUT, set: ((value: OUTPUT) => void)) =>void.
This supports the situation where a specific value may not have a valid conversion. The interface should also be familiar to anyone who is used to the derived interface.

I'm very interested to hear these conversations so I can inform the design choices in my libraries: npmjs @crikey/*

@aradalvand
Copy link

I created #9998 for $proxy.

@Malix-Labs
Copy link

Malix-Labs commented Dec 27, 2023

@cupcakearmy, your workaround https://svelte.dev/repl/dc963bbead384b69aad17824149d6d27?version=4 contains an infinite effect trigger loop bug (stopped before the 2nd iteration of an effect) that still makes one effect to trigger the other.
In our case, it cause date input UX bugs

  1. When a date is set, input 0 with keyboard anywhere in the date input will clear it
  2. When month and day are set, year input with keyboard can only range from 1901 to 1909

REPL about a workaround fixing the 2nd UX bug

@cloudymeatball
Copy link

2-Way date binding | valid dates only svelte v4
A fork from @Malix-off
fixes 0 input error, by undoing change
half-fixes year error. It's not aesthetic but year can be changed. It's fine if user powers through the 4 digits but if they get confused and stop half way then chaos ensues.

@Malix-Labs
Copy link

Malix-Labs commented Dec 27, 2023

It's theoretically fixable by using stores.
I'm currently also trying a rune port

@Malix-Labs
Copy link

Malix-Labs commented Dec 27, 2023

@cloudymeatball
Your version indeed fixes, in a hacky-way

When a date is set, input 0 with keyboard anywhere in the date input will clear it

but still has

When month and day are set, year input with keyboard can only range from 1901 to 1909

And add some UX bugs when I tried to input some random values in the date input

@Malix-Labs
Copy link

Malix-Labs commented Dec 27, 2023

@cloudymeatball for some reason, on the latest windows and chrome, pressing 4 consecutive digits while having my cursor selection on the input year still only take the last digit in account (except 0, that simply doesn't work), so I can only set 1901-1909

@cloudymeatball
Copy link

@Malix-off See this breaking https://svelte.dev/repl/f9441e746728408d8ed481e2d3572896?version=4.2.8 which uses the web Date object instead of a third-party library. This fixes the year issue.

@Malix-Labs
Copy link

@cloudymeatball

@Malix-off See this breaking svelte.dev/repl/f9441e746728408d8ed481e2d3572896?version=4.2.8 which uses the web Date object instead of a third-party library. This fixes the year issue.

It's pretty close, yep!
The input still doesn't act like the native <input type="date"/> HTML Element in some case, like not allowing the input or tabbing to the next value automatically

@Malix-Labs
Copy link

Malix-Labs commented Dec 28, 2023

@cloudymeatball

@Malix-off In both chromium and firefox, tab, shift+tab, arrows all work normally once the element has focus. We were just adding javascript to a native input. We didn't really take away any browser defaults.

Ye manual tabbing and arrow works, but it doesn't switch to the next automatically in some cases on my browser
Try "whatever-01-whatever" or "whatever-02-whatever", focus away, focus back to DD, and input 1 or 2 respectively (the same digit that the current last DD).
It won't tab automatically to YYYY

Never mind, it does the same with the native <input type="date"/>

@Malix-Labs
Copy link

Malix-Labs commented Dec 28, 2023

@cloudymeatball my bad!
Check above for clarification.
Your version is was the best current workaround imo. (see #3937 (comment))
It still cause one effect to trigger the other though.
Might be fixable with a store but I got severe skill issue for now.
Might try soon™

@cloudymeatball
Copy link

cloudymeatball commented Dec 28, 2023

@Malix-off thanks for the kind words. It's definitely fixable if the $ reactive statements are refactored to on:change events. Ie on:input conflicts with two-way bindings, but i believe on:change doesn't. I haven't familiarized myself with svelte 5 but I believe runes also solve the problem.

@cloudymeatball my bad! Check above for clarification. Your version is the best current workaround imo. It still cause one effect to trigger the other though. Might be fixable with a store but I got severe skill issue for now. Might try soon™

@Malix-Labs
Copy link

Malix-Labs commented Dec 28, 2023

@cloudymeatball

I haven't familiarized myself with svelte 5 but I believe runes also solve the problem.

I am currently trying to build a rune port

@cloudymeatball
Copy link

cloudymeatball commented Dec 28, 2023

@Malix-off Hi it's me, hacky. https://svelte.dev/repl/68648cb51cf44fe18686468dc2b64d81?version=4.2.8 I believe I first saw this when I was trying to learn Vue, but i might have seen it in svelte as well.

Same as ^ but a throttling version https://svelte.dev/repl/c64696445fc841958b901ef3972a7827?version=4.2.8

on:input is different from on:change but what I said before does not seem to apply/be true.

@Malix-Labs
Copy link

Malix-Labs commented Dec 31, 2023

I am working on a rune port.

Unfortunately, in Svelte 5 with Runes, it seems that circular effects doesn't stop after the first effect of the second cycle like it seemed to do in Svelte 4, and produce an infinite chain reaction that triggers this error:

Error: ERR_SVELTE_TOO_MANY_UPDATES: Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops.

Thus, as in my DateInput component any effect triggered triggers that circular effects chain reaction,
Any change to value, valueAsDate, and valueAsNumber breaks their whole reactivity.

I opened a new help thread in the Discord , if someone want to help me debug that.

Update

  1. @adiguba released in the discord help thread a working fix to that circular effects problem REPL
    However, it has some bugs happening with 2-way binding values, I'm looking into it.
  2. Fixed and enhanced: Input modifiers #3937 (comment)

@Malix-Labs
Copy link

Malix-Labs commented Jan 4, 2024

Workarounds

I plan on making a workaround for every input types that features other IDL attributes than value

I am also willing to publish them as a component library dependency, but idk how currently

input type="Date" 2-way binding

2-way binding for value, valueAsDate, and valueAsNumber

v5.0.0-next.28

REPL

<svelte:options runes />

<script lang="ts">
	import { untrack } from "svelte";

	const valueDefault:	HTMLInputElement['value'] = "";
	const valueAsDateDefault: HTMLInputElement['valueAsDate'] = null;
	const valueAsNumberDefault: HTMLInputElement['valueAsNumber'] = NaN;

	let {
		value,
		valueAsDate,
		valueAsNumber,
	} = $props<{
		value?: HTMLInputElement['value'],
		valueAsDate?: HTMLInputElement['valueAsDate'],
		valueAsNumber?: HTMLInputElement['valueAsNumber'],
	}>();

	if (value === undefined) {
		value = valueDefault;
	}
	if (valueAsDate === undefined) {
		valueAsDate = valueAsDateDefault;
	}
	if (valueAsNumber === undefined) {
		valueAsNumber = valueAsNumberDefault;
	}

	function updateFromValue(value: HTMLInputElement['value']): void {
		untrack(() => {
			if (value === "") {
				valueAsDate = valueAsDateDefault;
				valueAsNumber = valueAsNumberDefault;
			} else {
				valueAsDate = new Date(value);
				valueAsNumber = valueAsDate.getTime();
			}
		});
	}
	function updateFromValueAsDate(valueAsDate: HTMLInputElement['valueAsDate']): void {
		untrack(() => {
			if (valueAsDate === null) {
				value = valueDefault;
				valueAsNumber = valueAsNumberDefault;
			} else {
				value = valueAsDate.toISOString().slice(0, 10);
				valueAsNumber = valueAsDate.getTime();
			}
		});
	}
	function updateFromValueAsNumber(valueAsNumber: HTMLInputElement['valueAsNumber']): void {
		untrack(() => {
			if (Number.isNaN(valueAsNumber)) {
				value = valueDefault;
				valueAsDate = valueAsDateDefault;
			} else {
				value = new Date(valueAsNumber).toISOString().slice(0, 10);
				valueAsDate = new Date(valueAsNumber);
			}
		});
	}

	$effect(() => {
		updateFromValue(value as HTMLInputElement['value']);
	});
	$effect(() => {
		updateFromValueAsDate(valueAsDate as HTMLInputElement['valueAsDate']);
	});
	$effect(() => {
		updateFromValueAsNumber(valueAsNumber as HTMLInputElement['valueAsNumber']);
	});
</script>

<input type="date" bind:value={value} />

Note : Affected by #10061

@dummdidumm
Copy link
Member

I'm not sure we need input modifiers nor a $proxy rune since you can achieve the same using $derived and getters/setters. Given that you don't need this often, it doesn't feel worth it to bloat the API surface for this

@Prinzhorn
Copy link
Contributor

@dummdidumm how would this work without bind and also why does it even work? If you do proxy.value = 'test' you get a compiler error "Invalid assignment to derived state". Why is bind allowed to write into a derived but I'm not?

@dummdidumm
Copy link
Member

You're right, that's inconsistent. Either we disallow writing to properties of $derived everywhere, or nowhere.

How would this work without bind?

What do you mean by that?

@dummdidumm
Copy link
Member

Just realized that you don't even need $derived, a simple object with getters/setters is enough

@Malix-Labs
Copy link

@JakeBeaver
Copy link
Contributor

I've been trying to bind an <input type="date" /> to a proper Date() object for a while now with no luck and no working solutions in the observable internet, so I'm sharing what I've just found

No permutation of proxies and effects I've tried worked with bind:, so I tried something more basic:

<input type="date" value={value?.toISOString?.().split("T")[0]} 
  on:change={({target: {value: v}}) => value = v ? new Date(v) : undefined} />

Works perfectly in svelte 4 style and with runes

@cupcakearmy
Copy link

I've been trying to bind an <input type="date" /> to a proper Date() object for a while now with no luck and no working solutions in the observable internet, so I'm sharing what I've just found

No permutation of proxies and effects I've tried worked with bind:, so I tried something more basic:

<input type="date" value={value?.toISOString?.().split("T")[0]} 
  on:change={({target: {value: v}}) => value = v ? new Date(v) : undefined} />

Works perfectly in svelte 4 style and with runes

Removing the Timezone will result in bugs, seen it many times xD
But the logic makes sense :)

@uvulpos
Copy link

uvulpos commented Feb 7, 2024

Also, this is really ugly code imo

@JakeBeaver
Copy link
Contributor

yep, ugly af, but without a framework level solution, this is seemingly the only way. Shared this only in case there's more peeps like me who struggled and need something that works now

@uvulpos
Copy link

uvulpos commented Feb 14, 2024

Totally, just wanted to say I'm not satisfied with that as the final solution to this problem. So we need to come up with something better. But thanks for sharing the workaround ❤️

@hasinoorit
Copy link

Handling arrays of objects has become quite complex. For instance, I have an array of objects where each object has a date field. I need to validate this array using a schema validator, and it's quite challenging to do so efficiently. It would be incredibly helpful if Svelte had binding modifiers to simplify this process.

@regexident
Copy link

[…] Given that you don't need this often, it doesn't feel worth it to bloat the API surface for this. (@dummdidumm)

That's not what I'm experiencing, unfortunately. Quite the contrary. 😕

I frequently find myself reaching for binding modifiers (being accustomed to them from other declarative frameworks, such as @Binding, SwiftUI's equivalent for bind:, which supports providing custom ad-hoc(!) getter/setter, as well as chaining of bindings), and being forced to pull out the sledgehammer to crack a trivial nut.


The problem with store/rune-based "workarounds" is that they attempt to tackle the issue at the wrong level.

Stores have to be declared at the (outer) component's level and runes (afaik) can't just be created on-the-fly either. The the binding however is all the way down at the inner component's level, which in case of components with collections and corresponding each-blocks might not even have a clear one-to-one relationship between the binding and the proxy store, requiring complex logic, especially when the collection gets modified.

As a result of this any such store/rune-based "workaround" as outlined above will —afaict— inevitably fall short in situations where the outer component's state is non-trivially structured, requires binding modifiers for nested state fields (especially if the structure is not fixed) and the component needs to bind its state to a collection of sub-components (e.g. via each blocks).

@hasinoorit already mentioned arrays, but it gets much, much worse when dealing with deeply structured models, let alone dynamic or polymorphic ones.

And even if stores/runes are applicable to your particular scenario: what if your component needed to provide multiple different representations of the same bound value? Imagine a component for a numerical value that provides control through both, a slider, as well as a stepper input, while also showing a formatted textual representation with unit in a label. Now you either have to add multiple competing projection stores, or merge them into a single drastically more complex store. Now for a single number this might not be a big deal, but what if said number has to be validated/evaluated with respect to other values in a larger object, thus preventing a simple extraction into a dedicated component to encapsulate the projections?

Either way you're adding complexity and noise to your component that shouldn't have to be there in the first place. After all you often just needed this one field's value deep down in your component's store nested structure to be passed to shadcn-svelte's' <Slider> component as a number[] rather than in its natural number representation, because reasons.

A binding's modifier should not require external state in order be able to apply a (pure) projection to its value.


If Svelte had proper first-class binding modifiers (as outlined above by @Prinzhorn), then such issues would simply vanish:

Given a model like this:

const model = {
    foo: { bar: { value: number }},
};

… and the need to bind model.foo.bar.value to a shadcn-svelte Slider (which annoyingly expects value: number[], rather than value: number), one would simply add a trivial modifier like this:

const numberAsArray = {
    get(value) { return [value]; },
    set(values) { return values[0]; },
};

… use it:

<Slider bind:value|numberAsArray={model.foo.bar.value}>

… and move on, without spending a minute on the matter (as it arguably should be).


With proper stateless binding modifiers you don't have to touch the outer component's props/state/stores, nor do you have to make any changes to the inner component. Composition at its best. 💪🏻

And in 80% of use cases (such as dateTimeAsString as suggested by @Prinzhorn) you wouldn't even have to write the modifier either as Svelte could provide reasonable defaults for the most common cases.

@regexident
Copy link

I think this is way to specific and might be missing the big picture, as I've outlined here #7265 (comment) . The two comments above mine don't go far enough to solve a greater problem.

What I imagine is some sort of Proxy but for reactivity. Stores give us that level of control (hooking into declarative assignments with imperative code), regular reactivity does not.

💯 @Prinzhorn

Bindings would be a pretty good first step in that direction though (assuming their design is implemented as a restricted instance of the more general concept of reactivity proxies, thus allowing for progressive expansion later on).

A comparatively small step (as compared to generalized reactive proxies), but with potential to bring massive benefits to Svelte by itself alone.


With a few minor modifications the modifiers as roughly outlined by you in:

interface BindingModifier<V, P> = {
  get(value: V): P;
  set(projectedValue: P): V;
};

… they could be made quite a bit more flexible, even:

Parameterization

For default modifiers to be flexible enough one might however want to consider allowing modifiers to accept parameters (with reasonable defaults, where applicable), such as accepting custom date formats.

For this to work one would have to make bind: accept function expressions returning a modifier (i.e. modifier(param)), rather than just modifiers, directly (i.e. modifier).

Pseudocode
<script>
  const dateTimeAsString: (format?: string) => BindingModifier<Date, string> = {
    return {
      get(d: Date) {
        return format(d, format ?? "yyyy-MM-dd'T'HH:mm");
      },
      set(s: string) {
        return new Date(s);
      },
    };
  };
</script>

<!-- Reasonable defaults: -->
<input type="datetime-local" bind:value|dateTimeAsString />

<!-- Easy customization: -->
<input type="datetime-local" bind:value|dateTimeAsString("yyyy-MM-dd") />

Chaining

For composing specialized from a chain of simpler and more general modifiers.

Pseudocode
<script>
  type DateComponents = { year: number, monthIndex: number, day: number };
  let value: DateComponents = // ...

  const componentsAsDate: BindingModifier<DateComponents, Date> = {
    get(c: DateComponents) {
      return new Date(c.year, c.month, c.day);
    },
    set(d: Date) {
      return { year: d.getFullYear(), month: d.getMonth(), day: d.getDay() };
    },
  };
</script>

<!-- Ad-hoc modifier composition: -->
<input type="month" bind:value|componentsAsDate|dateAsString("yyyy-MM-dd") />

Lensing

For losslessly(!) binding against a composite value's partial projection, such as binding a date to an input that is only supposed to know about the date's year component.

(For this to work the BindingModifier<V, P>'s set() method would have to accept a second optional argument, through which it would get passed-in the current value, thus allowing for partial updates.)

Pseudocode
<script>
  interface BindingModifier<V, P> = {
    get(value: V): P;
    set(projectedValue: P): V;
    set(projectedValue: P, currentValue: V): V;
  };

  type Year = number;

  let value: Date = // ...

  const dateAsYear: BindingModifier<Date, Year> = {
    get(date: Date) {
      return date.getYear();
    },
    set(projectedYear: Year, currentDate: Date) {
      currentDate.setFullYear(projectedYear);
      return currentDate;
    },
  };
</script>

<input type="year" bind:value|dateAsYear />

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request popular more than 20 upthumbs
Projects
None yet
Development

Successfully merging a pull request may close this issue.