@threadlabs/looma 0.9.2 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/components/shared/form-reset.js +21 -0
  2. package/components/ui-checkbox/ui-checkbox.html +17 -4
  3. package/components/ui-checkbox/ui-checkbox.js +9 -0
  4. package/components/ui-combobox/ui-combobox.html +23 -11
  5. package/components/ui-combobox/ui-combobox.js +31 -12
  6. package/components/ui-editable/ui-editable.html +3 -1
  7. package/components/ui-editable/ui-editable.js +7 -6
  8. package/components/ui-editor-toolbar/ui-editor-toolbar.html +70 -20
  9. package/components/ui-editor-toolbar/ui-editor-toolbar.js +24 -0
  10. package/components/ui-input/ui-input.html +2 -2
  11. package/components/ui-input/ui-input.js +5 -2
  12. package/components/ui-radio/ui-radio.html +3 -1
  13. package/components/ui-radio/ui-radio.js +11 -0
  14. package/components/ui-radio-group/ui-radio-group.html +7 -5
  15. package/components/ui-radio-group/ui-radio-group.js +16 -3
  16. package/components/ui-search-result-row/ui-search-result-row.html +3 -3
  17. package/components/ui-select/ui-select.html +3 -1
  18. package/components/ui-select/ui-select.js +5 -2
  19. package/components/ui-switch/ui-switch.html +7 -4
  20. package/components/ui-switch/ui-switch.js +9 -0
  21. package/components/ui-textarea/ui-textarea.html +2 -2
  22. package/components/ui-textarea/ui-textarea.js +5 -2
  23. package/components/ui-tree-item/ui-tree-item.html +2 -2
  24. package/dist/index.js +20 -19
  25. package/package.json +1 -1
  26. package/styles/ui-editor-toolbar.css +72 -18
  27. package/vanilla/UiCheckbox.d.ts +1 -0
  28. package/vanilla/UiCheckbox.js +7 -4
  29. package/vanilla/UiCombobox.js +113 -99
  30. package/vanilla/UiEditorToolbar.js +11 -6
  31. package/vanilla/UiRadioGroup.js +7 -1
  32. package/vanilla/UiSearchResultRow.js +4 -1
  33. package/vanilla/UiSwitch.d.ts +1 -0
  34. package/vanilla/UiSwitch.js +7 -4
  35. package/vue/UiCheckbox.d.ts +2 -0
  36. package/vue/UiCheckbox.vue +3 -0
  37. package/vue/UiCombobox.vue +27 -4
  38. package/vue/UiEditorToolbar.vue +90 -20
  39. package/vue/UiRadioGroup.vue +2 -0
  40. package/vue/UiSearchResultRow.vue +1 -0
  41. package/vue/UiSwitch.d.ts +2 -0
  42. package/vue/UiSwitch.vue +3 -0
  43. package/vue/chunks/UiCheckbox.js +4 -1
  44. package/vue/chunks/UiCombobox.js +67 -39
  45. package/vue/chunks/UiEditorToolbar.js +33 -4
  46. package/vue/chunks/UiRadioGroup.js +8 -2
  47. package/vue/chunks/UiSearchResultRow.js +7 -2
  48. package/vue/chunks/UiSwitch.js +4 -1
  49. package/vue/components.css +159 -115
@@ -1,15 +1,20 @@
1
+ import { afterFormReset } from "../shared/form-reset.js";
1
2
  import { trackTrigger } from "../shared/trigger.js";
2
3
 
3
4
  let groups = 0;
4
5
 
5
6
  // Native radios that share a name already move and check with the arrow keys. The group gives its
