@xsynaptic/sonic-ui 0.10.4 → 0.12.0

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 (55) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +5 -16
  3. package/dist/default/define/toggle.js +7 -0
  4. package/dist/default/define.js +1 -0
  5. package/dist/default/elements/button.js +2 -0
  6. package/dist/default/elements/position-group.js +163 -0
  7. package/dist/default/elements/radio-group.js +34 -12
  8. package/dist/default/elements/switch.js +33 -167
  9. package/dist/default/elements/toggle.js +189 -0
  10. package/dist/default/elements/waveform.js +30 -10
  11. package/dist/default/elements/wavestrip.js +4 -0
  12. package/dist/default/index.js +2 -1
  13. package/dist/default/lib/key-press.js +27 -0
  14. package/dist/default/lib/toggle-travel.js +19 -0
  15. package/dist/dev/define/toggle.d.ts +2 -0
  16. package/dist/dev/define/toggle.js +8 -0
  17. package/dist/dev/define/toggle.js.map +1 -0
  18. package/dist/dev/define.d.ts +3 -1
  19. package/dist/dev/define.js +1 -0
  20. package/dist/dev/elements/button.js +2 -0
  21. package/dist/dev/elements/button.js.map +1 -1
  22. package/dist/dev/elements/position-group.d.ts +38 -0
  23. package/dist/dev/elements/position-group.js +164 -0
  24. package/dist/dev/elements/position-group.js.map +1 -0
  25. package/dist/dev/elements/radio-group.d.ts +4 -0
  26. package/dist/dev/elements/radio-group.js +34 -12
  27. package/dist/dev/elements/radio-group.js.map +1 -1
  28. package/dist/dev/elements/switch.d.ts +4 -19
  29. package/dist/dev/elements/switch.js +33 -167
  30. package/dist/dev/elements/switch.js.map +1 -1
  31. package/dist/dev/elements/toggle.d.ts +21 -0
  32. package/dist/dev/elements/toggle.js +197 -0
  33. package/dist/dev/elements/toggle.js.map +1 -0
  34. package/dist/dev/elements/waveform.d.ts +1 -1
  35. package/dist/dev/elements/waveform.js +30 -10
  36. package/dist/dev/elements/waveform.js.map +1 -1
  37. package/dist/dev/elements/wavestrip.d.ts +1 -0
  38. package/dist/dev/elements/wavestrip.js +4 -0
  39. package/dist/dev/elements/wavestrip.js.map +1 -1
  40. package/dist/dev/index.d.ts +3 -2
  41. package/dist/dev/index.js +2 -1
  42. package/dist/dev/lib/key-press.js +28 -0
  43. package/dist/dev/lib/key-press.js.map +1 -0
  44. package/dist/dev/lib/toggle-travel.js +20 -0
  45. package/dist/dev/lib/toggle-travel.js.map +1 -0
  46. package/dist/styles/button.css +2 -2
  47. package/dist/styles/controls.css +1 -0
  48. package/dist/styles/led.css +16 -13
  49. package/dist/styles/material.css +36 -9
  50. package/dist/styles/meter.css +7 -6
  51. package/dist/styles/segmented.css +8 -4
  52. package/dist/styles/spectrum.css +2 -2
  53. package/dist/styles/switch.css +6 -2
  54. package/dist/styles/toggle.css +481 -0
  55. package/package.json +2 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"position-group.js","names":[],"sources":["../../../src/elements/position-group.ts"],"sourcesContent":["import type { Hold } from '#lib/hold.ts';\n\nimport { optionValue, SonicRadioGroupElement } from '#elements/radio-group.ts';\nimport { bindHold } from '#lib/hold.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ntype Orientation = 'horizontal' | 'vertical';\n\nexport abstract class SonicPositionGroupElement extends SonicRadioGroupElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicRadioGroupElement.observedAttributes,\n\t\t'checked',\n\t\t'orientation',\n\t];\n\n\tget checked(): boolean {\n\t\treturn this.#isDirty ? this.#checked : this.defaultChecked;\n\t}\n\n\tset checked(isChecked: boolean | undefined) {\n\t\tthis.#isDirty = true;\n\t\tthis.#checked = isChecked === true;\n\t\tthis.render();\n\t}\n\n\tget defaultChecked(): boolean {\n\t\treturn this.hasAttribute('checked');\n\t}\n\n\tset defaultChecked(isChecked: boolean) {\n\t\tthis.reflect('checked', isChecked);\n\t}\n\n\tget orientation(): Orientation {\n\t\tconst orientation = this.getAttribute('orientation');\n\n\t\treturn orientation === 'horizontal' || orientation === 'vertical'\n\t\t\t? orientation\n\t\t\t: this.defaultOrientation;\n\t}\n\n\tset orientation(direction: Orientation | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\toverride get value(): string {\n\t\tconst value = super.value;\n\n\t\treturn value === '' && this.isBare() ? 'on' : value;\n\t}\n\n\toverride set value(next: string) {\n\t\tsuper.value = next;\n\t}\n\n\tprotected abstract readonly bare: HTMLButtonElement;\n\n\tprotected abstract readonly defaultOrientation: Orientation;\n\n\t#checked = false;\n\n\t#holding: Hold | undefined;\n\n\t#isDirty = false;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tif (name === 'checked') this.#isDirty = false;\n\t\tif (this.isDisabled()) this.#holding?.release();\n\t\tsuper.attributeChangedCallback(name);\n\t}\n\n\toverride formResetCallback(): void {\n\t\tthis.#isDirty = false;\n\t\tsuper.formResetCallback();\n\t}\n\n\tprotected override activate(): void {\n\t\tif (this.isBare()) this.bare.click();\n\t\telse super.activate();\n\t}\n\n\tprotected along(event: PointerEvent): number {\n\t\treturn this.orientation === 'vertical' ? event.clientY : event.clientX;\n\t}\n\n\tprotected checkedIndex(options: Array<HTMLButtonElement>): number {\n\t\treturn options.findIndex((option) => optionValue(option) === this.value);\n\t}\n\n\tprotected override chooseByClick(option: HTMLButtonElement): void {\n\t\tsuper.chooseByClick(option);\n\t\tif (this.isMomentary(option)) this.springBack();\n\t}\n\n\tprotected override chooseByKey(option: HTMLButtonElement, event: KeyboardEvent): void {\n\t\tif (!this.isMomentary(option)) {\n\t\t\tsuper.chooseByKey(option, event);\n\t\t\treturn;\n\t\t}\n\t\tif (event.repeat) return;\n\n\t\tthis.hold(option, event.key);\n\t\toption.focus();\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tthis.upgradeProperties('defaultChecked', 'checked', 'orientation');\n\t\tsuper.connect(signal);\n\t\tthis.#holding = bindHold(\n\t\t\tthis.group,\n\t\t\t{\n\t\t\t\tcanHold: () => !this.isDisabled(),\n\t\t\t\tleave: 'focusout',\n\t\t\t\tonHold: () => {\n\t\t\t\t\tthis.toggleState('held', true);\n\t\t\t\t},\n\t\t\t\tonRelease: () => {\n\t\t\t\t\tthis.toggleState('held', false);\n\t\t\t\t\tthis.springBack();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\tprotected override focusTarget(): HTMLElement | undefined {\n\t\treturn this.isBare() ? this.bare : super.focusTarget();\n\t}\n\n\tprotected hold(option: HTMLButtonElement, by: number | string): void {\n\t\tif (this.#holding?.hold(by) === true) this.select(option);\n\t}\n\n\tprotected isBare(): boolean {\n\t\treturn this.options().length === 0;\n\t}\n\n\tprotected isMomentary(option: HTMLButtonElement): boolean {\n\t\tconst options = this.options();\n\t\tconst isEnd = option === options[0] || option === options.at(-1);\n\n\t\treturn (\n\t\t\tisEnd &&\n\t\t\toptions.length > 1 &&\n\t\t\toption.querySelector('[data-sonic-value]')?.hasAttribute('data-sonic-momentary') === true\n\t\t);\n\t}\n\n\tprotected override isWrapping(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected pressTarget(option: HTMLButtonElement): HTMLButtonElement {\n\t\tconst options = this.options();\n\t\tconst index = this.checkedIndex(options);\n\t\tconst other = options.length === 2 && index >= 0 ? options[1 - index] : undefined;\n\n\t\treturn other ?? option;\n\t}\n\n\tprotected releaseHold(): void {\n\t\tthis.#holding?.release();\n\t}\n\n\tprotected override render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst group = this.group;\n\t\tconst count = this.options().length;\n\n\t\tif (count === 0) {\n\t\t\tthis.#renderBare();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.bare.remove();\n\t\twriteAttribute(group, 'role', 'radiogroup');\n\t\twriteAttribute(group, 'data-sonic-position-count', String(count));\n\t\tsuper.render();\n\t}\n\n\tprotected override restoreState(state: string): void {\n\t\tif (this.isBare()) this.checked = state === 'true';\n\t\telse super.restoreState(state);\n\t}\n\n\tprotected setChecked(isChecked: boolean): void {\n\t\tif (isChecked === this.checked) return;\n\n\t\tthis.checked = isChecked;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\tprotected springBack(): void {\n\t\tconst options = this.options();\n\t\tconst index = this.checkedIndex(options);\n\t\tconst held = options[index];\n\t\tif (!held || !this.isMomentary(held)) return;\n\n\t\tconst rest = options[index === 0 ? 1 : index - 1];\n\t\tif (!rest) return;\n\n\t\tconst isFocused = this.group.contains(this.ownerDocument.activeElement);\n\n\t\tthis.select(rest);\n\t\tif (isFocused) rest.focus();\n\t}\n\n\t#renderBare(): void {\n\t\tconst group = this.group;\n\t\tconst bare = this.bare;\n\t\tconst isChecked = this.checked;\n\n\t\twriteAttribute(group, 'role', undefined);\n\t\twriteAttribute(group, 'data-sonic-position-count', undefined);\n\t\tif (bare.parentElement !== group) group.append(bare);\n\t\twriteAttribute(bare, 'disabled', this.isDisabled() ? '' : undefined);\n\t\twriteAttribute(bare, 'aria-checked', String(isChecked));\n\t\tthis.forwardNaming(group, false);\n\t\tthis.forwardNaming(bare, true);\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tthis.writeFormValue(isChecked ? this.value : null, String(isChecked));\n\t}\n}\n"],"mappings":"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"}
