@xsynaptic/sonic-ui 0.5.0 → 0.6.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 (118) hide show
  1. package/README.md +3 -1
  2. package/dist/default/dat.js +5 -4
  3. package/dist/default/define/button.js +1 -1
  4. package/dist/default/define.js +2 -2
  5. package/dist/default/elements/button.js +3 -3
  6. package/dist/default/elements/envelope.js +32 -16
  7. package/dist/default/elements/meter.js +11 -7
  8. package/dist/default/elements/number.js +1 -1
  9. package/dist/default/elements/radio-group.js +43 -45
  10. package/dist/default/elements/readout.js +1 -1
  11. package/dist/default/elements/sonic-element.js +56 -29
  12. package/dist/default/elements/switch.js +32 -38
  13. package/dist/default/elements/value-element.js +111 -108
  14. package/dist/default/elements/wave-element.js +5 -2
  15. package/dist/default/elements/waveform.js +35 -12
  16. package/dist/default/elements/wavestrip.js +18 -15
  17. package/dist/default/elements/xy.js +32 -18
  18. package/dist/default/index.js +2 -2
  19. package/dist/default/lib/adsr-shape.js +5 -0
  20. package/dist/default/lib/ballistics.js +2 -2
  21. package/dist/default/lib/canvas-surface.js +13 -2
  22. package/dist/default/lib/distribute.js +1 -1
  23. package/dist/default/lib/drag-step.js +4 -4
  24. package/dist/default/lib/frame-timeline.js +2 -1
  25. package/dist/default/lib/marker-band.js +54 -10
  26. package/dist/default/lib/marker-rider.js +10 -4
  27. package/dist/default/lib/math.js +6 -1
  28. package/dist/default/lib/pointer-drag.js +8 -5
  29. package/dist/default/lib/resample-peaks.js +11 -1
  30. package/dist/default/lib/write-attribute.js +1 -0
  31. package/dist/dev/dat.d.ts +2 -2
  32. package/dist/dev/dat.js +5 -4
  33. package/dist/dev/dat.js.map +1 -1
  34. package/dist/dev/define/button.js +1 -1
  35. package/dist/dev/define.d.ts +4 -4
  36. package/dist/dev/define.js +2 -2
  37. package/dist/dev/elements/button.js +3 -3
  38. package/dist/dev/elements/button.js.map +1 -1
  39. package/dist/dev/elements/dial.d.ts +1 -1
  40. package/dist/dev/elements/dial.js.map +1 -1
  41. package/dist/dev/elements/envelope.js +32 -16
  42. package/dist/dev/elements/envelope.js.map +1 -1
  43. package/dist/dev/elements/meter.d.ts +1 -1
  44. package/dist/dev/elements/meter.js +11 -7
  45. package/dist/dev/elements/meter.js.map +1 -1
  46. package/dist/dev/elements/number.js +1 -1
  47. package/dist/dev/elements/number.js.map +1 -1
  48. package/dist/dev/elements/radio-group.js +43 -45
  49. package/dist/dev/elements/radio-group.js.map +1 -1
  50. package/dist/dev/elements/readout.js +1 -1
  51. package/dist/dev/elements/readout.js.map +1 -1
  52. package/dist/dev/elements/slider.d.ts +1 -1
  53. package/dist/dev/elements/slider.js.map +1 -1
  54. package/dist/dev/elements/sonic-element.d.ts +4 -2
  55. package/dist/dev/elements/sonic-element.js +79 -29
  56. package/dist/dev/elements/sonic-element.js.map +1 -1
  57. package/dist/dev/elements/switch.js +32 -38
  58. package/dist/dev/elements/switch.js.map +1 -1
  59. package/dist/dev/elements/value-element.d.ts +3 -3
  60. package/dist/dev/elements/value-element.js +111 -108
  61. package/dist/dev/elements/value-element.js.map +1 -1
  62. package/dist/dev/elements/wave-element.d.ts +1 -0
  63. package/dist/dev/elements/wave-element.js +5 -2
  64. package/dist/dev/elements/wave-element.js.map +1 -1
  65. package/dist/dev/elements/waveform.d.ts +1 -1
  66. package/dist/dev/elements/waveform.js +35 -12
  67. package/dist/dev/elements/waveform.js.map +1 -1
  68. package/dist/dev/elements/wavestrip.js +18 -15
  69. package/dist/dev/elements/wavestrip.js.map +1 -1
  70. package/dist/dev/elements/xy.d.ts +3 -3
  71. package/dist/dev/elements/xy.js +32 -18
  72. package/dist/dev/elements/xy.js.map +1 -1
  73. package/dist/dev/index.d.ts +2 -2
  74. package/dist/dev/index.js +2 -2
  75. package/dist/dev/lib/adsr-shape.js +5 -0
  76. package/dist/dev/lib/adsr-shape.js.map +1 -1
  77. package/dist/dev/lib/ballistics.js +2 -2
  78. package/dist/dev/lib/ballistics.js.map +1 -1
  79. package/dist/dev/lib/canvas-surface.d.ts +1 -0
  80. package/dist/dev/lib/canvas-surface.js +13 -2
  81. package/dist/dev/lib/canvas-surface.js.map +1 -1
  82. package/dist/dev/lib/distribute.js +1 -1
  83. package/dist/dev/lib/distribute.js.map +1 -1
  84. package/dist/dev/lib/drag-step.js +4 -4
  85. package/dist/dev/lib/drag-step.js.map +1 -1
  86. package/dist/dev/lib/frame-timeline.js +2 -1
  87. package/dist/dev/lib/frame-timeline.js.map +1 -1
  88. package/dist/dev/lib/marker-band.js +54 -10
  89. package/dist/dev/lib/marker-band.js.map +1 -1
  90. package/dist/dev/lib/marker-rider.js +10 -4
  91. package/dist/dev/lib/marker-rider.js.map +1 -1
  92. package/dist/dev/lib/math.js +6 -1
  93. package/dist/dev/lib/math.js.map +1 -1
  94. package/dist/dev/lib/pointer-drag.js +8 -5
  95. package/dist/dev/lib/pointer-drag.js.map +1 -1
  96. package/dist/dev/lib/resample-peaks.js +11 -1
  97. package/dist/dev/lib/resample-peaks.js.map +1 -1
  98. package/dist/dev/lib/write-attribute.js +1 -0
  99. package/dist/dev/lib/write-attribute.js.map +1 -1
  100. package/dist/skins/amber.css +8 -0
  101. package/dist/skins/flat.css +25 -0
  102. package/dist/skins/ivory.css +1 -0
  103. package/dist/skins/lime.css +2 -0
  104. package/dist/skins/slate.css +2 -0
  105. package/dist/styles/button.css +38 -10
  106. package/dist/styles/dial.css +24 -16
  107. package/dist/styles/envelope.css +56 -11
  108. package/dist/styles/led.css +3 -3
  109. package/dist/styles/material.css +83 -41
  110. package/dist/styles/meter.css +8 -4
  111. package/dist/styles/panel.css +5 -3
  112. package/dist/styles/segmented.css +27 -5
  113. package/dist/styles/slider.css +2 -1
  114. package/dist/styles/switch.css +59 -20
  115. package/dist/styles/waveform.css +28 -5
  116. package/dist/styles/wavestrip.css +23 -6
  117. package/dist/styles/xy.css +61 -24
  118. package/package.json +2 -5
@@ -1,6 +1,8 @@
1
1
  import { placeChildren } from "../lib/render.js";
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 { bindDrag } from "../lib/pointer-drag.js";
4
6
 
5
7
  //#region src/elements/radio-group.ts