6
- // radios that name, applies a controlled value, and reports the user's choice.
7
+ // radios that name, applies a controlled value, and reports the user's choice. Its fieldset disables
8
+ // them, so a radio's own disabled is never overwritten.
7
9
  export default function controller(host) {
8
10
  const element = host.element;
9
11
  const name = String(host.state.name || `ui-radio-group-${++groups}`);
10
12
  const [trigger, stopTracking] = trackTrigger(host);
11
13
  const inputs = () => Array.from(element.querySelectorAll('[data-component~="ui-radio"] input[type="radio"]'));
12
14
  let external = host.state.value;
15
+ // A radio's own required survives until the group's required changes, since either one makes the
16
+ // whole name group required.
17
+ let required = false;
13
18
  host.state.internalValue = String(external ?? "");
14
19
  const stop = host.effect(() => {
15
20
  if (host.state.value !== external) {
@@ -17,11 +22,14 @@ export default function controller(host) {
17
22
  host.state.internalValue = String(external ?? "");
18
23
  }
19
24
  const value = host.state.internalValue;
20
- const disabled = Boolean(host.state.disabled);
25
+ const applyRequired = Boolean(host.state.required) !== required;
26
+ required = Boolean(host.state.required);
21
27
  for (const input of inputs()) {
28
+ if (applyRequired) input.required = required;
22
29
  input.name = name;
23
30
  input.checked = input.value === value;
24
- input.disabled = disabled;
31
+ // The value prop is the choice a form reset returns to.
32
+ input.defaultChecked = input.value === String(external ?? "");
25
33
  }
26
34
  });
27
35
  const onChange = (event) => {
@@ -34,8 +42,13 @@ export default function controller(host) {
34
42
  host.dispatch("change", { checked: true, value: input.value, trigger: how });
35
43
  };
36
44
  element.addEventListener("change", onChange);
45
+ const stopReset = afterFormReset(element, () => {
46
+ host.state.internalValue = String(external ?? "");
47
+ for (const input of inputs()) input.checked = input.value === host.state.internalValue;
48
+ });
37
49
  return () => {
38
50
  stop();
51
+ stopReset();
39
52
  stopTracking();
40
53
  element.removeEventListener("change", onChange);
41
54
  };
@@ -2,10 +2,10 @@
2
2
  <defs>
3
3
  <prop name="disabled" type="boolean" default="false">Disables the row's button, so it cannot be chosen or focused, and fades it to 60% opacity.</prop>
4
4
  <prop name="selected" type="boolean" default="false"
5
- >Highlights the row with its hover surface, to mark the current result. It is visual only and sets no ARIA state, so convey the selection to assistive
6
- technology another way where it matters.</prop>
5
+ >Marks the row as the current result: it gets the hover surface and aria-current="true", which assistive technology announces as current. Set it on one row
6
+ at a time, such as the result the arrow keys have moved to.</prop>
7
7
  </defs>
8
- <button type="button" :disabled="disabled">
8
+ <button type="button" :disabled="disabled" :aria-current="selected">
9
9
  <span class="leading"><slot name="leading"></slot></span>
10
10
  <span class="content">
11
11
  <span class="title"><slot name="title"></slot></span>
@@ -1,6 +1,8 @@
1
1
  <template component="ui-select" controller="./ui-select.js">
2
2
  <defs>
3
- <prop name="value" type="string">Currently selected option value.</prop>
3
+ <prop name="value" type="string"
4
+ >The value of the selected option, initially or when set externally. It is also the default, so a form reset returns to it rather than to the first
5
+ option.</prop>
4
6
  <prop name="disabled" type="boolean" default="false">Prevents selection and form submission.</prop>
5
7
  <prop name="invalid" type="boolean" default="false">Exposes invalid state to assistive technology and styling.</prop>
6
8
  <prop name="required" type="boolean" default="false">Requires a non-empty option for form validation.</prop>
@@ -1,10 +1,13 @@
1
1
  // A controlled value can arrive before its projected <option> elements, so it is applied again
2
- // whenever the options change.
2
+ // whenever the options change. The value's option is also marked the native default, so a form
3
+ // reset returns to it rather than to the first option.
3
4
  export default function controller(host) {
4
5
  const select = host.element;
5
6
  const apply = () => {
6
7
  const value = host.state.value;
7
- if (value !== undefined && value !== null && select.value !== String(value)) select.value = String(value);
8
+ if (value === undefined || value === null) return;
9
+ for (const option of select.options) option.defaultSelected = option.value === String(value);
10
+ if (select.value !== String(value)) select.value = String(value);
8
11
  };
9
12
  const observer = new MutationObserver(apply);
10
13
  observer.observe(select, { childList: true, subtree: true });
@@ -1,16 +1,19 @@
1
1
  <template component="ui-switch" controller="./ui-switch.js">
2
2
  <defs>
3
- <prop name="checked" type="boolean" default="false">Whether the switch is checked initially or when set externally.</prop>
3
+ <prop name="checked" type="boolean" default="false">Whether the switch is checked initially or when set externally. A form reset returns it to this.</prop>
4
4
  <prop name="disabled" type="boolean" default="false">Disables the native switch, so it cannot be toggled or focused.</prop>
5
5
  <prop name="required" type="boolean" default="false">Marks the native switch required, so native form validation treats it as invalid until it is on.</prop>
6
+ <prop name="name" type="string" default=""
7
+ >The native name the switch submits under: a form that contains it sends name=value while it is on, and nothing while it is off, as a native checkbox does.
8
+ Left empty, it takes no part in form submission.</prop>
6
9
  <prop name="value" type="string" default="on"
7
- >The value set on the native input and reported in the change event's detail, so one handler can tell switches apart. on by default, as on a native
8
- checkbox.</prop>
10
+ >The value a form submits under name while the switch is on, also reported in the change event's detail so one handler can tell switches apart. on by
11
+ default, as on a native checkbox.</prop>
9
12
  <state name="internalChecked" :value="false"></state>
10
13
  <event name="change" type="object({ checked: boolean, value: string, trigger: keyboard | pointer | programmatic })"></event>
11
14
  </defs>
12
15
  <label>
13
- <input type="checkbox" role="switch" $ref="input" .checked="internalChecked" :disabled="disabled" :required="required" :value="value">
16
+ <input type="checkbox" role="switch" $ref="input" .checked="internalChecked" :disabled="disabled" :required="required" :name="name" :value="value">
14
17
  <span class="label"><slot></slot></span>
15
18
  </label>
16
19
  <style>
@@ -1,12 +1,16 @@
1
+ import { afterFormReset } from "../shared/form-reset.js";
1
2
  import { trackTrigger } from "../shared/trigger.js";
2
3
 
3
4
  // `checked` sets the control initially and whenever it changes; the user's changes update the state.
5
+ // A form reset returns the control to `checked`. Vue writes the checked attribute on every render, so
6
+ // the native default alone cannot hold it there.
4
7
  export default function controller(host) {
5
8
  const input = host.refs.input;
6
9
  const [trigger, stopTracking] = trackTrigger(host);
7
10
  let external = host.state.checked;
8
11
  host.state.internalChecked = Boolean(external);
9
12
  const stop = host.effect(() => {
13
+ input.defaultChecked = Boolean(host.state.checked);
10
14
  if (host.state.checked === external) return;
11
15
  external = host.state.checked;
12
16
  host.state.internalChecked = Boolean(external);
@@ -16,8 +20,13 @@ export default function controller(host) {
16
20
  host.dispatch("change", { checked: input.checked, value: String(host.state.value ?? "on"), trigger: trigger() });
17
21
  };
18
22
  input.addEventListener("change", onChange);
23
+ const stopReset = afterFormReset(input, () => {
24
+ host.state.internalChecked = Boolean(external);
25
+ input.checked = Boolean(external);
26
+ });
19
27
  return () => {
20
28
  stop();
29
+ stopReset();
21
30
  stopTracking();
22
31
  input.removeEventListener("change", onChange);
23
32
  };
@@ -1,8 +1,8 @@
1
1
  <template component="ui-textarea" controller="./ui-textarea.js">
2
2
  <defs>
3
3
  <prop name="value" type="string"
4
- >The textarea's text. Setting or changing it replaces the live text; between changes the user's typing stands, so follow input events to keep it in
5
- step.</prop>
4
+ >The textarea's text. Setting or changing it replaces the live text; between changes the user's typing stands, so follow input events to keep it in step. It
5
+ is also the textarea's default, so a form reset returns to it.</prop>
6
6
  <prop name="disabled" type="boolean" default="false">Prevents editing and form submission.</prop>
7
7
  <prop name="invalid" type="boolean" default="false">Exposes invalid state to assistive technology and styling.</prop>
8
8
  <prop name="readonly" type="boolean" default="false">Allows selection without allowing edits.</prop>
@@ -1,7 +1,10 @@
1
- // The value attribute only sets the default; a controlled value is applied to the live textarea.
1
+ // The value attribute only sets the default; a controlled value is applied to the live textarea. The
2
+ // value is also made the native default, so a form reset returns to it.
2
3
  export default function controller(host) {
3
4
  return host.effect(() => {
4
5
  const value = host.state.value;
5
- if (value !== undefined && value !== null && host.element.value !== String(value)) host.element.value = String(value);
6
+ if (value === undefined || value === null) return;
7
+ host.element.defaultValue = String(value);
8
+ if (host.element.value !== String(value)) host.element.value = String(value);
6
9
  });
7
10
  }
@@ -20,8 +20,8 @@
20
20
  >The item's kind, for drag rules: it can be dropped before or after only items of the same type, and inside a branch only if that branch's accepts allows
21
21
  it. Reported as sourceType and targetType.</prop>
22
22
  <prop name="dropScope" type="string" default=""
23
- >A label passed through as sourceScope and targetScope in the tree's reorder events. The tree does not enforce it; use it to tell drops within a scope from
24
- drops across scopes.</prop>
23
+ >Names the list the item sits in, such as its parent's id, and is reported as sourceScope and targetScope in the tree's reorder events, so a handler can
24
+ tell a reorder within one list from a move between lists. It never restricts where an item can be dropped; dragType and accepts do that.</prop>
25
25
  <prop name="accepts" type="string" default=""
26
26
  >A comma-separated list of the dragType values this branch accepts dropped inside it. Empty accepts any type. It does not affect drops before or after the
27
27
  item.</prop>