@@ -12,14 +12,18 @@ export declare abstract class SonicRadioGroupElement extends SonicFormElement {
12
12
  protected claimPress(_option: HTMLButtonElement, _event: PointerEvent): boolean;
13
13
  protected connect(signal: AbortSignal): void;
14
14
  protected focusTarget(): HTMLElement | undefined;
15
+ protected isMirrored(child: Node): boolean;
16
+ protected isOptionDisabled(option: HTMLButtonElement): boolean;
15
17
  protected isWrapping(): boolean;
16
18
  protected optionOf(target: EventTarget | null): HTMLButtonElement | undefined;
17
19
  protected options(): Array<HTMLButtonElement>;
20
+ protected placeCopies(copies: Array<Node>): void;
18
21
  protected releaseTarget(option: HTMLButtonElement): HTMLButtonElement | undefined;
19
22
  protected render(): void;
20
23
  protected abstract renderOption(): HTMLButtonElement;
21
24
  protected restoreState(state: string): void;
22
25
  protected select(option: HTMLButtonElement): void;
26
+ protected stepFrom(from: number, step: number): HTMLButtonElement | undefined;
23
27
  }
24
28
  //#endregion
25
29
  //# sourceMappingURL=radio-group.d.ts.map
@@ -1,6 +1,8 @@
1
+ import { clamp } from "../lib/math.js";
1
2
  import { writeAttribute } from "../lib/write-attribute.js";
2
3
  import { SonicFormElement } from "./form-element.js";
3
4
  import { copyNode } from "../lib/copy-node.js";
5
+ import { bindKeyPress } from "../lib/key-press.js";
4
6
  import { placeChildren } from "../lib/render.js";
5
7
  import { mirrorChildren } from "../lib/mirror-children.js";
6
8
  import { bindDrag } from "../lib/pointer-drag.js";