6
8
  const keySteps = /* @__PURE__ */ new Map([
@@ -9,10 +11,12 @@ const keySteps = /* @__PURE__ */ new Map([
9
11
  ["ArrowRight", 1],
10
12
  ["ArrowUp", -1]
11
13
  ]);
12
- function isUnder(option, x, y) {
13
- const box = option.getBoundingClientRect();
14
+ function isInside(box, x, y) {
14
15
  return x >= box.left && x < box.right && y >= box.top && y < box.bottom;
15
16
  }
17
+ function isUnder(option, x, y) {
18
+ return isInside(option.getBoundingClientRect(), x, y);
19
+ }
16
20
  function optionValue(option) {
17
21
  return option.querySelector("[data-sonic-value]")?.dataset.sonicValue;
18
22
  }
@@ -26,7 +30,6 @@ var SonicRadioGroupElement = class extends SonicFormElement {
26
30
  this.#value = next;
27
31
  this.render();
28
32
  }
29
- #press;
30
33
  #value = "";
31
34
  attributeChangedCallback(name) {
32
35
  if (name === "value") this.#value = this.getAttribute("value") ?? "";
@@ -86,13 +89,15 @@ var SonicRadioGroupElement = class extends SonicFormElement {
86
89
  return option;
87
90
  }
88
91
  render() {
92
+ if (!this.isBound()) return;
89
93
  const options = this.options();
90
94
  const checked = options.find((option) => optionValue(option) === this.#value);
91
95
  const focusable = checked ?? options[0];
96
+ const isDisabled = this.isDisabled();
92
97
  for (const option of options) {
93
- option.disabled = this.isDisabled();
94
- option.setAttribute("aria-checked", String(option === checked));
95
- option.tabIndex = option === focusable ? 0 : -1;
98
+ writeAttribute(option, "disabled", isDisabled ? "" : void 0);
99
+ writeAttribute(option, "aria-checked", String(option === checked));
100
+ writeAttribute(option, "tabindex", option === focusable ? "0" : "-1");
96
101
  }
97
102
  this.forwardNaming(this.group, true);
98
103
  this.writeFormValue(checked ? this.#value : null, this.#value);
@@ -107,41 +112,32 @@ var SonicRadioGroupElement = class extends SonicFormElement {
107
112
  this.dispatchEvent(new Event("change", { bubbles: true }));
108
113
  }
109
114
  #bindPress(group, signal) {
110
- const isOwn = (event) => event.pointerId === this.#press?.pointerId;
111
- const release = (event) => {
112
- if (isOwn(event)) this.#hold(void 0);
113
- };
114
- group.addEventListener("pointerdown", (event) => {
115
- const option = this.optionOf(event.target);
116
- if (!option || event.button !== 0 || this.isDisabled()) return;
117
- if (this.claimPress(option, event)) return;
118
- group.setPointerCapture(event.pointerId);
119
- this.#hold({
120
- option,
121
- pointerId: event.pointerId
122
- });
123
- }, { signal });
124
- group.addEventListener("pointermove", (event) => {
125
- if (!isOwn(event)) return;
126
- this.#hold({
127
- option: this.options().find((option) => isUnder(option, event.clientX, event.clientY)),
128
- pointerId: event.pointerId
129
- });
130
- }, { signal });
131
- group.addEventListener("pointerup", (event) => {
132
- const pressed = isOwn(event) ? this.#press?.option : void 0;
133
- release(event);
134
- if (!pressed || this.isDisabled()) return;
135
- const option = this.releaseTarget(pressed);
136
- if (!option) return;
137
- this.select(option);
138
- if (group.matches(":focus-within")) option.focus();
139
- }, { signal });
140
- group.addEventListener("pointercancel", release, { signal });
141
- group.addEventListener("lostpointercapture", release, { signal });
142
- signal.addEventListener("abort", () => {
143
- this.#hold(void 0);
144
- }, { once: true });
115
+ bindDrag(group, {
116
+ grab: (event) => {
117
+ const option = this.optionOf(event.target);
118
+ if (!option || this.isDisabled() || this.claimPress(option, event)) return;
119
+ const boxes = this.#optionBoxes();
120
+ this.#markPressed(option);
121
+ return {
122
+ boxes,
123
+ option
124
+ };
125
+ },
126
+ lift: (press) => {
127
+ if (!press.option || this.isDisabled()) return;
128
+ const option = this.releaseTarget(press.option);
129
+ if (!option) return;
130
+ this.select(option);
131
+ if (group.matches(":focus-within")) option.focus();
132
+ },
133
+ move: (press, event) => {
134
+ press.option = press.boxes.find(([, box]) => isInside(box, event.clientX, event.clientY))?.[0];
135
+ this.#markPressed(press.option);
136
+ },
137
+ release: () => {
138
+ this.#markPressed(void 0);
139
+ }
140
+ }, signal);
145
141
  }
146
142
  #copyOptions(copies) {
147
143
  const group = this.group;
@@ -160,10 +156,6 @@ var SonicRadioGroupElement = class extends SonicFormElement {
160
156
  this.render();
161
157
  if (focused) this.#refocus(focused, focusedValue);
162
158
  }
163
- #hold(press) {
164
- this.#press = press;
165
- for (const option of this.options()) option.toggleAttribute("data-sonic-pressed", option === press?.option);
166
- }
167
159
  #keyTarget(option, key) {
168
160
  const options = this.options();
169
161
  if (key === "Home") return options[0];
@@ -174,6 +166,12 @@ var SonicRadioGroupElement = class extends SonicFormElement {
174
166
  if (this.isWrapping()) return options[(index + options.length) % options.length];
175
167
  return options[Math.min(Math.max(index, 0), options.length - 1)];
176
168
  }
169
+ #markPressed(pressed) {
170
+ for (const option of this.options()) option.toggleAttribute("data-sonic-pressed", option === pressed);
171
+ }
172
+ #optionBoxes() {
173
+ return this.options().map((option) => [option, option.getBoundingClientRect()]);
174
+ }
177
175
  #refocus(focused, value) {
178
176
  const options = this.options();
179
177
  const next = options.find((option) => optionValue(option) === value) ?? options.find((option) => option.tabIndex === 0);
@@ -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 { placeChildren } from '#lib/render.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\toption: HTMLButtonElement | undefined;\n\tpointerId: number;\n}\n\nexport function isUnder(option: Element, x: number, y: number): boolean {\n\tconst box = option.getBoundingClientRect();\n\n\treturn x >= box.left && x < box.right && y >= box.top && y < box.bottom;\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#press: Press | undefined;\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\tthis.mirrorChildren(\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\tconst options = this.options();\n\t\tconst checked = options.find((option) => optionValue(option) === this.#value);\n\t\tconst focusable = checked ?? options[0];\n\n\t\tfor (const option of options) {\n\t\t\toption.disabled = this.isDisabled();\n\t\t\toption.setAttribute('aria-checked', String(option === checked));\n\t\t\toption.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\tconst isOwn = (event: PointerEvent): boolean => event.pointerId === this.#press?.pointerId;\n\t\tconst release = (event: PointerEvent): void => {\n\t\t\tif (isOwn(event)) this.#hold(undefined);\n\t\t};\n\n\t\tgroup.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t(event) => {\n\t\t\t\tconst option = this.optionOf(event.target);\n\t\t\t\tif (!option || event.button !== 0 || this.isDisabled()) return;\n\t\t\t\tif (this.claimPress(option, event)) return;\n\n\t\t\t\tgroup.setPointerCapture(event.pointerId);\n\t\t\t\tthis.#hold({ option, pointerId: event.pointerId });\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tgroup.addEventListener(\n\t\t\t'pointermove',\n\t\t\t(event) => {\n\t\t\t\tif (!isOwn(event)) return;\n\n\t\t\t\tthis.#hold({\n\t\t\t\t\toption: this.options().find((option) => isUnder(option, event.clientX, event.clientY)),\n\t\t\t\t\tpointerId: event.pointerId,\n\t\t\t\t});\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tgroup.addEventListener(\n\t\t\t'pointerup',\n\t\t\t(event) => {\n\t\t\t\tconst pressed = isOwn(event) ? this.#press?.option : undefined;\n\n\t\t\t\trelease(event);\n\t\t\t\tif (!pressed || this.isDisabled()) return;\n\n\t\t\t\tconst option = this.releaseTarget(pressed);\n\t\t\t\tif (!option) return;\n\n\t\t\t\tthis.select(option);\n\t\t\t\tif (group.matches(':focus-within')) option.focus();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tgroup.addEventListener('pointercancel', release, { signal });\n\t\tgroup.addEventListener('lostpointercapture', release, { signal });\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tthis.#hold(undefined);\n\t\t\t},\n\t\t\t{ once: true },\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#hold(press: Press | undefined): void {\n\t\tthis.#press = press;\n\t\tfor (const option of this.options()) {\n\t\t\toption.toggleAttribute('data-sonic-pressed', option === press?.option);\n\t\t}\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#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 { 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\tthis.mirrorChildren(\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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"}
@@ -23,7 +23,7 @@ var Readout = class {
23
23
  this.#anchor = anchor;
24
24
  }