@@ -52,9 +54,9 @@ var SonicRadioGroupElement = class extends SonicFormElement {
52
54
  mirrorChildren(this, {
53
55
  control: group,
54
56
  copy: copyNode,
55
- isCopied: (child) => child instanceof Element && child.matches("[data-sonic-value]"),
57
+ isCopied: (child) => this.isMirrored(child),
56
58
  place: (copies) => {
57
- this.#copyOptions(copies);
59
+ this.placeCopies(copies);
58
60
  }
59
61
  }, signal);
60
62
  group.addEventListener("click", (event) => {
@@ -63,6 +65,7 @@ var SonicRadioGroupElement = class extends SonicFormElement {
63
65
  if (option) this.chooseByClick(option);
64
66
  }, { signal });
65
67
  this.#bindPress(group, signal);
68
+ bindKeyPress(group, signal);
66
69
  group.addEventListener("keydown", (event) => {
67
70
  if (event.defaultPrevented) return;
68
71
  const option = this.optionOf(event.target);
@@ -75,6 +78,12 @@ var SonicRadioGroupElement = class extends SonicFormElement {
75
78
  focusTarget() {
76
79
  return this.options().find((option) => option.tabIndex === 0);
77
80
  }
81
+ isMirrored(child) {
82
+ return child instanceof Element && child.matches("[data-sonic-value]");
83
+ }
84
+ isOptionDisabled(option) {
85
+ return option.querySelector("[data-sonic-value]")?.hasAttribute("data-sonic-disabled") === true;
86
+ }
78
87
  isWrapping() {
79
88
  return true;
80
89
  }
@@ -86,6 +95,9 @@ var SonicRadioGroupElement = class extends SonicFormElement {
86
95
  options() {
87
96
  return [...this.group.children].filter((child) => child instanceof HTMLButtonElement && child.getAttribute("role") === "radio");
88
97
  }
98
+ placeCopies(copies) {
99
+ this.#copyOptions(copies);
100
+ }
89
101
  releaseTarget(option) {
90
102
  return option;
91
103
  }
@@ -93,10 +105,10 @@ var SonicRadioGroupElement = class extends SonicFormElement {
93
105
  if (!this.isBound()) return;
94
106
  const options = this.options();
95
107
  const checked = options.find((option) => optionValue(option) === this.#value);
96
- const focusable = checked ?? options[0];
97
- const isDisabled = this.isDisabled();
108
+ const enabled = this.isDisabled() ? [] : options.filter((option) => !this.isOptionDisabled(option));
109
+ const focusable = enabled.find((option) => option === checked) ?? enabled[0];
98
110
  for (const option of options) {
99
- writeAttribute(option, "disabled", isDisabled ? "" : void 0);
111
+ writeAttribute(option, "disabled", enabled.includes(option) ? void 0 : "");
100
112
  writeAttribute(option, "aria-checked", String(option === checked));
101
113
  writeAttribute(option, "tabindex", option === focusable ? "0" : "-1");
102
114
  }
@@ -112,11 +124,22 @@ var SonicRadioGroupElement = class extends SonicFormElement {
112
124
  this.value = next;
113
125
  this.dispatchEvent(new Event("change", { bubbles: true }));
114
126
  }
127
+ stepFrom(from, step) {
128
+ const options = this.options();
129
+ let index = from;
130
+ for (const _ of options) {
131
+ const next = index + step;
132
+ index = this.isWrapping() ? (next + options.length) % options.length : clamp(next, 0, options.length - 1);
133
+ const option = options[index];
134
+ if (option && !this.isOptionDisabled(option)) return option;
135
+ }
136
+ }
115
137
  #bindPress(group, signal) {
116
138
  bindDrag(group, {
117
139
  grab: (event) => {
118
140
  const option = this.optionOf(event.target);
119
- if (!option || this.isDisabled() || this.claimPress(option, event)) return;
141
+ if (!option || this.isDisabled() || this.isOptionDisabled(option)) return;
142
+ if (this.claimPress(option, event)) return;
120
143
  const boxes = this.#optionBoxes();
121
144
  this.#markPressed(option);
122
145
  return {
@@ -159,19 +182,18 @@ var SonicRadioGroupElement = class extends SonicFormElement {
159
182
  }
160
183
  #keyTarget(option, key) {
161
184
  const options = this.options();
162
- if (key === "Home") return options[0];
163
- if (key === "End") return options.at(-1);
185
+ const isEnabled = (other) => !this.isOptionDisabled(other);
186
+ if (key === "Home") return options.find((other) => isEnabled(other));
187
+ if (key === "End") return options.findLast((other) => isEnabled(other));
164
188
  const step = keySteps.get(key);
165
189
  if (step === void 0) return void 0;
166
- const index = options.indexOf(option) + step;
167
- if (this.isWrapping()) return options[(index + options.length) % options.length];
168
- return options[Math.min(Math.max(index, 0), options.length - 1)];
190
+ return this.stepFrom(options.indexOf(option), step) ?? option;
169
191
  }
170
192
  #markPressed(pressed) {
171
193
  for (const option of this.options()) option.toggleAttribute("data-sonic-pressed", option === pressed);
172
194
  }
173
195
  #optionBoxes() {
174
- return this.options().map((option) => [option, option.getBoundingClientRect()]);
196
+ return this.options().filter((option) => !this.isOptionDisabled(option)).map((option) => [option, option.getBoundingClientRect()]);
175
197
  }
176
198
  #refocus(focused, value) {
177
199
  const options = this.options();
@@ -1 +1 @@
1
- {"version":3,"file":"radio-group.js","names":[],"sources":["../../../src/elements/radio-group.ts"],"sourcesContent":["import { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { placeChildren } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\n// RTL is not mirrored\nconst keySteps = new Map([\n\t['ArrowDown', 1],\n\t['ArrowLeft', -1],\n\t['ArrowRight', 1],\n\t['ArrowUp', -1],\n]);\n\ninterface Press {\n\tboxes: Array<[HTMLButtonElement, DOMRect]>;\n\toption: HTMLButtonElement | undefined;\n}\n\nfunction isInside(box: DOMRect, x: number, y: number): boolean {\n\treturn x >= box.left && x < box.right && y >= box.top && y < box.bottom;\n}\n\nexport function isUnder(option: Element, x: number, y: number): boolean {\n\treturn isInside(option.getBoundingClientRect(), x, y);\n}\n\nexport function optionValue(option: Element): string | undefined {\n\treturn option.querySelector<HTMLElement | SVGElement>('[data-sonic-value]')?.dataset.sonicValue;\n}\n\nexport abstract class SonicRadioGroupElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [...SonicFormElement.observedAttributes, 'value'];\n\n\tget value(): string {\n\t\treturn this.#value;\n\t}\n\n\tset value(next: string) {\n\t\tif (next === this.#value) return;\n\n\t\tthis.#value = next;\n\t\tthis.render();\n\t}\n\n\tprotected abstract readonly group: HTMLElement;\n\n\t#value = '';\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value') this.#value = this.getAttribute('value') ?? '';\n\t\tthis.render();\n\t}\n\n\tprotected chooseByClick(option: HTMLButtonElement): void {\n\t\tthis.select(option);\n\t}\n\n\tprotected chooseByKey(option: HTMLButtonElement, _event: KeyboardEvent): void {\n\t\tthis.select(option);\n\t\toption.focus();\n\t}\n\n\tprotected claimPress(_option: HTMLButtonElement, _event: PointerEvent): boolean {\n\t\treturn false;\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst group = this.group;\n\n\t\tthis.upgradeProperties('value');\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: group,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tisCopied: (child) => child instanceof Element && child.matches('[data-sonic-value]'),\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tthis.#copyOptions(copies);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tgroup.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tif (event instanceof PointerEvent && event.pointerType !== '') return;\n\n\t\t\t\tconst option = this.optionOf(event.target);\n\t\t\t\tif (option) this.chooseByClick(option);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindPress(group, signal);\n\t\tgroup.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.defaultPrevented) return;\n\n\t\t\t\tconst option = this.optionOf(event.target);\n\t\t\t\tconst next = option && this.#keyTarget(option, event.key);\n\t\t\t\tif (!next) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.chooseByKey(next, event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected override focusTarget(): HTMLElement | undefined {\n\t\treturn this.options().find((option) => option.tabIndex === 0);\n\t}\n\n\tprotected isWrapping(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected optionOf(target: EventTarget | null): HTMLButtonElement | undefined {\n\t\tif (!(target instanceof Element)) return undefined;\n\n\t\tconst option = target.closest('[role=\"radio\"]');\n\n\t\treturn option instanceof HTMLButtonElement && option.parentElement === this.group\n\t\t\t? option\n\t\t\t: undefined;\n\t}\n\n\tprotected options(): Array<HTMLButtonElement> {\n\t\treturn [...this.group.children].filter(\n\t\t\t(child): child is HTMLButtonElement =>\n\t\t\t\tchild instanceof HTMLButtonElement && child.getAttribute('role') === 'radio',\n\t\t);\n\t}\n\n\tprotected releaseTarget(option: HTMLButtonElement): HTMLButtonElement | undefined {\n\t\treturn option;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst options = this.options();\n\t\tconst checked = options.find((option) => optionValue(option) === this.#value);\n\t\tconst focusable = checked ?? options[0];\n\t\tconst isDisabled = this.isDisabled();\n\n\t\tfor (const option of options) {\n\t\t\twriteAttribute(option, 'disabled', isDisabled ? '' : undefined);\n\t\t\twriteAttribute(option, 'aria-checked', String(option === checked));\n\t\t\twriteAttribute(option, 'tabindex', option === focusable ? '0' : '-1');\n\t\t}\n\t\tthis.forwardNaming(this.group, true);\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tthis.writeFormValue(checked ? this.#value : null, this.#value);\n\t}\n\n\tprotected abstract renderOption(): HTMLButtonElement;\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = state;\n\t}\n\n\tprotected select(option: HTMLButtonElement): void {\n\t\tconst next = optionValue(option);\n\t\tif (next === undefined || next === this.#value) return;\n\n\t\tthis.value = next;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#bindPress(group: HTMLElement, signal: AbortSignal): void {\n\t\tbindDrag<Press>(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tconst option = this.optionOf(event.target);\n\t\t\t\t\tif (!option || this.isDisabled() || this.claimPress(option, event)) return;\n\n\t\t\t\t\tconst boxes = this.#optionBoxes();\n\n\t\t\t\t\tthis.#markPressed(option);\n\n\t\t\t\t\treturn { boxes, option };\n\t\t\t\t},\n\t\t\t\tlift: (press) => {\n\t\t\t\t\tif (!press.option || this.isDisabled()) return;\n\n\t\t\t\t\tconst option = this.releaseTarget(press.option);\n\t\t\t\t\tif (!option) return;\n\n\t\t\t\t\tthis.select(option);\n\t\t\t\t\tif (group.matches(':focus-within')) option.focus();\n\t\t\t\t},\n\t\t\t\tmove: (press, event) => {\n\t\t\t\t\tpress.option = press.boxes.find(([, box]) =>\n\t\t\t\t\t\tisInside(box, event.clientX, event.clientY),\n\t\t\t\t\t)?.[0];\n\t\t\t\t\tthis.#markPressed(press.option);\n\t\t\t\t},\n\t\t\t\trelease: () => {\n\t\t\t\t\tthis.#markPressed(undefined);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#copyOptions(copies: Array<Node>): void {\n\t\tconst group = this.group;\n\t\tconst options = this.options();\n\t\tconst focused = this.optionOf(group.querySelector(':scope > :focus'));\n\t\tconst focusedValue = focused && optionValue(focused);\n\n\t\tfor (const [index, copy] of copies.entries()) {\n\t\t\tlet option = options[index];\n\t\t\tif (!option) {\n\t\t\t\toption = this.renderOption();\n\t\t\t\tgroup.append(option);\n\t\t\t}\n\n\t\t\tif (option.firstElementChild) placeChildren(option.firstElementChild, [copy]);\n\t\t}\n\t\tfor (const option of options.slice(copies.length)) option.remove();\n\t\tthis.render();\n\t\tif (focused) this.#refocus(focused, focusedValue);\n\t}\n\n\t#keyTarget(option: HTMLButtonElement, key: string): HTMLButtonElement | undefined {\n\t\tconst options = this.options();\n\n\t\tif (key === 'Home') return options[0];\n\t\tif (key === 'End') return options.at(-1);\n\n\t\tconst step = keySteps.get(key);\n\t\tif (step === undefined) return undefined;\n\n\t\tconst index = options.indexOf(option) + step;\n\t\tif (this.isWrapping()) return options[(index + options.length) % options.length];\n\n\t\treturn options[Math.min(Math.max(index, 0), options.length - 1)];\n\t}\n\n\t#markPressed(pressed: HTMLButtonElement | undefined): void {\n\t\tfor (const option of this.options()) {\n\t\t\toption.toggleAttribute('data-sonic-pressed', option === pressed);\n\t\t}\n\t}\n\n\t#optionBoxes(): Array<[HTMLButtonElement, DOMRect]> {\n\t\treturn this.options().map((option) => [option, option.getBoundingClientRect()]);\n\t}\n\n\t#refocus(focused: HTMLButtonElement, value: string | undefined): void {\n\t\tconst options = this.options();\n\t\tconst next =\n\t\t\toptions.find((option) => optionValue(option) === value) ??\n\t\t\toptions.find((option) => option.tabIndex === 0);\n\n\t\tif (next !== focused) next?.focus();\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"radio-group.js","names":[],"sources":["../../../src/elements/radio-group.ts"],"sourcesContent":["import { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { clamp } from '#lib/math.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { placeChildren } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\n// RTL is not mirrored\nconst keySteps = new Map([\n\t['ArrowDown', 1],\n\t['ArrowLeft', -1],\n\t['ArrowRight', 1],\n\t['ArrowUp', -1],\n]);\n\ninterface Press {\n\tboxes: Array<[HTMLButtonElement, DOMRect]>;\n\toption: HTMLButtonElement | undefined;\n}\n\nfunction isInside(box: DOMRect, x: number, y: number): boolean {\n\treturn x >= box.left && x < box.right && y >= box.top && y < box.bottom;\n}\n\nexport function isUnder(option: Element, x: number, y: number): boolean {\n\treturn isInside(option.getBoundingClientRect(), x, y);\n}\n\nexport function optionValue(option: Element): string | undefined {\n\treturn option.querySelector<HTMLElement | SVGElement>('[data-sonic-value]')?.dataset.sonicValue;\n}\n\nexport abstract class SonicRadioGroupElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [...SonicFormElement.observedAttributes, 'value'];\n\n\tget value(): string {\n\t\treturn this.#value;\n\t}\n\n\tset value(next: string) {\n\t\tif (next === this.#value) return;\n\n\t\tthis.#value = next;\n\t\tthis.render();\n\t}\n\n\tprotected abstract readonly group: HTMLElement;\n\n\t#value = '';\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value') this.#value = this.getAttribute('value') ?? '';\n\t\tthis.render();\n\t}\n\n\tprotected chooseByClick(option: HTMLButtonElement): void {\n\t\tthis.select(option);\n\t}\n\n\tprotected chooseByKey(option: HTMLButtonElement, _event: KeyboardEvent): void {\n\t\tthis.select(option);\n\t\toption.focus();\n\t}\n\n\tprotected claimPress(_option: HTMLButtonElement, _event: PointerEvent): boolean {\n\t\treturn false;\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst group = this.group;\n\n\t\tthis.upgradeProperties('value');\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: group,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tisCopied: (child) => this.isMirrored(child),\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tthis.placeCopies(copies);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tgroup.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tif (event instanceof PointerEvent && event.pointerType !== '') return;\n\n\t\t\t\tconst option = this.optionOf(event.target);\n\t\t\t\tif (option) this.chooseByClick(option);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindPress(group, signal);\n\t\tbindKeyPress(group, signal);\n\t\tgroup.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.defaultPrevented) return;\n\n\t\t\t\tconst option = this.optionOf(event.target);\n\t\t\t\tconst next = option && this.#keyTarget(option, event.key);\n\t\t\t\tif (!next) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.chooseByKey(next, event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected override focusTarget(): HTMLElement | undefined {\n\t\treturn this.options().find((option) => option.tabIndex === 0);\n\t}\n\n\tprotected isMirrored(child: Node): boolean {\n\t\treturn child instanceof Element && child.matches('[data-sonic-value]');\n\t}\n\n\tprotected isOptionDisabled(option: HTMLButtonElement): boolean {\n\t\treturn option.querySelector('[data-sonic-value]')?.hasAttribute('data-sonic-disabled') === true;\n\t}\n\n\tprotected isWrapping(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected optionOf(target: EventTarget | null): HTMLButtonElement | undefined {\n\t\tif (!(target instanceof Element)) return undefined;\n\n\t\tconst option = target.closest('[role=\"radio\"]');\n\n\t\treturn option instanceof HTMLButtonElement && option.parentElement === this.group\n\t\t\t? option\n\t\t\t: undefined;\n\t}\n\n\tprotected options(): Array<HTMLButtonElement> {\n\t\treturn [...this.group.children].filter(\n\t\t\t(child): child is HTMLButtonElement =>\n\t\t\t\tchild instanceof HTMLButtonElement && child.getAttribute('role') === 'radio',\n\t\t);\n\t}\n\n\tprotected placeCopies(copies: Array<Node>): void {\n\t\tthis.#copyOptions(copies);\n\t}\n\n\tprotected releaseTarget(option: HTMLButtonElement): HTMLButtonElement | undefined {\n\t\treturn option;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst options = this.options();\n\t\tconst checked = options.find((option) => optionValue(option) === this.#value);\n\t\tconst enabled = this.isDisabled()\n\t\t\t? []\n\t\t\t: options.filter((option) => !this.isOptionDisabled(option));\n\t\tconst focusable = enabled.find((option) => option === checked) ?? enabled[0];\n\n\t\tfor (const option of options) {\n\t\t\twriteAttribute(option, 'disabled', enabled.includes(option) ? undefined : '');\n\t\t\twriteAttribute(option, 'aria-checked', String(option === checked));\n\t\t\twriteAttribute(option, 'tabindex', option === focusable ? '0' : '-1');\n\t\t}\n\t\tthis.forwardNaming(this.group, true);\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tthis.writeFormValue(checked ? this.#value : null, this.#value);\n\t}\n\n\tprotected abstract renderOption(): HTMLButtonElement;\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = state;\n\t}\n\n\tprotected select(option: HTMLButtonElement): void {\n\t\tconst next = optionValue(option);\n\t\tif (next === undefined || next === this.#value) return;\n\n\t\tthis.value = next;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\tprotected stepFrom(from: number, step: number): HTMLButtonElement | undefined {\n\t\tconst options = this.options();\n\t\tlet index = from;\n\n\t\tfor (const _ of options) {\n\t\t\tconst next = index + step;\n\n\t\t\tindex = this.isWrapping()\n\t\t\t\t? (next + options.length) % options.length\n\t\t\t\t: clamp(next, 0, options.length - 1);\n\n\t\t\tconst option = options[index];\n\t\t\tif (option && !this.isOptionDisabled(option)) return option;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t#bindPress(group: HTMLElement, signal: AbortSignal): void {\n\t\tbindDrag<Press>(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tconst option = this.optionOf(event.target);\n\t\t\t\t\tif (!option || this.isDisabled() || this.isOptionDisabled(option)) return;\n\t\t\t\t\tif (this.claimPress(option, event)) return;\n\n\t\t\t\t\tconst boxes = this.#optionBoxes();\n\n\t\t\t\t\tthis.#markPressed(option);\n\n\t\t\t\t\treturn { boxes, option };\n\t\t\t\t},\n\t\t\t\tlift: (press) => {\n\t\t\t\t\tif (!press.option || this.isDisabled()) return;\n\n\t\t\t\t\tconst option = this.releaseTarget(press.option);\n\t\t\t\t\tif (!option) return;\n\n\t\t\t\t\tthis.select(option);\n\t\t\t\t\tif (group.matches(':focus-within')) option.focus();\n\t\t\t\t},\n\t\t\t\tmove: (press, event) => {\n\t\t\t\t\tpress.option = press.boxes.find(([, box]) =>\n\t\t\t\t\t\tisInside(box, event.clientX, event.clientY),\n\t\t\t\t\t)?.[0];\n\t\t\t\t\tthis.#markPressed(press.option);\n\t\t\t\t},\n\t\t\t\trelease: () => {\n\t\t\t\t\tthis.#markPressed(undefined);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#copyOptions(copies: Array<Node>): void {\n\t\tconst group = this.group;\n\t\tconst options = this.options();\n\t\tconst focused = this.optionOf(group.querySelector(':scope > :focus'));\n\t\tconst focusedValue = focused && optionValue(focused);\n\n\t\tfor (const [index, copy] of copies.entries()) {\n\t\t\tlet option = options[index];\n\t\t\tif (!option) {\n\t\t\t\toption = this.renderOption();\n\t\t\t\tgroup.append(option);\n\t\t\t}\n\n\t\t\tif (option.firstElementChild) placeChildren(option.firstElementChild, [copy]);\n\t\t}\n\t\tfor (const option of options.slice(copies.length)) option.remove();\n\t\tthis.render();\n\t\tif (focused) this.#refocus(focused, focusedValue);\n\t}\n\n\t#keyTarget(option: HTMLButtonElement, key: string): HTMLButtonElement | undefined {\n\t\tconst options = this.options();\n\t\tconst isEnabled = (other: HTMLButtonElement): boolean => !this.isOptionDisabled(other);\n\n\t\tif (key === 'Home') return options.find((other) => isEnabled(other));\n\t\tif (key === 'End') return options.findLast((other) => isEnabled(other));\n\n\t\tconst step = keySteps.get(key);\n\t\tif (step === undefined) return undefined;\n\n\t\treturn this.stepFrom(options.indexOf(option), step) ?? option;\n\t}\n\n\t#markPressed(pressed: HTMLButtonElement | undefined): void {\n\t\tfor (const option of this.options()) {\n\t\t\toption.toggleAttribute('data-sonic-pressed', option === pressed);\n\t\t}\n\t}\n\n\t#optionBoxes(): Array<[HTMLButtonElement, DOMRect]> {\n\t\treturn this.options()\n\t\t\t.filter((option) => !this.isOptionDisabled(option))\n\t\t\t.map((option) => [option, option.getBoundingClientRect()]);\n\t}\n\n\t#refocus(focused: HTMLButtonElement, value: string | undefined): void {\n\t\tconst options = this.options();\n\t\tconst next =\n\t\t\toptions.find((option) => optionValue(option) === value) ??\n\t\t\toptions.find((option) => option.tabIndex === 0);\n\n\t\tif (next !== focused) next?.focus();\n\t}\n}\n"],"mappings":"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"}
@@ -1,35 +1,20 @@
1
- import { SonicRadioGroupElement } from "./radio-group.js";
1
+ import { SonicPositionGroupElement } from "./position-group.js";
2
2
  //#region src/elements/switch.d.ts
3
3
  declare global {
4
4
  interface HTMLElementTagNameMap {
5
5
  'sonic-switch': SonicSwitch;
6
6
  }
7
7
  }
8
- export declare class SonicSwitch extends SonicRadioGroupElement {
8
+ export declare class SonicSwitch extends SonicPositionGroupElement {
9
9
  #private;
10
- static readonly observedAttributes: string[];
11
- get checked(): boolean;
12
- set checked(isChecked: boolean | undefined);
13
- get defaultChecked(): boolean;
14
- set defaultChecked(isChecked: boolean);
15
- get orientation(): 'horizontal' | 'vertical';
16
- set orientation(direction: 'horizontal' | 'vertical' | undefined);
17
- get value(): string;
18
- set value(next: string);
10
+ protected readonly bare: HTMLButtonElement;
11
+ protected readonly defaultOrientation = "vertical";
19
12
  protected readonly group: HTMLDivElement;
20
- attributeChangedCallback(name: string): void;
21
- formResetCallback(): void;
22
- protected activate(): void;
23
- protected chooseByClick(option: HTMLButtonElement): void;
24
- protected chooseByKey(option: HTMLButtonElement, event: KeyboardEvent): void;
25
13
  protected claimPress(option: HTMLButtonElement, event: PointerEvent): boolean;
26
14
  protected connect(signal: AbortSignal): void;
27
- protected focusTarget(): HTMLElement | undefined;
28
- protected isWrapping(): boolean;
29
15
  protected releaseTarget(option: HTMLButtonElement): HTMLButtonElement | undefined;
30
16
  protected render(): void;
31
17
  protected renderOption(): HTMLButtonElement;
32
- protected restoreState(state: string): void;
33
18
  }
34
19
  //#endregion
35
20
  //# sourceMappingURL=switch.d.ts.map
@@ -1,9 +1,7 @@
1
- import { clamp } from "../lib/math.js";
2
- import { writeAttribute } from "../lib/write-attribute.js";
3
- import { bindHold } from "../lib/hold.js";
4
1
  import { requireChild, template } from "../lib/render.js";
5
2
  import { bindDrag } from "../lib/pointer-drag.js";
6
- import { SonicRadioGroupElement, isUnder, optionValue } from "./radio-group.js";
3
+ import { isUnder, optionValue } from "./radio-group.js";
4
+ import { SonicPositionGroupElement } from "./position-group.js";
7
5
 
8
6
  //#region src/elements/switch.ts
9
7
  const renderSwitch = template(`
@@ -19,165 +17,59 @@ const renderPosition = template(`
19
17
  `, HTMLButtonElement);
20
18
  const renderBare = template(`<button class="sonic-switch-position" role="switch" type="button"></button>`, HTMLButtonElement);
21
19
  const pressCenterZone = .08;
22
- function pressedIndex(index, count, offset) {
23
- if (count === 2) return index === 0 ? 1 : 0;
24
- const side = Math.abs(offset) < pressCenterZone ? 1 : Math.sign(offset);
25
- const next = index + side;
26
- return next < 0 || next >= count ? index - side : next;
27
- }
28
- var SonicSwitch = class extends SonicRadioGroupElement {
29
- static observedAttributes = [
30
- ...SonicRadioGroupElement.observedAttributes,
31
- "checked",
32
- "orientation"
33
- ];
34
- get checked() {
35
- return this.#isDirty ? this.#checked : this.defaultChecked;
36
- }
37
- set checked(isChecked) {
38
- this.#isDirty = true;
39
- this.#checked = isChecked === true;
40
- this.render();
41
- }
42
- get defaultChecked() {
43
- return this.hasAttribute("checked");
44
- }
45
- set defaultChecked(isChecked) {
46
- this.reflect("checked", isChecked);
47
- }
48
- get orientation() {
49
- return this.getAttribute("orientation") === "horizontal" ? "horizontal" : "vertical";
50
- }
51
- set orientation(direction) {
52
- this.reflect("orientation", direction);
53
- }
54
- get value() {
55
- const value = super.value;
56
- return value === "" && this.#isBare() ? "on" : value;
57
- }
58
- set value(next) {
59
- super.value = next;
60
- }
20
+ var SonicSwitch = class extends SonicPositionGroupElement {
21
+ bare = renderBare();
22
+ defaultOrientation = "vertical";
61
23
  group = renderSwitch();
62
- #bare = renderBare();
63
24
  #bat = requireChild(this.group, ".sonic-switch-bat", HTMLSpanElement);
64
- #checked = false;
65
- #holding;
66
25
  #isClickSwallowed = false;
67
- #isDirty = false;
68
- attributeChangedCallback(name) {
69
- if (name === "checked") this.#isDirty = false;
70
- if (this.isDisabled()) this.#holding?.release();
71
- super.attributeChangedCallback(name);
72
- }
73
- formResetCallback() {
74
- this.#isDirty = false;
75
- super.formResetCallback();
76
- }
77
- activate() {
78
- if (this.#isBare()) this.#bare.click();
79
- else super.activate();
80
- }
81
- chooseByClick(option) {
82
- super.chooseByClick(option);
83
- if (this.#isMomentary(option)) this.#springBack();
84
- }
85
- chooseByKey(option, event) {
86
- if (!this.#isMomentary(option)) {
87
- super.chooseByKey(option, event);
88
- return;
89
- }
90
- if (event.repeat) return;
91
- this.#hold(option, event.key);
92
- option.focus();
93
- }
94
26
  claimPress(option, event) {
95
27
  if (this.#isOnBat(event)) return true;
96
- if (!this.#isMomentary(option)) return false;
28
+ const target = this.pressTarget(option);
29
+ if (this.isOptionDisabled(target) || !this.isMomentary(target)) return false;
97
30
  this.group.setPointerCapture(event.pointerId);
98
- this.#hold(option, event.pointerId);
31
+ this.hold(target, event.pointerId);
99
32
  return true;
100
33
  }
101
34
  connect(signal) {
102
35
  const group = this.group;
103
- this.upgradeProperties("defaultChecked", "checked", "orientation");
104
36
  super.connect(signal);
105
37
  this.checkStyles(group, "switch.css");
106
- this.#bare.addEventListener("click", (event) => {
38
+ this.bare.addEventListener("click", (event) => {
107
39
  const isSwallowed = this.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== "";
108
40
  this.#isClickSwallowed = false;
109
- if (!isSwallowed) this.#setChecked(!this.checked);
41
+ if (!isSwallowed) this.setChecked(!this.checked);
110
42
  }, { signal });
111
- this.#bare.addEventListener("pointerdown", () => {
43
+ this.bare.addEventListener("pointerdown", () => {
112
44
  this.#isClickSwallowed = false;
113
45
  }, { signal });
114
46
  this.#bindBat(group, signal);
115
- this.#holding = bindHold(group, {
116
- canHold: () => !this.isDisabled(),
117
- leave: "focusout",
118
- onHold: () => {
119
- this.toggleState("held", true);
120
- },
121
- onRelease: () => {
122
- this.toggleState("held", false);
123
- this.#springBack();
124
- }
125
- }, signal);
126
- }
127
- focusTarget() {
128
- return this.#isBare() ? this.#bare : super.focusTarget();
129
- }
130
- isWrapping() {
131
- return false;
132
47
  }
133
48
  releaseTarget(option) {
134
- if (this.#isMomentary(option)) return void 0;
135
- const options = this.options();
136
- if (options.length === 2 && optionValue(option) === this.value) return options.find((other) => other !== option);
137
- return option;
49
+ const target = this.pressTarget(option);
50
+ return this.isMomentary(target) || this.isOptionDisabled(target) ? void 0 : target;
138
51
  }
139
52
  render() {
140
53
  if (!this.isBound()) return;
141
- const group = this.group;
54
+ super.render();
142
55
  const options = this.options();
143
56
  if (options.length > 0) {
144
- this.#bare.remove();
145
- writeAttribute(group, "role", "radiogroup");
146
- writeAttribute(group, "data-sonic-position-count", String(options.length));
147
- super.render();
148
- this.#renderThrow(this.#checkedIndex(options));
57
+ this.#renderThrow(this.checkedIndex(options));
149
58
  return;
150
59
  }
151
- const bare = this.#bare;
152
- const isChecked = this.checked;
153
- writeAttribute(group, "role", void 0);
154
- delete group.dataset.sonicPositionCount;
155
- if (bare.parentElement !== group) group.append(bare);
156
- writeAttribute(bare, "disabled", this.isDisabled() ? "" : void 0);
157
- writeAttribute(bare, "aria-checked", String(isChecked));
158
- this.forwardNaming(group, false);
159
- this.forwardNaming(bare, true);
160
60
  const onSide = this.orientation === "horizontal" ? 1 : -1;
161
- group.style.setProperty("--_sonic-switch-at", String(isChecked ? onSide : -onSide));
162
- this.writeFormValue(isChecked ? this.value : null, String(isChecked));
61
+ this.group.style.setProperty("--_sonic-switch-at", String(this.checked ? onSide : -onSide));
163
62
  }
164
63
  renderOption() {
165
64
  return renderPosition();
166
65
  }
167
- restoreState(state) {
168
- if (this.#isBare()) this.checked = state === "true";
169
- else super.restoreState(state);
170
- }
171
- #along(event) {
172
- return this.orientation === "horizontal" ? event.clientX : event.clientY;
173
- }
174
66
  #bindBat(group, signal) {
175
67
  bindDrag(group, {
176
68
  grab: (event) => {
177
- if (!(event.target === this.#bare || this.optionOf(event.target) !== void 0) || this.isDisabled() || !this.#isOnBat(event)) return;
69
+ if (!(event.target === this.bare || this.optionOf(event.target) !== void 0) || this.isDisabled() || !this.#isOnBat(event)) return;
178
70
  return {
179
71
  hasMoved: false,
180
- last: this.#along(event),
72
+ last: this.along(event),
181
73
  width: this.#bat.getBoundingClientRect().width
182
74
  };
183
75
  },
@@ -187,83 +79,57 @@ var SonicSwitch = class extends SonicRadioGroupElement {
187
79
  },
188
80
  move: (press, event) => {
189
81
  if (this.isDisabled()) return;
190
- const delta = this.#along(event) - press.last;
82
+ const delta = this.along(event) - press.last;
191
83
  if (Math.abs(delta) < press.width / 4) return;
192
84
  press.hasMoved = true;
193
- press.last = this.#along(event);
85
+ press.last = this.along(event);
194
86
  this.#throwBy(Math.sign(delta), event.pointerId);
195
87
  }
196
88
  }, signal);
197
89
  }
198
- #checkedIndex(options) {
199
- return options.findIndex((option) => optionValue(option) === this.value);
200
- }
201
90
  #hasSprungFrom(option) {
202
- if (!option || !this.#isMomentary(option)) return false;
203
- this.#holding?.release();
91
+ if (!option || !this.isMomentary(option)) return false;
92
+ this.releaseHold();
204
93
  return this.value !== optionValue(option);
205
94
  }
206
- #hold(option, by) {
207
- if (this.#holding?.hold(by) === true) this.select(option);
208
- }
209
- #isBare() {
210
- return this.options().length === 0;
211
- }
212
- #isMomentary(option) {
213
- const options = this.options();
214
- return (option === options[0] || option === options.at(-1)) && options.length > 1 && option.querySelector("[data-sonic-value]")?.hasAttribute("data-sonic-momentary") === true;
215
- }
216
95
  #isOnBat(event) {
217
96
  return isUnder(this.#bat, event.clientX, event.clientY);
218
97
  }
219
98
  #pressBat(event) {
220
99
  const options = this.options();
221
100
  if (options.length === 0) {
222
- this.#setChecked(!this.checked);
101
+ this.setChecked(!this.checked);
223
102
  return;
224
103
  }
225
104
  const box = this.#bat.getBoundingClientRect();
226
105
  const [start, size] = this.orientation === "horizontal" ? [box.left, box.width] : [box.top, box.height];
227
- const offset = (this.#along(event) - start) / size - .5;
228
- const option = options[pressedIndex(this.#checkedIndex(options), options.length, offset)];
106
+ const offset = (this.along(event) - start) / size - .5;
107
+ const index = this.checkedIndex(options);
108
+ const side = Math.abs(offset) < pressCenterZone ? 1 : Math.sign(offset);
109
+ const toward = this.stepFrom(index, side);
110
+ const option = toward && toward !== options[index] ? toward : this.stepFrom(index, -side);
229
111
  if (!option) return;
230
112
  this.chooseByClick(option);
231
113
  this.#refocus(options);
232
114
  }
233
115
  #refocus(options) {
234
- if (this.group.matches(":focus-within")) options[this.#checkedIndex(options)]?.focus();
116
+ if (this.group.matches(":focus-within")) options[this.checkedIndex(options)]?.focus();
235
117
  }
236
118
  #renderThrow(index) {
237
119
  const count = this.options().length;
238
120
  const at = index < 0 || count < 2 ? 0 : 2 * index / (count - 1) - 1;
239
121
  this.group.style.setProperty("--_sonic-switch-at", String(at));
240
122
  }
241
- #setChecked(isChecked) {
242
- if (isChecked === this.checked) return;
243
- this.checked = isChecked;
244
- this.dispatchEvent(new Event("change", { bubbles: true }));
245
- }
246
- #springBack() {
247
- const options = this.options();
248
- const index = this.#checkedIndex(options);
249
- const held = options[index];
250
- if (!held || !this.#isMomentary(held)) return;
251
- const rest = options[index === 0 ? 1 : index - 1];
252
- if (!rest) return;
253
- const isFocused = this.group.contains(this.ownerDocument.activeElement);
254
- this.select(rest);
255
- if (isFocused) rest.focus();
256
- }
257
123
  #throwBy(direction, pointerId) {
258
124
  const options = this.options();
259
125
  if (options.length === 0) {
260
- this.#setChecked(direction === (this.orientation === "horizontal" ? 1 : -1));
126
+ this.setChecked(direction === (this.orientation === "horizontal" ? 1 : -1));
261
127
  return;
262
128
  }
263
- const index = this.#checkedIndex(options);
264
- const option = options[clamp(index + direction, 0, options.length - 1)];
129
+ const index = this.checkedIndex(options);
130
+ const option = this.stepFrom(index, direction);
265
131
  if (!option || option === options[index] || this.#hasSprungFrom(options[index])) return;
266
- if (this.#isMomentary(option)) this.#hold(option, pointerId);
132
+ if (this.isMomentary(option)) this.hold(option, pointerId);
267
133
  else this.select(option);
268
134
  this.#refocus(options);
269
135
  }
@@ -1 +1 @@
1
- {"version":3,"file":"switch.js","names":[],"sources":["../../../src/elements/switch.ts"],"sourcesContent":["import type { Hold } from '#lib/hold.ts';\n\nimport { isUnder, optionValue, SonicRadioGroupElement } from '#elements/radio-group.ts';\nimport { bindHold } from '#lib/hold.ts';\nimport { clamp } from '#lib/math.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-switch': SonicSwitch;\n\t}\n}\n\nconst renderSwitch = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-switch\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-switch-bushing\"></span>\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-switch-bat\"></span>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst renderPosition = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-switch-position\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-switch-label\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst renderBare = template(\n\t/* HTML */ `<button class=\"sonic-switch-position\" role=\"switch\" type=\"button\"></button>`,\n\tHTMLButtonElement,\n);\n\ninterface BatPress {\n\thasMoved: boolean;\n\tlast: number;\n\twidth: number;\n}\n\nconst pressCenterZone = 0.08;\n\nfunction pressedIndex(index: number, count: number, offset: number): number {\n\tif (count === 2) return index === 0 ? 1 : 0;\n\n\tconst side = Math.abs(offset) < pressCenterZone ? 1 : Math.sign(offset);\n\tconst next = index + side;\n\n\treturn next < 0 || next >= count ? index - side : next;\n}\n\nexport class SonicSwitch extends SonicRadioGroupElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicRadioGroupElement.observedAttributes,\n\t\t'checked',\n\t\t'orientation',\n\t];\n\n\tget checked(): boolean {\n\t\treturn this.#isDirty ? this.#checked : this.defaultChecked;\n\t}\n\n\tset checked(isChecked: boolean | undefined) {\n\t\tthis.#isDirty = true;\n\t\tthis.#checked = isChecked === true;\n\t\tthis.render();\n\t}\n\n\tget defaultChecked(): boolean {\n\t\treturn this.hasAttribute('checked');\n\t}\n\n\tset defaultChecked(isChecked: boolean) {\n\t\tthis.reflect('checked', isChecked);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'horizontal' ? 'horizontal' : 'vertical';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\toverride get value(): string {\n\t\tconst value = super.value;\n\n\t\treturn value === '' && this.#isBare() ? 'on' : value;\n\t}\n\n\toverride set value(next: string) {\n\t\tsuper.value = next;\n\t}\n\n\tprotected readonly group = renderSwitch();\n\n\treadonly #bare = renderBare();\n\n\treadonly #bat = requireChild(this.group, '.sonic-switch-bat', HTMLSpanElement);\n\n\t#checked = false;\n\n\t#holding: Hold | undefined;\n\n\t#isClickSwallowed = false;\n\n\t#isDirty = false;\n\n\toverride attributeChangedCallback(name: string): void {\n\t\tif (name === 'checked') this.#isDirty = false;\n\t\tif (this.isDisabled()) this.#holding?.release();\n\t\tsuper.attributeChangedCallback(name);\n\t}\n\n\toverride formResetCallback(): void {\n\t\tthis.#isDirty = false;\n\t\tsuper.formResetCallback();\n\t}\n\n\tprotected override activate(): void {\n\t\tif (this.#isBare()) this.#bare.click();\n\t\telse super.activate();\n\t}\n\n\tprotected override chooseByClick(option: HTMLButtonElement): void {\n\t\tsuper.chooseByClick(option);\n\t\tif (this.#isMomentary(option)) this.#springBack();\n\t}\n\n\tprotected override chooseByKey(option: HTMLButtonElement, event: KeyboardEvent): void {\n\t\tif (!this.#isMomentary(option)) {\n\t\t\tsuper.chooseByKey(option, event);\n\t\t\treturn;\n\t\t}\n\t\tif (event.repeat) return;\n\n\t\tthis.#hold(option, event.key);\n\t\toption.focus();\n\t}\n\n\tprotected override claimPress(option: HTMLButtonElement, event: PointerEvent): boolean {\n\t\tif (this.#isOnBat(event)) return true;\n\t\tif (!this.#isMomentary(option)) return false;\n\n\t\tthis.group.setPointerCapture(event.pointerId);\n\t\tthis.#hold(option, event.pointerId);\n\n\t\treturn true;\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tconst group = this.group;\n\n\t\tthis.upgradeProperties('defaultChecked', 'checked', 'orientation');\n\t\tsuper.connect(signal);\n\t\tthis.checkStyles(group, 'switch.css');\n\n\t\tthis.#bare.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tconst isSwallowed =\n\t\t\t\t\tthis.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== '';\n\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t\tif (!isSwallowed) this.#setChecked(!this.checked);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bare.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t() => {\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindBat(group, signal);\n\n\t\tthis.#holding = bindHold(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tcanHold: () => !this.isDisabled(),\n\t\t\t\tleave: 'focusout',\n\t\t\t\tonHold: () => {\n\t\t\t\t\tthis.toggleState('held', true);\n\t\t\t\t},\n\t\t\t\tonRelease: () => {\n\t\t\t\t\tthis.toggleState('held', false);\n\t\t\t\t\tthis.#springBack();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\tprotected override focusTarget(): HTMLElement | undefined {\n\t\treturn this.#isBare() ? this.#bare : super.focusTarget();\n\t}\n\n\tprotected override isWrapping(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected override releaseTarget(option: HTMLButtonElement): HTMLButtonElement | undefined {\n\t\tif (this.#isMomentary(option)) return undefined;\n\n\t\tconst options = this.options();\n\t\tif (options.length === 2 && optionValue(option) === this.value) {\n\t\t\treturn options.find((other) => other !== option);\n\t\t}\n\n\t\treturn option;\n\t}\n\n\tprotected override render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst group = this.group;\n\t\tconst options = this.options();\n\n\t\tif (options.length > 0) {\n\t\t\tthis.#bare.remove();\n\t\t\twriteAttribute(group, 'role', 'radiogroup');\n\t\t\twriteAttribute(group, 'data-sonic-position-count', String(options.length));\n\t\t\tsuper.render();\n\t\t\tthis.#renderThrow(this.#checkedIndex(options));\n\t\t\treturn;\n\t\t}\n\n\t\tconst bare = this.#bare;\n\t\tconst isChecked = this.checked;\n\n\t\twriteAttribute(group, 'role', undefined);\n\t\tdelete group.dataset.sonicPositionCount;\n\t\tif (bare.parentElement !== group) group.append(bare);\n\t\twriteAttribute(bare, 'disabled', this.isDisabled() ? '' : undefined);\n\t\twriteAttribute(bare, 'aria-checked', String(isChecked));\n\t\tthis.forwardNaming(group, false);\n\t\tthis.forwardNaming(bare, true);\n\t\tconst onSide = this.orientation === 'horizontal' ? 1 : -1;\n\n\t\tgroup.style.setProperty('--_sonic-switch-at', String(isChecked ? onSide : -onSide));\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tthis.writeFormValue(isChecked ? this.value : null, String(isChecked));\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderPosition();\n\t}\n\n\tprotected override restoreState(state: string): void {\n\t\tif (this.#isBare()) this.checked = state === 'true';\n\t\telse super.restoreState(state);\n\t}\n\n\t#along(event: PointerEvent): number {\n\t\treturn this.orientation === 'horizontal' ? event.clientX : event.clientY;\n\t}\n\n\t#bindBat(group: HTMLElement, signal: AbortSignal): void {\n\t\tbindDrag<BatPress>(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tconst isOnPosition =\n\t\t\t\t\t\tevent.target === this.#bare || this.optionOf(event.target) !== undefined;\n\t\t\t\t\tif (!isOnPosition || this.isDisabled() || !this.#isOnBat(event)) return;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\thasMoved: false,\n\t\t\t\t\t\tlast: this.#along(event),\n\t\t\t\t\t\twidth: this.#bat.getBoundingClientRect().width,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\tlift: (press, event) => {\n\t\t\t\t\tif (!press.hasMoved && !this.isDisabled()) this.#pressBat(event);\n\t\t\t\t\tthis.#isClickSwallowed = true;\n\t\t\t\t},\n\t\t\t\tmove: (press, event) => {\n\t\t\t\t\tif (this.isDisabled()) return;\n\n\t\t\t\t\tconst delta = this.#along(event) - press.last;\n\t\t\t\t\tif (Math.abs(delta) < press.width / 4) return;\n\n\t\t\t\t\tpress.hasMoved = true;\n\t\t\t\t\tpress.last = this.#along(event);\n\t\t\t\t\tthis.#throwBy(Math.sign(delta), event.pointerId);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#checkedIndex(options: Array<HTMLButtonElement>): number {\n\t\treturn options.findIndex((option) => optionValue(option) === this.value);\n\t}\n\n\t#hasSprungFrom(option: HTMLButtonElement | undefined): boolean {\n\t\tif (!option || !this.#isMomentary(option)) return false;\n\n\t\tthis.#holding?.release();\n\n\t\treturn this.value !== optionValue(option);\n\t}\n\n\t#hold(option: HTMLButtonElement, by: number | string): void {\n\t\tif (this.#holding?.hold(by) === true) this.select(option);\n\t}\n\n\t#isBare(): boolean {\n\t\treturn this.options().length === 0;\n\t}\n\n\t#isMomentary(option: HTMLButtonElement): boolean {\n\t\tconst options = this.options();\n\t\tconst isEnd = option === options[0] || option === options.at(-1);\n\n\t\treturn (\n\t\t\tisEnd &&\n\t\t\toptions.length > 1 &&\n\t\t\toption.querySelector('[data-sonic-value]')?.hasAttribute('data-sonic-momentary') === true\n\t\t);\n\t}\n\n\t#isOnBat(event: PointerEvent): boolean {\n\t\treturn isUnder(this.#bat, event.clientX, event.clientY);\n\t}\n\n\t#pressBat(event: PointerEvent): void {\n\t\tconst options = this.options();\n\t\tif (options.length === 0) {\n\t\t\tthis.#setChecked(!this.checked);\n\t\t\treturn;\n\t\t}\n\n\t\tconst box = this.#bat.getBoundingClientRect();\n\t\tconst [start, size] =\n\t\t\tthis.orientation === 'horizontal' ? [box.left, box.width] : [box.top, box.height];\n\t\tconst offset = (this.#along(event) - start) / size - 0.5;\n\t\tconst option = options[pressedIndex(this.#checkedIndex(options), options.length, offset)];\n\t\tif (!option) return;\n\n\t\tthis.chooseByClick(option);\n\t\tthis.#refocus(options);\n\t}\n\n\t#refocus(options: Array<HTMLButtonElement>): void {\n\t\tif (this.group.matches(':focus-within')) options[this.#checkedIndex(options)]?.focus();\n\t}\n\n\t#renderThrow(index: number): void {\n\t\tconst count = this.options().length;\n\t\tconst at = index < 0 || count < 2 ? 0 : (2 * index) / (count - 1) - 1;\n\n\t\tthis.group.style.setProperty('--_sonic-switch-at', String(at));\n\t}\n\n\t#setChecked(isChecked: boolean): void {\n\t\tif (isChecked === this.checked) return;\n\n\t\tthis.checked = isChecked;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\t#springBack(): void {\n\t\tconst options = this.options();\n\t\tconst index = this.#checkedIndex(options);\n\t\tconst held = options[index];\n\t\tif (!held || !this.#isMomentary(held)) return;\n\n\t\tconst rest = options[index === 0 ? 1 : index - 1];\n\t\tif (!rest) return;\n\n\t\tconst isFocused = this.group.contains(this.ownerDocument.activeElement);\n\n\t\tthis.select(rest);\n\t\tif (isFocused) rest.focus();\n\t}\n\n\t#throwBy(direction: number, pointerId: number): void {\n\t\tconst options = this.options();\n\t\tif (options.length === 0) {\n\t\t\tthis.#setChecked(direction === (this.orientation === 'horizontal' ? 1 : -1));\n\t\t\treturn;\n\t\t}\n\n\t\tconst index = this.#checkedIndex(options);\n\t\tconst option = options[clamp(index + direction, 0, options.length - 1)];\n\t\tif (!option || option === options[index] || this.#hasSprungFrom(options[index])) return;\n\n\t\tif (this.#isMomentary(option)) this.#hold(option, pointerId);\n\t\telse this.select(option);\n\t\tthis.#refocus(options);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"switch.js","names":[],"sources":["../../../src/elements/switch.ts"],"sourcesContent":["import { SonicPositionGroupElement } from '#elements/position-group.ts';\nimport { isUnder, optionValue } from '#elements/radio-group.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-switch': SonicSwitch;\n\t}\n}\n\nconst renderSwitch = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-switch\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-switch-bushing\"></span>\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-switch-bat\"></span>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst renderPosition = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-switch-position\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-switch-label\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst renderBare = template(\n\t/* HTML */ `<button class=\"sonic-switch-position\" role=\"switch\" type=\"button\"></button>`,\n\tHTMLButtonElement,\n);\n\ninterface BatPress {\n\thasMoved: boolean;\n\tlast: number;\n\twidth: number;\n}\n\nconst pressCenterZone = 0.08;\n\nexport class SonicSwitch extends SonicPositionGroupElement {\n\tprotected readonly bare = renderBare();\n\n\tprotected readonly defaultOrientation = 'vertical';\n\n\tprotected readonly group = renderSwitch();\n\n\treadonly #bat = requireChild(this.group, '.sonic-switch-bat', HTMLSpanElement);\n\n\t#isClickSwallowed = false;\n\n\tprotected override claimPress(option: HTMLButtonElement, event: PointerEvent): boolean {\n\t\tif (this.#isOnBat(event)) return true;\n\n\t\tconst target = this.pressTarget(option);\n\t\tif (this.isOptionDisabled(target) || !this.isMomentary(target)) return false;\n\n\t\tthis.group.setPointerCapture(event.pointerId);\n\t\tthis.hold(target, event.pointerId);\n\n\t\treturn true;\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tconst group = this.group;\n\n\t\tsuper.connect(signal);\n\t\tthis.checkStyles(group, 'switch.css');\n\n\t\tthis.bare.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tconst isSwallowed =\n\t\t\t\t\tthis.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== '';\n\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t\tif (!isSwallowed) this.setChecked(!this.checked);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bare.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t() => {\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindBat(group, signal);\n\t}\n\n\tprotected override releaseTarget(option: HTMLButtonElement): HTMLButtonElement | undefined {\n\t\tconst target = this.pressTarget(option);\n\n\t\treturn this.isMomentary(target) || this.isOptionDisabled(target) ? undefined : target;\n\t}\n\n\tprotected override render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tsuper.render();\n\n\t\tconst options = this.options();\n\t\tif (options.length > 0) {\n\t\t\tthis.#renderThrow(this.checkedIndex(options));\n\t\t\treturn;\n\t\t}\n\n\t\tconst onSide = this.orientation === 'horizontal' ? 1 : -1;\n\n\t\tthis.group.style.setProperty('--_sonic-switch-at', String(this.checked ? onSide : -onSide));\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderPosition();\n\t}\n\n\t#bindBat(group: HTMLElement, signal: AbortSignal): void {\n\t\tbindDrag<BatPress>(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tconst isOnPosition =\n\t\t\t\t\t\tevent.target === this.bare || this.optionOf(event.target) !== undefined;\n\t\t\t\t\tif (!isOnPosition || this.isDisabled() || !this.#isOnBat(event)) return;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\thasMoved: false,\n\t\t\t\t\t\tlast: this.along(event),\n\t\t\t\t\t\twidth: this.#bat.getBoundingClientRect().width,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\tlift: (press, event) => {\n\t\t\t\t\tif (!press.hasMoved && !this.isDisabled()) this.#pressBat(event);\n\t\t\t\t\tthis.#isClickSwallowed = true;\n\t\t\t\t},\n\t\t\t\tmove: (press, event) => {\n\t\t\t\t\tif (this.isDisabled()) return;\n\n\t\t\t\t\tconst delta = this.along(event) - press.last;\n\t\t\t\t\tif (Math.abs(delta) < press.width / 4) return;\n\n\t\t\t\t\tpress.hasMoved = true;\n\t\t\t\t\tpress.last = this.along(event);\n\t\t\t\t\tthis.#throwBy(Math.sign(delta), event.pointerId);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#hasSprungFrom(option: HTMLButtonElement | undefined): boolean {\n\t\tif (!option || !this.isMomentary(option)) return false;\n\n\t\tthis.releaseHold();\n\n\t\treturn this.value !== optionValue(option);\n\t}\n\n\t#isOnBat(event: PointerEvent): boolean {\n\t\treturn isUnder(this.#bat, event.clientX, event.clientY);\n\t}\n\n\t#pressBat(event: PointerEvent): void {\n\t\tconst options = this.options();\n\t\tif (options.length === 0) {\n\t\t\tthis.setChecked(!this.checked);\n\t\t\treturn;\n\t\t}\n\n\t\tconst box = this.#bat.getBoundingClientRect();\n\t\tconst [start, size] =\n\t\t\tthis.orientation === 'horizontal' ? [box.left, box.width] : [box.top, box.height];\n\t\tconst offset = (this.along(event) - start) / size - 0.5;\n\t\tconst index = this.checkedIndex(options);\n\t\tconst side = Math.abs(offset) < pressCenterZone ? 1 : Math.sign(offset);\n\t\tconst toward = this.stepFrom(index, side);\n\t\tconst option = toward && toward !== options[index] ? toward : this.stepFrom(index, -side);\n\t\tif (!option) return;\n\n\t\tthis.chooseByClick(option);\n\t\tthis.#refocus(options);\n\t}\n\n\t#refocus(options: Array<HTMLButtonElement>): void {\n\t\tif (this.group.matches(':focus-within')) options[this.checkedIndex(options)]?.focus();\n\t}\n\n\t#renderThrow(index: number): void {\n\t\tconst count = this.options().length;\n\t\tconst at = index < 0 || count < 2 ? 0 : (2 * index) / (count - 1) - 1;\n\n\t\tthis.group.style.setProperty('--_sonic-switch-at', String(at));\n\t}\n\n\t#throwBy(direction: number, pointerId: number): void {\n\t\tconst options = this.options();\n\t\tif (options.length === 0) {\n\t\t\tthis.setChecked(direction === (this.orientation === 'horizontal' ? 1 : -1));\n\t\t\treturn;\n\t\t}\n\n\t\tconst index = this.checkedIndex(options);\n\t\tconst option = this.stepFrom(index, direction);\n\t\tif (!option || option === options[index] || this.#hasSprungFrom(options[index])) return;\n\n\t\tif (this.isMomentary(option)) this.hold(option, pointerId);\n\t\telse this.select(option);\n\t\tthis.#refocus(options);\n\t}\n}\n"],"mappings":";;;;;;AAWA,MAAM,eAAe,SACT;;;;;IAMX,cACD;AAEA,MAAM,iBAAiB,SACX;;;;IAKX,iBACD;AAEA,MAAM,aAAa,SACP,+EACX,iBACD;AAQA,MAAM,kBAAkB;AAExB,IAAa,cAAb,cAAiC,0BAA0B;CAC1D,AAAmB,OAAO,WAAW;CAErC,AAAmB,qBAAqB;CAExC,AAAmB,QAAQ,aAAa;CAExC,AAAS,OAAO,aAAa,KAAK,OAAO,qBAAqB,eAAe;CAE7E,oBAAoB;CAEpB,AAAmB,WAAW,QAA2B,OAA8B;EACtF,IAAI,KAAK,SAAS,KAAK,GAAG,OAAO;EAEjC,MAAM,SAAS,KAAK,YAAY,MAAM;EACtC,IAAI,KAAK,iBAAiB,MAAM,KAAK,CAAC,KAAK,YAAY,MAAM,GAAG,OAAO;EAEvE,KAAK,MAAM,kBAAkB,MAAM,SAAS;EAC5C,KAAK,KAAK,QAAQ,MAAM,SAAS;EAEjC,OAAO;CACR;CAEA,AAAmB,QAAQ,QAA2B;EACrD,MAAM,QAAQ,KAAK;EAEnB,MAAM,QAAQ,MAAM;EACpB,KAAK,YAAY,OAAO,YAAY;EAEpC,KAAK,KAAK,iBACT,UACC,UAAU;GACV,MAAM,cACL,KAAK,qBAAqB,iBAAiB,gBAAgB,MAAM,gBAAgB;GAElF,KAAK,oBAAoB;GACzB,IAAI,CAAC,aAAa,KAAK,WAAW,CAAC,KAAK,OAAO;EAChD,GACA,EAAE,OAAO,CACV;EACA,KAAK,KAAK,iBACT,qBACM;GACL,KAAK,oBAAoB;EAC1B,GACA,EAAE,OAAO,CACV;EACA,KAAK,SAAS,OAAO,MAAM;CAC5B;CAEA,AAAmB,cAAc,QAA0D;EAC1F,MAAM,SAAS,KAAK,YAAY,MAAM;EAEtC,OAAO,KAAK,YAAY,MAAM,KAAK,KAAK,iBAAiB,MAAM,IAAI,SAAY;CAChF;CAEA,AAAmB,SAAe;EACjC,IAAI,CAAC,KAAK,QAAQ,GAAG;EAErB,MAAM,OAAO;EAEb,MAAM,UAAU,KAAK,QAAQ;EAC7B,IAAI,QAAQ,SAAS,GAAG;GACvB,KAAK,aAAa,KAAK,aAAa,OAAO,CAAC;GAC5C;EACD;EAEA,MAAM,SAAS,KAAK,gBAAgB,eAAe,IAAI;EAEvD,KAAK,MAAM,MAAM,YAAY,sBAAsB,OAAO,KAAK,UAAU,SAAS,CAAC,MAAM,CAAC;CAC3F;CAEA,AAAU,eAAkC;EAC3C,OAAO,eAAe;CACvB;CAEA,SAAS,OAAoB,QAA2B;EACvD,SACC,OACA;GACC,OAAO,UAAU;IAGhB,IAAI,EADH,MAAM,WAAW,KAAK,QAAQ,KAAK,SAAS,MAAM,MAAM,MAAM,WAC1C,KAAK,WAAW,KAAK,CAAC,KAAK,SAAS,KAAK,GAAG;IAEjE,OAAO;KACN,UAAU;KACV,MAAM,KAAK,MAAM,KAAK;KACtB,OAAO,KAAK,KAAK,sBAAsB,CAAC,CAAC;IAC1C;GACD;GACA,OAAO,OAAO,UAAU;IACvB,IAAI,CAAC,MAAM,YAAY,CAAC,KAAK,WAAW,GAAG,KAAK,UAAU,KAAK;IAC/D,KAAK,oBAAoB;GAC1B;GACA,OAAO,OAAO,UAAU;IACvB,IAAI,KAAK,WAAW,GAAG;IAEvB,MAAM,QAAQ,KAAK,MAAM,KAAK,IAAI,MAAM;IACxC,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,QAAQ,GAAG;IAEvC,MAAM,WAAW;IACjB,MAAM,OAAO,KAAK,MAAM,KAAK;IAC7B,KAAK,SAAS,KAAK,KAAK,KAAK,GAAG,MAAM,SAAS;GAChD;EACD,GACA,MACD;CACD;CAEA,eAAe,QAAgD;EAC9D,IAAI,CAAC,UAAU,CAAC,KAAK,YAAY,MAAM,GAAG,OAAO;EAEjD,KAAK,YAAY;EAEjB,OAAO,KAAK,UAAU,YAAY,MAAM;CACzC;CAEA,SAAS,OAA8B;EACtC,OAAO,QAAQ,KAAK,MAAM,MAAM,SAAS,MAAM,OAAO;CACvD;CAEA,UAAU,OAA2B;EACpC,MAAM,UAAU,KAAK,QAAQ;EAC7B,IAAI,QAAQ,WAAW,GAAG;GACzB,KAAK,WAAW,CAAC,KAAK,OAAO;GAC7B;EACD;EAEA,MAAM,MAAM,KAAK,KAAK,sBAAsB;EAC5C,MAAM,CAAC,OAAO,QACb,KAAK,gBAAgB,eAAe,CAAC,IAAI,MAAM,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,MAAM;EACjF,MAAM,UAAU,KAAK,MAAM,KAAK,IAAI,SAAS,OAAO;EACpD,MAAM,QAAQ,KAAK,aAAa,OAAO;EACvC,MAAM,OAAO,KAAK,IAAI,MAAM,IAAI,kBAAkB,IAAI,KAAK,KAAK,MAAM;EACtE,MAAM,SAAS,KAAK,SAAS,OAAO,IAAI;EACxC,MAAM,SAAS,UAAU,WAAW,QAAQ,SAAS,SAAS,KAAK,SAAS,OAAO,CAAC,IAAI;EACxF,IAAI,CAAC,QAAQ;EAEb,KAAK,cAAc,MAAM;EACzB,KAAK,SAAS,OAAO;CACtB;CAEA,SAAS,SAAyC;EACjD,IAAI,KAAK,MAAM,QAAQ,eAAe,GAAG,QAAQ,KAAK,aAAa,OAAO,EAAE,EAAE,MAAM;CACrF;CAEA,aAAa,OAAqB;EACjC,MAAM,QAAQ,KAAK,QAAQ,CAAC,CAAC;EAC7B,MAAM,KAAK,QAAQ,KAAK,QAAQ,IAAI,IAAK,IAAI,SAAU,QAAQ,KAAK;EAEpE,KAAK,MAAM,MAAM,YAAY,sBAAsB,OAAO,EAAE,CAAC;CAC9D;CAEA,SAAS,WAAmB,WAAyB;EACpD,MAAM,UAAU,KAAK,QAAQ;EAC7B,IAAI,QAAQ,WAAW,GAAG;GACzB,KAAK,WAAW,eAAe,KAAK,gBAAgB,eAAe,IAAI,GAAG;GAC1E;EACD;EAEA,MAAM,QAAQ,KAAK,aAAa,OAAO;EACvC,MAAM,SAAS,KAAK,SAAS,OAAO,SAAS;EAC7C,IAAI,CAAC,UAAU,WAAW,QAAQ,UAAU,KAAK,eAAe,QAAQ,MAAM,GAAG;EAEjF,IAAI,KAAK,YAAY,MAAM,GAAG,KAAK,KAAK,QAAQ,SAAS;OACpD,KAAK,OAAO,MAAM;EACvB,KAAK,SAAS,OAAO;CACtB;AACD"}
@@ -0,0 +1,21 @@
1
+ import { SonicPositionGroupElement } from "./position-group.js";
2
+ //#region src/elements/toggle.d.ts
3
+ declare global {
4
+ interface HTMLElementTagNameMap {
5
+ 'sonic-toggle': SonicToggle;
6
+ }
7
+ }
8
+ export declare class SonicToggle extends SonicPositionGroupElement {
9
+ #private;
10
+ protected readonly bare: HTMLButtonElement;
11
+ protected readonly defaultOrientation = "horizontal";
12
+ protected readonly group: HTMLDivElement;
13
+ protected claimPress(): boolean;
14
+ protected connect(signal: AbortSignal): void;
15
+ protected isMirrored(child: Node): boolean;
16
+ protected placeCopies(copies: Array<Node>): void;
17
+ protected render(): void;
18
+ protected renderOption(): HTMLButtonElement;
19
+ }
20
+ //#endregion
21
+ //# sourceMappingURL=toggle.d.ts.map