25
25
  this.#text.hidden = text === void 0;
26
- if (text !== void 0) this.#text.textContent = text;
26
+ if (isOpen && text !== void 0 && this.#text.textContent !== text) this.#text.textContent = text;
27
27
  this.#toggle(isOpen);
28
28
  }
29
29
  #toggle(isOpen) {
@@ -1 +1 @@
1
- {"version":3,"file":"readout.js","names":[],"sources":["../../../src/elements/readout.ts"],"sourcesContent":["import { requireChild } from '#lib/render.ts';\n\ninterface ReadoutView {\n\tanchor: HTMLElement;\n\tisOpen: boolean;\n\ttext: string | undefined;\n}\n\n// happy-dom has no popover API\nconst canPopover = 'togglePopover' in HTMLElement.prototype;\n\nlet readoutCount = 0;\n\nexport class Readout {\n\t#anchor: HTMLElement | undefined;\n\n\treadonly #bubble: HTMLElement;\n\n\treadonly #name = `--sonic-readout-${String((readoutCount += 1))}`;\n\n\treadonly #text: HTMLElement;\n\n\tconstructor(bubble: HTMLElement) {\n\t\tthis.#bubble = bubble;\n\t\tthis.#text = requireChild(bubble, 'span', HTMLElement);\n\t\tbubble.style.setProperty('position-anchor', this.#name);\n\t}\n\n\tclose(): void {\n\t\tthis.#toggle(false);\n\t}\n\n\tshow({ anchor, isOpen, text }: ReadoutView): void {\n\t\tif (anchor !== this.#anchor) {\n\t\t\tthis.#anchor?.style.removeProperty('anchor-name');\n\t\t\tanchor.style.setProperty('anchor-name', this.#name);\n\t\t\tthis.#anchor = anchor;\n\t\t}\n\n\t\tthis.#text.hidden = text === undefined;\n\t\tif (text !== undefined) this.#text.textContent = text;\n\t\tthis.#toggle(isOpen);\n\t}\n\n\t#toggle(isOpen: boolean): void {\n\t\tif (canPopover && this.#bubble.isConnected) this.#bubble.togglePopover(isOpen);\n\t}\n}\n"],"mappings":";;;AASA,MAAM,aAAa,mBAAmB,YAAY;AAElD,IAAI,eAAe;AAEnB,IAAa,UAAb,MAAqB;CACpB;CAEA,AAAS;CAET,AAAS,QAAQ,mBAAmB,OAAQ,gBAAgB,CAAE;CAE9D,AAAS;CAET,YAAY,QAAqB;EAChC,KAAK,UAAU;EACf,KAAK,QAAQ,aAAa,QAAQ,QAAQ,WAAW;EACrD,OAAO,MAAM,YAAY,mBAAmB,KAAK,KAAK;CACvD;CAEA,QAAc;EACb,KAAK,QAAQ,KAAK;CACnB;CAEA,KAAK,EAAE,QAAQ,QAAQ,QAA2B;EACjD,IAAI,WAAW,KAAK,SAAS;GAC5B,KAAK,SAAS,MAAM,eAAe,aAAa;GAChD,OAAO,MAAM,YAAY,eAAe,KAAK,KAAK;GAClD,KAAK,UAAU;EAChB;EAEA,KAAK,MAAM,SAAS,SAAS;EAC7B,IAAI,SAAS,QAAW,KAAK,MAAM,cAAc;EACjD,KAAK,QAAQ,MAAM;CACpB;CAEA,QAAQ,QAAuB;EAC9B,IAAI,cAAc,KAAK,QAAQ,aAAa,KAAK,QAAQ,cAAc,MAAM;CAC9E;AACD"}
1
+ {"version":3,"file":"readout.js","names":[],"sources":["../../../src/elements/readout.ts"],"sourcesContent":["import { requireChild } from '#lib/render.ts';\n\ninterface ReadoutView {\n\tanchor: HTMLElement;\n\tisOpen: boolean;\n\ttext: string | undefined;\n}\n\n// happy-dom has no popover API\nconst canPopover = 'togglePopover' in HTMLElement.prototype;\n\nlet readoutCount = 0;\n\nexport class Readout {\n\t#anchor: HTMLElement | undefined;\n\n\treadonly #bubble: HTMLElement;\n\n\treadonly #name = `--sonic-readout-${String((readoutCount += 1))}`;\n\n\treadonly #text: HTMLElement;\n\n\tconstructor(bubble: HTMLElement) {\n\t\tthis.#bubble = bubble;\n\t\tthis.#text = requireChild(bubble, 'span', HTMLElement);\n\t\tbubble.style.setProperty('position-anchor', this.#name);\n\t}\n\n\tclose(): void {\n\t\tthis.#toggle(false);\n\t}\n\n\tshow({ anchor, isOpen, text }: ReadoutView): void {\n\t\tif (anchor !== this.#anchor) {\n\t\t\tthis.#anchor?.style.removeProperty('anchor-name');\n\t\t\tanchor.style.setProperty('anchor-name', this.#name);\n\t\t\tthis.#anchor = anchor;\n\t\t}\n\n\t\tthis.#text.hidden = text === undefined;\n\t\tif (isOpen && text !== undefined && this.#text.textContent !== text) {\n\t\t\tthis.#text.textContent = text;\n\t\t}\n\t\tthis.#toggle(isOpen);\n\t}\n\n\t#toggle(isOpen: boolean): void {\n\t\tif (canPopover && this.#bubble.isConnected) this.#bubble.togglePopover(isOpen);\n\t}\n}\n"],"mappings":";;;AASA,MAAM,aAAa,mBAAmB,YAAY;AAElD,IAAI,eAAe;AAEnB,IAAa,UAAb,MAAqB;CACpB;CAEA,AAAS;CAET,AAAS,QAAQ,mBAAmB,OAAQ,gBAAgB,CAAE;CAE9D,AAAS;CAET,YAAY,QAAqB;EAChC,KAAK,UAAU;EACf,KAAK,QAAQ,aAAa,QAAQ,QAAQ,WAAW;EACrD,OAAO,MAAM,YAAY,mBAAmB,KAAK,KAAK;CACvD;CAEA,QAAc;EACb,KAAK,QAAQ,KAAK;CACnB;CAEA,KAAK,EAAE,QAAQ,QAAQ,QAA2B;EACjD,IAAI,WAAW,KAAK,SAAS;GAC5B,KAAK,SAAS,MAAM,eAAe,aAAa;GAChD,OAAO,MAAM,YAAY,eAAe,KAAK,KAAK;GAClD,KAAK,UAAU;EAChB;EAEA,KAAK,MAAM,SAAS,SAAS;EAC7B,IAAI,UAAU,SAAS,UAAa,KAAK,MAAM,gBAAgB,MAC9D,KAAK,MAAM,cAAc;EAE1B,KAAK,QAAQ,MAAM;CACpB;CAEA,QAAQ,QAAuB;EAC9B,IAAI,cAAc,KAAK,QAAQ,aAAa,KAAK,QAAQ,cAAc,MAAM;CAC9E;AACD"}
@@ -13,7 +13,7 @@ export declare class SonicSlider extends SonicValueElement {
13
13
  get modulation(): number;
14
14
  set modulation(amount: number | undefined);
15
15
  get modulationValue(): number | undefined;
16
- set modulationValue(value: number | undefined);
16
+ set modulationValue(value: null | number | undefined);
17
17
  get orientation(): 'horizontal' | 'vertical';
18
18
  set orientation(direction: 'horizontal' | 'vertical' | undefined);
19
19
  get spring(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-element.ts';\n\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends ValueAxis {\n\tstartPx: number;\n}\n\nfunction grooveStop(proportion: number): string {\n\tif (proportion <= 0) return '0%';\n\tif (proportion >= 1) return '100%';\n\n\treturn `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove) / 2)`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-notches\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-slider-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicSlider extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'spring',\n\t];\n\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\tget modulationValue(): number | undefined {\n\t\treturn this.readModulationValue();\n\t}\n\n\tset modulationValue(value: number | undefined) {\n\t\tthis.writeModulationValue(this.#slider, 'slider', value);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\treadonly #slider = renderSlider();\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('groovePress', 'modulationValue', 'modulation', 'orientation', 'spring');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tthis.bindScale(slider, requireChild(slider, '.sonic-slider-scale', HTMLDivElement), signal);\n\t\tslider.addEventListener(\n\t\t\t'transitionend',\n\t\t\t(event) => {\n\t\t\t\tif (event.propertyName === '--_sonic-slider-value') this.toggleState('springing', false);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(slider, 'slider.css');\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override controlOrientation(): 'horizontal' | 'vertical' {\n\t\treturn this.orientation;\n\t}\n\n\tprotected draw(): void {\n\t\tthis.writeProportions(this.#slider, 'slider', this.modulation);\n\t\tthis.#renderBuffered(this.#slider);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.originValue() : undefined;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst box = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\treturn this.orientation === 'vertical'\n\t\t\t? {\n\t\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\t\tstartPx: capBox.height / 2 - box.bottom,\n\t\t\t\t\ttravelPx: Math.max(1, box.height - capBox.height),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tposition: (event) => event.clientX,\n\t\t\t\t\tstartPx: box.left + capBox.width / 2,\n\t\t\t\t\ttravelPx: Math.max(1, box.width - capBox.width),\n\t\t\t\t};\n\t}\n\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tconst buffered = this.buffered;\n\n\t\tif (buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-regions');\n\t\t\treturn;\n\t\t}\n\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst stops = buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(proportionOf(Math.min(start, end)));\n\t\t\tconst to = grooveStop(proportionOf(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-regions',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-element.ts';\n\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends ValueAxis {\n\tstartPx: number;\n}\n\nfunction grooveStop(proportion: number): string {\n\tif (proportion <= 0) return '0%';\n\tif (proportion >= 1) return '100%';\n\n\treturn `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove) / 2)`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-notches\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-slider-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicSlider extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'spring',\n\t];\n\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\tget modulationValue(): number | undefined {\n\t\treturn this.readModulationValue();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.writeModulationValue(this.#slider, 'slider', value);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\treadonly #slider = renderSlider();\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('groovePress', 'modulationValue', 'modulation', 'orientation', 'spring');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tthis.bindScale(slider, requireChild(slider, '.sonic-slider-scale', HTMLDivElement), signal);\n\t\tslider.addEventListener(\n\t\t\t'transitionend',\n\t\t\t(event) => {\n\t\t\t\tif (event.propertyName === '--_sonic-slider-value') this.toggleState('springing', false);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.render();\n\t\tthis.checkStyles(slider, 'slider.css');\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override controlOrientation(): 'horizontal' | 'vertical' {\n\t\treturn this.orientation;\n\t}\n\n\tprotected draw(): void {\n\t\tthis.writeProportions(this.#slider, 'slider', this.modulation);\n\t\tthis.#renderBuffered(this.#slider);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.originValue() : undefined;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst box = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\treturn this.orientation === 'vertical'\n\t\t\t? {\n\t\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\t\tstartPx: capBox.height / 2 - box.bottom,\n\t\t\t\t\ttravelPx: Math.max(1, box.height - capBox.height),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tposition: (event) => event.clientX,\n\t\t\t\t\tstartPx: box.left + capBox.width / 2,\n\t\t\t\t\ttravelPx: Math.max(1, box.width - capBox.width),\n\t\t\t\t};\n\t}\n\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tconst buffered = this.buffered;\n\n\t\tif (buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-regions');\n\t\t\treturn;\n\t\t}\n\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst stops = buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(proportionOf(Math.min(start, end)));\n\t\t\tconst to = grooveStop(proportionOf(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-regions',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
@@ -3,6 +3,7 @@ interface Mirror {
3
3
  control: Element;
4
4
  copy: (original: ChildNode) => Node | undefined;
5
5
  isCopied?: (child: Node) => boolean;
6
+ isPassed?: (child: Node) => boolean;
6
7
  place: (copies: Array<Node>) => void;
7
8
  }
8
9
  export declare abstract class SonicElement extends HTMLElement {
@@ -16,13 +17,14 @@ export declare abstract class SonicElement extends HTMLElement {
16
17
  connectedMoveCallback(): void;
17
18
  disconnectedCallback(): void;
18
19
  focus(options?: FocusOptions): void;
19
- protected appendOnce(control: Element): void;
20
20
  protected checkStyles(control: HTMLElement, sheet: string): void;
21
21
  protected abstract connect(signal: AbortSignal): void;
22
22
  protected focusTarget(): HTMLElement | undefined;
23
23
  protected forwardNaming(target: Element, isNamed: boolean): void;
24
+ protected isBound(): boolean;
24
25
  protected isDisabled(): boolean;
25
- protected mirrorChildren({ control, copy, isCopied, place }: Mirror, signal: AbortSignal): void;
26
+ protected keepControl(control: Element, signal: AbortSignal): void;
27
+ protected mirrorChildren({ control, copy, isCopied, isPassed, place }: Mirror, signal: AbortSignal): void;
26
28
  protected namingLabels(): Array<Element>;
27
29
  protected numberAttribute(name: string, fallback: number): number;
28
30
  protected optionalNumberAttribute(name: string): number | undefined;
@@ -1,3 +1,4 @@
1
+ import { toNumber } from "../lib/math.js";
1
2
  import { attachSlots } from "../lib/render.js";
2
3
  import { writeAttribute } from "../lib/write-attribute.js";
3
4
 
@@ -8,11 +9,22 @@ const namingAttributes = [
8
9
  "aria-labelledby"
9
10
  ];
10
11
  const checkedSheets = /* @__PURE__ */ new WeakMap();
12
+ const checkedMirrors = /* @__PURE__ */ new WeakSet();
13
+ const checkedChildren = /* @__PURE__ */ new WeakSet();
14
+ const interactive = "a[href], button, input, select, textarea, [tabindex]";
11
15
  function writeLabelledBy(target, labels) {
12
16
  const current = target.ariaLabelledByElements;
13
17
  if (current?.length === labels.length && labels.every((label, index) => label === current[index])) return;
14
18
  target.ariaLabelledByElements = labels;
15
19
  }
20
+ function unmirrorable(original) {
21
+ if (!(original instanceof Element)) return void 0;
22
+ return [original, ...original.querySelectorAll("*")].find((element) => element.matches(interactive) || element.localName.includes("-"));
23
+ }
24
+ function isContent(child) {
25
+ if (child instanceof Element) return true;
26
+ return child instanceof Text && child.data.trim() !== "";
27
+ }
16
28
  var SonicElement = class extends HTMLElement {
17
29
  static observedAttributes = [...namingAttributes, "disabled"];
18
30
  get disabled() {
@@ -31,13 +43,14 @@ var SonicElement = class extends HTMLElement {
31
43
  this.upgradeProperties("disabled");
32
44
  if (this.#connection) return;
33
45
  const connection = new AbortController();
46
+ this.#connection = connection;
34
47
  try {
35
48
  this.connect(connection.signal);
36
49
  } catch (error) {
37
50
  connection.abort();
51
+ this.#connection = void 0;
38
52
  throw error;
39
53
  }
40
- this.#connection = connection;
41
54
  }
42
55
  connectedMoveCallback() {}
43
56
  disconnectedCallback() {
@@ -57,12 +70,6 @@ var SonicElement = class extends HTMLElement {
57
70
  }
58
71
  if (!this.isDisabled() && !this.#focused()) target.focus(options);
59
72
  }
60
- appendOnce(control) {
61
- const [hook] = control.classList;
62
- const stale = [...this.children].filter((child) => child !== control && hook !== void 0 && child.classList.contains(hook));
63
- for (const child of stale) child.remove();
64
- if (control.parentNode !== this) this.append(control);
65
- }
66
73
  checkStyles(control, sheet) {
67
74
  const elementClass = this.constructor;
68
75
  const checked = checkedSheets.get(elementClass) ?? /* @__PURE__ */ new Set();
@@ -106,36 +113,64 @@ var SonicElement = class extends HTMLElement {
106
113
  }
107
114
  if (labels.length > 0) writeLabelledBy(target, labels);
108
115
  }
116
+ isBound() {
117
+ return this.#connection !== void 0;
118
+ }
109
119
  isDisabled() {
110
120
  return this.disabled || this.matches(":disabled");
111
121
  }
112
- mirrorChildren({ control, copy, isCopied = () => true, place }, signal) {
122
+ keepControl(control, signal) {
123
+ const keep = () => {
124
+ this.#appendOnce(control);
125
+ this.#checkChildren([...this.childNodes].filter((child) => child !== control));
126
+ };
127
+ const observer = new MutationObserver(keep);
128
+ keep();
129
+ observer.observe(this, { childList: true });
130
+ signal.addEventListener("abort", () => {
131
+ observer.disconnect();
132
+ }, { once: true });
133
+ }
134
+ mirrorChildren({ control, copy, isCopied = () => true, isPassed = () => false, place }, signal) {
113
135
  const slots = this.#slots ?? attachSlots(this);
114
136
  let copies = /* @__PURE__ */ new Map();
115
137
  this.#slots = slots;
116
138
  const mirror = (touched) => {
117
- this.appendOnce(control);
139
+ this.#appendOnce(control);
118
140
  const children = [...this.childNodes].filter((child) => child !== control);
119
141
  const slotted = children.filter((child) => child instanceof Element || child instanceof Text);
120
142
  const kept = copies;
121
- copies = new Map(children.filter((child) => isCopied(child)).map((original) => [original, touched && !touched.has(original) && kept.has(original) ? kept.get(original) : copy(original)]));
143
+ const originals = children.filter((child) => isCopied(child));
144
+ this.#checkMirrored(originals);
145
+ this.#checkChildren(children.filter((child) => !isCopied(child) && !isPassed(child)));
146
+ copies = new Map(originals.map((original) => [original, touched && !touched.has(original) && kept.has(original) ? kept.get(original) : copy(original)]));
122
147
  place([...copies.values()].filter((made) => made !== void 0));
123
148
  slots.shown.assign(...slotted.filter((child) => !isCopied(child)), control);
124
149
  slots.kept.assign(...slotted.filter((child) => isCopied(child)));
125
150
  };
151
+ const watch = () => {
152
+ observer.disconnect();
153
+ observer.observe(this, { childList: true });
154
+ for (const child of this.childNodes) {
155
+ if (child === control) continue;
156
+ observer.observe(child, {
157
+ attributes: true,
158
+ characterData: true,
159
+ childList: true,
160
+ subtree: true
161
+ });
162
+ }
163
+ };
126
164
  const observer = new MutationObserver((records) => {
127
165
  const changes = records.filter((record) => this.#isCopiedChange(record, control, isCopied));
128
166
  if (changes.length === 0) return;
129
- if (changes.some((record) => record.target === this)) mirror();
130
- else mirror(new Set(changes.map((record) => this.#childHolding(record.target))));
167
+ if (changes.some((record) => record.target === this)) {
168
+ mirror();
169
+ watch();
170
+ } else mirror(new Set(changes.map((record) => this.#childHolding(record.target))));
131
171
  });
132
172
  mirror();
133
- observer.observe(this, {
134
- attributes: true,
135
- characterData: true,
136
- childList: true,
137
- subtree: true
138
- });
173
+ watch();
139
174
  signal.addEventListener("abort", () => {
140
175
  observer.disconnect();
141
176
  }, { once: true });
@@ -144,9 +179,7 @@ var SonicElement = class extends HTMLElement {
144
179
  return [];
145
180
  }
146
181
  numberAttribute(name, fallback) {
147
- const attribute = this.getAttribute(name);
148
- if (attribute === null || attribute.trim() === "") return fallback;
149
- const parsed = Number(attribute);
182
+ const parsed = toNumber(this.getAttribute(name));
150
183
  return Number.isFinite(parsed) ? parsed : fallback;
151
184
  }
152
185
  optionalNumberAttribute(name) {
@@ -154,11 +187,11 @@ var SonicElement = class extends HTMLElement {
154
187
  return Number.isNaN(value) ? void 0 : value;
155
188
  }
156
189
  reflect(name, value) {
157
- if (value === true) {
158
- this.setAttribute(name, "");
190
+ if (typeof value !== "number" && typeof value !== "string" && value !== true) {
191
+ this.removeAttribute(name);
159
192
  return;
160
193
  }
161
- writeAttribute(this, name, value === false || value === null ? void 0 : value?.toString());
194
+ this.setAttribute(name, value === true ? "" : value.toString());
162
195
  }
163
196
  toggleState(state, isOn) {
164
197
  if (isOn) this.internals?.states.add(state);
@@ -172,6 +205,26 @@ var SonicElement = class extends HTMLElement {
172
205
  this[name] = value;
173
206
  }
174
207
  }
208
+ #appendOnce(control) {
209
+ const [hook] = control.classList;
210
+ const stale = [...this.children].filter((child) => child !== control && hook !== void 0 && child.classList.contains(hook));
211
+ for (const child of stale) child.remove();
212
+ if (control.parentNode !== this) this.append(control);
213
+ }
214
+ #checkChildren(undocumented) {
215
+ if (checkedChildren.has(this.constructor)) return;
216
+ const found = undocumented.find((child) => isContent(child));
217
+ if (!found) return;
218
+ checkedChildren.add(this.constructor);
219
+ console.warn(`<${this.localName}> uses only the children it documents, so the ${found instanceof Element ? `<${found.localName}>` : "text"} inside it is unsupported`);
220
+ }
221
+ #checkMirrored(originals) {
222
+ if (checkedMirrors.has(this.constructor)) return;
223
+ const [found] = originals.flatMap((original) => unmirrorable(original) ?? []);
224
+ if (!found) return;
225
+ checkedMirrors.add(this.constructor);
226
+ console.warn(`<${this.localName}> copies its children into the control, so the <${found.localName}> inside it loses its listeners and state; keep mirrored children static`);
227
+ }
175
228
  #childHolding(node) {
176
229
  let current = node;
177
230
  while (current && current.parentNode !== this) current = current.parentNode;
@@ -183,12 +236,9 @@ var SonicElement = class extends HTMLElement {
183
236
  return active instanceof HTMLElement && this.contains(active) ? active : void 0;
184
237
  }
185
238
  #isCopiedChange(record, control, isCopied) {
186
- if (record.target === this) {
187
- if (record.type === "attributes") return false;
188
- return record.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control);
189
- }
239
+ if (record.target === this) return record.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control);
190
240
  const child = this.#childHolding(record.target);
191
- if (child === void 0 || child === control) return false;
241
+ if (child === void 0) return false;
192
242
  return isCopied(child) || record.type === "attributes" && record.target === child;
193
243
  }
194
244
  };
@@ -1 +1 @@
1
- {"version":3,"file":"sonic-element.js","names":[],"sources":["../../../src/elements/sonic-element.ts"],"sourcesContent":["import type { MirrorSlots } from '#lib/render.ts';\n\nimport { attachSlots } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\n// eslint-disable-next-line unicorn/consistent-boolean-name -- the name bundlers and other kits use\ndeclare const __DEV__: boolean;\n\nconst namingAttributes = ['aria-describedby', 'aria-label', 'aria-labelledby'];\n\nconst checkedSheets = new WeakMap<object, Set<string>>();\n\ninterface StyleProbe {\n\tselector?: string;\n\tsheet: string;\n\ttoken: string;\n}\n\nfunction writeLabelledBy(target: Element, labels: Array<Element>): void {\n\tconst current = target.ariaLabelledByElements;\n\tif (\n\t\tcurrent?.length === labels.length &&\n\t\tlabels.every((label, index) => label === current[index])\n\t) {\n\t\treturn;\n\t}\n\n\ttarget.ariaLabelledByElements = labels;\n}\n\ninterface Mirror {\n\tcontrol: Element;\n\tcopy: (original: ChildNode) => Node | undefined;\n\tisCopied?: (child: Node) => boolean;\n\tplace: (copies: Array<Node>) => void;\n}\n\n// eslint-disable-next-line wc/define-tag-after-class-definition -- abstract; only subclasses are defined\nexport abstract class SonicElement extends HTMLElement {\n\tstatic readonly observedAttributes = [...namingAttributes, 'disabled'];\n\n\tget disabled(): boolean {\n\t\treturn this.hasAttribute('disabled');\n\t}\n\n\tset disabled(isDisabled: boolean) {\n\t\tthis.reflect('disabled', isDisabled);\n\t}\n\n\t// happy-dom has no `ElementInternals`\n\tprotected readonly internals =\n\t\t'attachInternals' in HTMLElement.prototype ? this.attachInternals() : undefined;\n\n\t#connection: AbortController | undefined;\n\n\t#slots: MirrorSlots | undefined;\n\n\toverride blur(): void {\n\t\tthis.#focused()?.blur();\n\t}\n\n\tconnectedCallback(): void {\n\t\tthis.upgradeProperties('disabled');\n\t\tif (this.#connection) return;\n\n\t\tconst connection = new AbortController();\n\n\t\ttry {\n\t\t\tthis.connect(connection.signal);\n\t\t} catch (error) {\n\t\t\tconnection.abort();\n\t\t\tthrow error;\n\t\t}\n\n\t\tthis.#connection = connection;\n\t}\n\n\tconnectedMoveCallback(): void {\n\t\t// Defined so `moveBefore` from a persisting router keeps every binding\n\t}\n\n\t// Without `moveBefore` (Safari), a persisting router disconnects and reconnects in one task\n\tdisconnectedCallback(): void {\n\t\tconst connection = this.#connection;\n\t\tif (!connection) return;\n\n\t\tqueueMicrotask(() => {\n\t\t\tif (this.isConnected || this.#connection !== connection) return;\n\n\t\t\tconnection.abort();\n\t\t\tthis.#connection = undefined;\n\t\t});\n\t}\n\n\toverride focus(options?: FocusOptions): void {\n\t\tconst target = this.focusTarget();\n\t\tif (!target) {\n\t\t\tsuper.focus(options);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isDisabled() && !this.#focused()) target.focus(options);\n\t}\n\n\tprotected appendOnce(control: Element): void {\n\t\tconst [hook] = control.classList;\n\t\tconst stale = [...this.children].filter(\n\t\t\t(child) => child !== control && hook !== undefined && child.classList.contains(hook),\n\t\t);\n\n\t\tfor (const child of stale) child.remove();\n\t\tif (control.parentNode !== this) this.append(control);\n\t}\n\n\tprotected checkStyles(control: HTMLElement, sheet: string): void {\n\t\tif (!__DEV__) return;\n\n\t\tconst elementClass = this.constructor;\n\t\tconst checked = checkedSheets.get(elementClass) ?? new Set<string>();\n\t\tconst part = ({ selector }: StyleProbe): Element | undefined =>\n\t\t\tselector === undefined ? control : (this.querySelector(selector) ?? undefined);\n\t\t// An LED inherits `--_sonic-unit` from its control, so a token of its own probes it\n\t\tconst probes: Array<StyleProbe> = [\n\t\t\t{ sheet: 'material.css', token: '--_sonic-unlit' },\n\t\t\t{ sheet, token: '--_sonic-unit' },\n\t\t\t{ selector: '.sonic-led', sheet: 'led.css', token: '--_sonic-led-lens-ratio' },\n\t\t].filter((probe) => !checked.has(probe.sheet) && part(probe) !== undefined);\n\t\tif (probes.length === 0) return;\n\n\t\tcheckedSheets.set(elementClass, checked);\n\t\tfor (const probe of probes) checked.add(probe.sheet);\n\t\trequestAnimationFrame(() => {\n\t\t\tconst missing: Array<string> = [];\n\n\t\t\tfor (const probe of probes) {\n\t\t\t\tconst drawn = control.isConnected ? part(probe) : undefined;\n\n\t\t\t\tif (!drawn) checked.delete(probe.sheet);\n\t\t\t\telse if (getComputedStyle(drawn).getPropertyValue(probe.token) === '') {\n\t\t\t\t\tmissing.push(`@xsynaptic/sonic-ui/${probe.sheet}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (missing.length === 0) return;\n\n\t\t\tconsole.warn(\n\t\t\t\t`<${this.localName}> draws blank without ${missing.join(' and ')} (or controls.css)`,\n\t\t\t);\n\t\t});\n\t}\n\n\tprotected abstract connect(signal: AbortSignal): void;\n\n\tprotected focusTarget(): HTMLElement | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected forwardNaming(target: Element, isNamed: boolean): void {\n\t\tconst labels = isNamed ? this.namingLabels() : [];\n\n\t\tfor (const name of namingAttributes) {\n\t\t\t// `ariaLabelledByElements` holds the attribute, empty\n\t\t\tif (name === 'aria-labelledby' && labels.length > 0) continue;\n\n\t\t\tconst value = this.getAttribute(name);\n\n\t\t\twriteAttribute(target, name, isNamed && value !== null ? value : undefined);\n\t\t}\n\t\tif (labels.length > 0) writeLabelledBy(target, labels);\n\t}\n\n\t// happy-dom matches `:disabled` on no custom element, and an element not form-associated never matches\n\tprotected isDisabled(): boolean {\n\t\treturn this.disabled || this.matches(':disabled');\n\t}\n\n\tprotected mirrorChildren(\n\t\t{ control, copy, isCopied = () => true, place }: Mirror,\n\t\tsignal: AbortSignal,\n\t): void {\n\t\tconst slots = this.#slots ?? attachSlots(this);\n\t\tlet copies = new Map<ChildNode, Node | undefined>();\n\n\t\tthis.#slots = slots;\n\t\tconst mirror = (touched?: Set<Node | undefined>): void => {\n\t\t\tthis.appendOnce(control);\n\n\t\t\tconst children = [...this.childNodes].filter((child) => child !== control);\n\t\t\tconst slotted = children.filter(\n\t\t\t\t(child): child is Element | Text => child instanceof Element || child instanceof Text,\n\t\t\t);\n\t\t\tconst kept = copies;\n\n\t\t\tcopies = new Map(\n\t\t\t\tchildren\n\t\t\t\t\t.filter((child) => isCopied(child))\n\t\t\t\t\t.map((original) => [\n\t\t\t\t\t\toriginal,\n\t\t\t\t\t\ttouched && !touched.has(original) && kept.has(original)\n\t\t\t\t\t\t\t? kept.get(original)\n\t\t\t\t\t\t\t: copy(original),\n\t\t\t\t\t]),\n\t\t\t);\n\t\t\tplace([...copies.values()].filter((made) => made !== undefined));\n\t\t\tslots.shown.assign(...slotted.filter((child) => !isCopied(child)), control);\n\t\t\tslots.kept.assign(...slotted.filter((child) => isCopied(child)));\n\t\t};\n\t\tconst observer = new MutationObserver((records) => {\n\t\t\tconst changes = records.filter((record) => this.#isCopiedChange(record, control, isCopied));\n\t\t\tif (changes.length === 0) return;\n\n\t\t\tif (changes.some((record) => record.target === this)) mirror();\n\t\t\telse mirror(new Set(changes.map((record) => this.#childHolding(record.target))));\n\t\t});\n\n\t\tmirror();\n\t\tobserver.observe(this, {\n\t\t\tattributes: true,\n\t\t\tcharacterData: true,\n\t\t\tchildList: true,\n\t\t\tsubtree: true,\n\t\t});\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tprotected namingLabels(): Array<Element> {\n\t\treturn [];\n\t}\n\n\tprotected numberAttribute(name: string, fallback: number): number {\n\t\tconst attribute = this.getAttribute(name);\n\t\tif (attribute === null || attribute.trim() === '') return fallback;\n\n\t\tconst parsed = Number(attribute);\n\n\t\treturn Number.isFinite(parsed) ? parsed : fallback;\n\t}\n\n\tprotected optionalNumberAttribute(name: string): number | undefined {\n\t\tconst value = this.numberAttribute(name, NaN);\n\n\t\treturn Number.isNaN(value) ? undefined : value;\n\t}\n\n\tprotected reflect(name: string, value: boolean | null | number | string | undefined): void {\n\t\tif (value === true) {\n\t\t\tthis.setAttribute(name, '');\n\t\t\treturn;\n\t\t}\n\n\t\twriteAttribute(this, name, value === false || value === null ? undefined : value?.toString());\n\t}\n\n\tprotected toggleState(state: string, isOn: boolean): void {\n\t\tif (isOn) this.internals?.states.add(state);\n\t\telse this.internals?.states.delete(state);\n\t}\n\n\t// A property set before upgrade shadows its accessor\n\tprotected upgradeProperties(...names: Array<keyof this>): void {\n\t\tfor (const name of names) {\n\t\t\tif (!Object.hasOwn(this, name)) continue;\n\n\t\t\tconst value = this[name];\n\n\t\t\tReflect.deleteProperty(this, name);\n\t\t\tthis[name] = value;\n\t\t}\n\t}\n\n\t#childHolding(node: Node): Node | undefined {\n\t\tlet current: Node | null = node;\n\n\t\twhile (current && current.parentNode !== this) current = current.parentNode;\n\n\t\treturn current ?? undefined;\n\t}\n\n\t#focused(): HTMLElement | undefined {\n\t\tconst root = this.getRootNode();\n\t\tconst active =\n\t\t\troot instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;\n\n\t\treturn active instanceof HTMLElement && this.contains(active) ? active : undefined;\n\t}\n\n\t#isCopiedChange(\n\t\trecord: MutationRecord,\n\t\tcontrol: Element,\n\t\tisCopied: (child: Node) => boolean,\n\t): boolean {\n\t\tif (record.target === this) {\n\t\t\tif (record.type === 'attributes') return false;\n\n\t\t\treturn (\n\t\t\t\trecord.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control)\n\t\t\t);\n\t\t}\n\n\t\tconst child = this.#childHolding(record.target);\n\n\t\tif (child === undefined || child === control) return false;\n\n\t\treturn isCopied(child) || (record.type === 'attributes' && record.target === child);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sonic-element.js","names":[],"sources":["../../../src/elements/sonic-element.ts"],"sourcesContent":["import type { MirrorSlots } from '#lib/render.ts';\n\nimport { toNumber } from '#lib/math.ts';\nimport { attachSlots } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\n// eslint-disable-next-line unicorn/consistent-boolean-name -- the name bundlers and other kits use\ndeclare const __DEV__: boolean;\n\nconst namingAttributes = ['aria-describedby', 'aria-label', 'aria-labelledby'];\n\nconst checkedSheets = new WeakMap<object, Set<string>>();\n\nconst checkedMirrors = new WeakSet<object>();\n\nconst checkedChildren = new WeakSet<object>();\n\nconst interactive = 'a[href], button, input, select, textarea, [tabindex]';\n\ninterface StyleProbe {\n\tselector?: string;\n\tsheet: string;\n\ttoken: string;\n}\n\nfunction writeLabelledBy(target: Element, labels: Array<Element>): void {\n\tconst current = target.ariaLabelledByElements;\n\tif (\n\t\tcurrent?.length === labels.length &&\n\t\tlabels.every((label, index) => label === current[index])\n\t) {\n\t\treturn;\n\t}\n\n\ttarget.ariaLabelledByElements = labels;\n}\n\nfunction unmirrorable(original: Node): Element | undefined {\n\tif (!(original instanceof Element)) return undefined;\n\n\treturn [original, ...original.querySelectorAll('*')].find(\n\t\t(element) => element.matches(interactive) || element.localName.includes('-'),\n\t);\n}\n\nfunction isContent(child: Node): boolean {\n\tif (child instanceof Element) return true;\n\n\treturn child instanceof Text && child.data.trim() !== '';\n}\n\ninterface Mirror {\n\tcontrol: Element;\n\tcopy: (original: ChildNode) => Node | undefined;\n\tisCopied?: (child: Node) => boolean;\n\tisPassed?: (child: Node) => boolean;\n\tplace: (copies: Array<Node>) => void;\n}\n\n// eslint-disable-next-line wc/define-tag-after-class-definition -- abstract; only subclasses are defined\nexport abstract class SonicElement extends HTMLElement {\n\tstatic readonly observedAttributes = [...namingAttributes, 'disabled'];\n\n\tget disabled(): boolean {\n\t\treturn this.hasAttribute('disabled');\n\t}\n\n\tset disabled(isDisabled: boolean) {\n\t\tthis.reflect('disabled', isDisabled);\n\t}\n\n\t// happy-dom has no `ElementInternals`\n\tprotected readonly internals =\n\t\t'attachInternals' in HTMLElement.prototype ? this.attachInternals() : undefined;\n\n\t#connection: AbortController | undefined;\n\n\t#slots: MirrorSlots | undefined;\n\n\toverride blur(): void {\n\t\tthis.#focused()?.blur();\n\t}\n\n\tconnectedCallback(): void {\n\t\tthis.upgradeProperties('disabled');\n\t\tif (this.#connection) return;\n\n\t\tconst connection = new AbortController();\n\n\t\tthis.#connection = connection;\n\t\ttry {\n\t\t\tthis.connect(connection.signal);\n\t\t} catch (error) {\n\t\t\tconnection.abort();\n\t\t\tthis.#connection = undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tconnectedMoveCallback(): void {\n\t\t// Defined so `moveBefore` from a persisting router keeps every binding\n\t}\n\n\t// Without `moveBefore` (Safari), a persisting router disconnects and reconnects in one task\n\tdisconnectedCallback(): void {\n\t\tconst connection = this.#connection;\n\t\tif (!connection) return;\n\n\t\tqueueMicrotask(() => {\n\t\t\tif (this.isConnected || this.#connection !== connection) return;\n\n\t\t\tconnection.abort();\n\t\t\tthis.#connection = undefined;\n\t\t});\n\t}\n\n\toverride focus(options?: FocusOptions): void {\n\t\tconst target = this.focusTarget();\n\t\tif (!target) {\n\t\t\tsuper.focus(options);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isDisabled() && !this.#focused()) target.focus(options);\n\t}\n\n\tprotected checkStyles(control: HTMLElement, sheet: string): void {\n\t\tif (!__DEV__) return;\n\n\t\tconst elementClass = this.constructor;\n\t\tconst checked = checkedSheets.get(elementClass) ?? new Set<string>();\n\t\tconst part = ({ selector }: StyleProbe): Element | undefined =>\n\t\t\tselector === undefined ? control : (this.querySelector(selector) ?? undefined);\n\t\t// An LED inherits `--_sonic-unit` from its control, so a token of its own probes it\n\t\tconst probes: Array<StyleProbe> = [\n\t\t\t{ sheet: 'material.css', token: '--_sonic-unlit' },\n\t\t\t{ sheet, token: '--_sonic-unit' },\n\t\t\t{ selector: '.sonic-led', sheet: 'led.css', token: '--_sonic-led-lens-ratio' },\n\t\t].filter((probe) => !checked.has(probe.sheet) && part(probe) !== undefined);\n\t\tif (probes.length === 0) return;\n\n\t\tcheckedSheets.set(elementClass, checked);\n\t\tfor (const probe of probes) checked.add(probe.sheet);\n\t\trequestAnimationFrame(() => {\n\t\t\tconst missing: Array<string> = [];\n\n\t\t\tfor (const probe of probes) {\n\t\t\t\tconst drawn = control.isConnected ? part(probe) : undefined;\n\n\t\t\t\tif (!drawn) checked.delete(probe.sheet);\n\t\t\t\telse if (getComputedStyle(drawn).getPropertyValue(probe.token) === '') {\n\t\t\t\t\tmissing.push(`@xsynaptic/sonic-ui/${probe.sheet}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (missing.length === 0) return;\n\n\t\t\tconsole.warn(\n\t\t\t\t`<${this.localName}> draws blank without ${missing.join(' and ')} (or controls.css)`,\n\t\t\t);\n\t\t});\n\t}\n\n\tprotected abstract connect(signal: AbortSignal): void;\n\n\tprotected focusTarget(): HTMLElement | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected forwardNaming(target: Element, isNamed: boolean): void {\n\t\tconst labels = isNamed ? this.namingLabels() : [];\n\n\t\tfor (const name of namingAttributes) {\n\t\t\t// `ariaLabelledByElements` holds the attribute, empty\n\t\t\tif (name === 'aria-labelledby' && labels.length > 0) continue;\n\n\t\t\tconst value = this.getAttribute(name);\n\n\t\t\twriteAttribute(target, name, isNamed && value !== null ? value : undefined);\n\t\t}\n\t\tif (labels.length > 0) writeLabelledBy(target, labels);\n\t}\n\n\t// happy-dom matches `:disabled` on no custom element, and an element not form-associated never matches\n\tprotected isBound(): boolean {\n\t\treturn this.#connection !== undefined;\n\t}\n\n\tprotected isDisabled(): boolean {\n\t\treturn this.disabled || this.matches(':disabled');\n\t}\n\n\t// A morph against server HTML, or a stray `innerHTML`, deletes the control\n\tprotected keepControl(control: Element, signal: AbortSignal): void {\n\t\tconst keep = (): void => {\n\t\t\tthis.#appendOnce(control);\n\t\t\tthis.#checkChildren([...this.childNodes].filter((child) => child !== control));\n\t\t};\n\t\tconst observer = new MutationObserver(keep);\n\n\t\tkeep();\n\t\tobserver.observe(this, { childList: true });\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tprotected mirrorChildren(\n\t\t{ control, copy, isCopied = () => true, isPassed = () => false, place }: Mirror,\n\t\tsignal: AbortSignal,\n\t): void {\n\t\tconst slots = this.#slots ?? attachSlots(this);\n\t\tlet copies = new Map<ChildNode, Node | undefined>();\n\n\t\tthis.#slots = slots;\n\t\tconst mirror = (touched?: Set<Node | undefined>): void => {\n\t\t\tthis.#appendOnce(control);\n\n\t\t\tconst children = [...this.childNodes].filter((child) => child !== control);\n\t\t\tconst slotted = children.filter(\n\t\t\t\t(child): child is Element | Text => child instanceof Element || child instanceof Text,\n\t\t\t);\n\t\t\tconst kept = copies;\n\t\t\tconst originals = children.filter((child) => isCopied(child));\n\n\t\t\tthis.#checkMirrored(originals);\n\t\t\tthis.#checkChildren(children.filter((child) => !isCopied(child) && !isPassed(child)));\n\t\t\tcopies = new Map(\n\t\t\t\toriginals.map((original) => [\n\t\t\t\t\toriginal,\n\t\t\t\t\ttouched && !touched.has(original) && kept.has(original)\n\t\t\t\t\t\t? kept.get(original)\n\t\t\t\t\t\t: copy(original),\n\t\t\t\t]),\n\t\t\t);\n\t\t\tplace([...copies.values()].filter((made) => made !== undefined));\n\t\t\tslots.shown.assign(...slotted.filter((child) => !isCopied(child)), control);\n\t\t\tslots.kept.assign(...slotted.filter((child) => isCopied(child)));\n\t\t};\n\t\t// Each child is watched, never the control, so a value write wakes nothing\n\t\tconst watch = (): void => {\n\t\t\tobserver.disconnect();\n\t\t\tobserver.observe(this, { childList: true });\n\t\t\tfor (const child of this.childNodes) {\n\t\t\t\tif (child === control) continue;\n\n\t\t\t\tobserver.observe(child, {\n\t\t\t\t\tattributes: true,\n\t\t\t\t\tcharacterData: true,\n\t\t\t\t\tchildList: true,\n\t\t\t\t\tsubtree: true,\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\t\tconst observer = new MutationObserver((records) => {\n\t\t\tconst changes = records.filter((record) => this.#isCopiedChange(record, control, isCopied));\n\t\t\tif (changes.length === 0) return;\n\n\t\t\tif (changes.some((record) => record.target === this)) {\n\t\t\t\tmirror();\n\t\t\t\twatch();\n\t\t\t} else mirror(new Set(changes.map((record) => this.#childHolding(record.target))));\n\t\t});\n\n\t\tmirror();\n\t\twatch();\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tprotected namingLabels(): Array<Element> {\n\t\treturn [];\n\t}\n\n\tprotected numberAttribute(name: string, fallback: number): number {\n\t\tconst parsed = toNumber(this.getAttribute(name));\n\n\t\treturn Number.isFinite(parsed) ? parsed : fallback;\n\t}\n\n\tprotected optionalNumberAttribute(name: string): number | undefined {\n\t\tconst value = this.numberAttribute(name, NaN);\n\n\t\treturn Number.isNaN(value) ? undefined : value;\n\t}\n\n\t// Never compared; a host attribute set again resets the value a gesture left dirty\n\tprotected reflect(name: string, value: boolean | null | number | string | undefined): void {\n\t\tif (typeof value !== 'number' && typeof value !== 'string' && value !== true) {\n\t\t\tthis.removeAttribute(name);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setAttribute(name, value === true ? '' : value.toString());\n\t}\n\n\tprotected toggleState(state: string, isOn: boolean): void {\n\t\tif (isOn) this.internals?.states.add(state);\n\t\telse this.internals?.states.delete(state);\n\t}\n\n\t// A property set before upgrade shadows its accessor\n\tprotected upgradeProperties(...names: Array<keyof this>): void {\n\t\tfor (const name of names) {\n\t\t\tif (!Object.hasOwn(this, name)) continue;\n\n\t\t\tconst value = this[name];\n\n\t\t\tReflect.deleteProperty(this, name);\n\t\t\tthis[name] = value;\n\t\t}\n\t}\n\n\t#appendOnce(control: Element): void {\n\t\tconst [hook] = control.classList;\n\t\tconst stale = [...this.children].filter(\n\t\t\t(child) => child !== control && hook !== undefined && child.classList.contains(hook),\n\t\t);\n\n\t\tfor (const child of stale) child.remove();\n\t\tif (control.parentNode !== this) this.append(control);\n\t}\n\n\t#checkChildren(undocumented: Array<Node>): void {\n\t\tif (!__DEV__ || checkedChildren.has(this.constructor)) return;\n\n\t\tconst found = undocumented.find((child) => isContent(child));\n\t\tif (!found) return;\n\n\t\tcheckedChildren.add(this.constructor);\n\t\tconsole.warn(\n\t\t\t`<${this.localName}> uses only the children it documents, so the ${\n\t\t\t\tfound instanceof Element ? `<${found.localName}>` : 'text'\n\t\t\t} inside it is unsupported`,\n\t\t);\n\t}\n\n\t#checkMirrored(originals: Array<Node>): void {\n\t\tif (!__DEV__ || checkedMirrors.has(this.constructor)) return;\n\n\t\tconst [found] = originals.flatMap((original) => unmirrorable(original) ?? []);\n\t\tif (!found) return;\n\n\t\tcheckedMirrors.add(this.constructor);\n\t\tconsole.warn(\n\t\t\t`<${this.localName}> copies its children into the control, so the <${found.localName}> inside it loses its listeners and state; keep mirrored children static`,\n\t\t);\n\t}\n\n\t#childHolding(node: Node): Node | undefined {\n\t\tlet current: Node | null = node;\n\n\t\twhile (current && current.parentNode !== this) current = current.parentNode;\n\n\t\treturn current ?? undefined;\n\t}\n\n\t#focused(): HTMLElement | undefined {\n\t\tconst root = this.getRootNode();\n\t\tconst active =\n\t\t\troot instanceof ShadowRoot ? root.activeElement : this.ownerDocument.activeElement;\n\n\t\treturn active instanceof HTMLElement && this.contains(active) ? active : undefined;\n\t}\n\n\t#isCopiedChange(\n\t\trecord: MutationRecord,\n\t\tcontrol: Element,\n\t\tisCopied: (child: Node) => boolean,\n\t): boolean {\n\t\tif (record.target === this) {\n\t\t\treturn (\n\t\t\t\trecord.removedNodes.length > 0 || [...record.addedNodes].some((node) => node !== control)\n\t\t\t);\n\t\t}\n\n\t\tconst child = this.#childHolding(record.target);\n\n\t\tif (child === undefined) return false;\n\n\t\treturn isCopied(child) || (record.type === 'attributes' && record.target === child);\n\t}\n}\n"],"mappings":"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"}