@xsynaptic/sonic-ui 0.10.3 → 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 (68) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +12 -0
  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/sonic-element.js +11 -2
  9. package/dist/default/elements/switch.js +34 -168
  10. package/dist/default/elements/toggle.js +189 -0
  11. package/dist/default/elements/value-element.js +9 -0
  12. package/dist/default/elements/waveform.js +38 -19
  13. package/dist/default/elements/wavestrip.js +6 -1
  14. package/dist/default/index.js +2 -1
  15. package/dist/default/lib/key-press.js +27 -0
  16. package/dist/default/lib/strip-scene.js +4 -3
  17. package/dist/default/lib/toggle-travel.js +19 -0
  18. package/dist/dev/define/toggle.d.ts +2 -0
  19. package/dist/dev/define/toggle.js +8 -0
  20. package/dist/dev/define/toggle.js.map +1 -0
  21. package/dist/dev/define.d.ts +3 -1
  22. package/dist/dev/define.js +1 -0
  23. package/dist/dev/elements/button.js +2 -0
  24. package/dist/dev/elements/button.js.map +1 -1
  25. package/dist/dev/elements/position-group.d.ts +38 -0
  26. package/dist/dev/elements/position-group.js +164 -0
  27. package/dist/dev/elements/position-group.js.map +1 -0
  28. package/dist/dev/elements/radio-group.d.ts +4 -0
  29. package/dist/dev/elements/radio-group.js +34 -12
  30. package/dist/dev/elements/radio-group.js.map +1 -1
  31. package/dist/dev/elements/sonic-element.d.ts +1 -0
  32. package/dist/dev/elements/sonic-element.js +11 -2
  33. package/dist/dev/elements/sonic-element.js.map +1 -1
  34. package/dist/dev/elements/switch.d.ts +4 -19
  35. package/dist/dev/elements/switch.js +34 -168
  36. package/dist/dev/elements/switch.js.map +1 -1
  37. package/dist/dev/elements/toggle.d.ts +21 -0
  38. package/dist/dev/elements/toggle.js +197 -0
  39. package/dist/dev/elements/toggle.js.map +1 -0
  40. package/dist/dev/elements/value-element.d.ts +3 -0
  41. package/dist/dev/elements/value-element.js +9 -0
  42. package/dist/dev/elements/value-element.js.map +1 -1
  43. package/dist/dev/elements/waveform.d.ts +1 -1
  44. package/dist/dev/elements/waveform.js +38 -19
  45. package/dist/dev/elements/waveform.js.map +1 -1
  46. package/dist/dev/elements/wavestrip.d.ts +3 -0
  47. package/dist/dev/elements/wavestrip.js +6 -1
  48. package/dist/dev/elements/wavestrip.js.map +1 -1
  49. package/dist/dev/index.d.ts +3 -2
  50. package/dist/dev/index.js +2 -1
  51. package/dist/dev/lib/key-press.js +28 -0
  52. package/dist/dev/lib/key-press.js.map +1 -0
  53. package/dist/dev/lib/strip-scene.js +4 -3
  54. package/dist/dev/lib/strip-scene.js.map +1 -1
  55. package/dist/dev/lib/toggle-travel.js +20 -0
  56. package/dist/dev/lib/toggle-travel.js.map +1 -0
  57. package/dist/styles/button.css +2 -2
  58. package/dist/styles/controls.css +1 -0
  59. package/dist/styles/led.css +16 -13
  60. package/dist/styles/material.css +36 -9
  61. package/dist/styles/meter.css +7 -6
  62. package/dist/styles/segmented.css +8 -4
  63. package/dist/styles/spectrum.css +2 -2
  64. package/dist/styles/switch.css +6 -2
  65. package/dist/styles/toggle.css +481 -0
  66. package/dist/styles/waveform.css +5 -2
  67. package/dist/styles/wavestrip.css +7 -0
  68. package/package.json +2 -1
@@ -24,6 +24,7 @@ var SonicElement = class extends HTMLElement {
24
24
  }
25
25
  internals = "attachInternals" in HTMLElement.prototype ? this.attachInternals() : void 0;
26
26
  #connection;
27
+ #states = /* @__PURE__ */ new Set();
27
28
  blur() {
28
29
  this.#focused()?.blur();
29
30
  }
@@ -101,6 +102,9 @@ var SonicElement = class extends HTMLElement {
101
102
  }
102
103
  if (labels.length > 0) writeLabelledBy(target, labels);
103
104
  }
105
+ hasState(state) {
106
+ return this.#states.has(state);
107
+ }
104
108
  isBound() {
105
109
  return this.#connection !== void 0;
106
110
  }
@@ -138,8 +142,13 @@ var SonicElement = class extends HTMLElement {
138
142
  this.setAttribute(name, value === true ? "" : value.toString());
139
143
  }
140
144
  toggleState(state, isOn) {
141
- if (isOn) this.internals?.states.add(state);
142
- else this.internals?.states.delete(state);
145
+ if (isOn) {
146
+ this.#states.add(state);
147
+ this.internals?.states.add(state);
148
+ return;
149
+ }
150
+ this.#states.delete(state);
151
+ this.internals?.states.delete(state);
143
152
  }
144
153
  upgradeProperties(...names) {
145
154
  for (const name of names) {
@@ -1 +1 @@
1
- {"version":3,"file":"sonic-element.js","names":[],"sources":["../../../src/elements/sonic-element.ts"],"sourcesContent":["import { toNumber } from '#lib/math.ts';\nimport { appendOnce, checkChildren } from '#lib/owned-control.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\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\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\tappendOnce(this, control);\n\t\t\tif (__DEV__)\n\t\t\t\tcheckChildren(\n\t\t\t\t\tthis,\n\t\t\t\t\t[...this.childNodes].filter((child) => child !== control),\n\t\t\t\t);\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 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#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"],"mappings":";;;;;AAOA,MAAM,mBAAmB;CAAC;CAAoB;CAAc;AAAiB;AAE7E,MAAM,gCAAgB,IAAI,QAA6B;AAQvD,SAAS,gBAAgB,QAAiB,QAA8B;CACvE,MAAM,UAAU,OAAO;CACvB,IACC,SAAS,WAAW,OAAO,UAC3B,OAAO,OAAO,OAAO,UAAU,UAAU,QAAQ,MAAM,GAEvD;CAGD,OAAO,yBAAyB;AACjC;AAGA,IAAsB,eAAtB,cAA2C,YAAY;CACtD,OAAgB,qBAAqB,CAAC,GAAG,kBAAkB,UAAU;CAErE,IAAI,WAAoB;EACvB,OAAO,KAAK,aAAa,UAAU;CACpC;CAEA,IAAI,SAAS,YAAqB;EACjC,KAAK,QAAQ,YAAY,UAAU;CACpC;CAGA,AAAmB,YAClB,qBAAqB,YAAY,YAAY,KAAK,gBAAgB,IAAI;CAEvE;CAEA,AAAS,OAAa;EACrB,KAAK,SAAS,CAAC,EAAE,KAAK;CACvB;CAEA,oBAA0B;EACzB,KAAK,kBAAkB,UAAU;EACjC,IAAI,KAAK,aAAa;EAEtB,MAAM,aAAa,IAAI,gBAAgB;EAEvC,KAAK,cAAc;EACnB,IAAI;GACH,KAAK,QAAQ,WAAW,MAAM;EAC/B,SAAS,OAAO;GACf,WAAW,MAAM;GACjB,KAAK,cAAc;GACnB,MAAM;EACP;CACD;CAEA,wBAA8B,CAE9B;CAGA,uBAA6B;EAC5B,MAAM,aAAa,KAAK;EACxB,IAAI,CAAC,YAAY;EAEjB,qBAAqB;GACpB,IAAI,KAAK,eAAe,KAAK,gBAAgB,YAAY;GAEzD,WAAW,MAAM;GACjB,KAAK,cAAc;EACpB,CAAC;CACF;CAEA,AAAS,MAAM,SAA8B;EAC5C,MAAM,SAAS,KAAK,YAAY;EAChC,IAAI,CAAC,QAAQ;GACZ,MAAM,MAAM,OAAO;GACnB;EACD;EAEA,IAAI,CAAC,KAAK,WAAW,KAAK,CAAC,KAAK,SAAS,GAAG,OAAO,MAAM,OAAO;CACjE;CAEA,AAAU,YAAY,SAAsB,OAAqB;EAGhE,MAAM,eAAe,KAAK;EAC1B,MAAM,UAAU,cAAc,IAAI,YAAY,qBAAK,IAAI,IAAY;EACnE,MAAM,QAAQ,EAAE,eACf,aAAa,SAAY,UAAW,KAAK,cAAc,QAAQ,KAAK;EAErE,MAAM,SAA4B;GACjC;IAAE,OAAO;IAAgB,OAAO;GAAiB;GACjD;IAAE;IAAO,OAAO;GAAgB;GAChC;IAAE,UAAU;IAAc,OAAO;IAAW,OAAO;GAA0B;EAC9E,CAAC,CAAC,QAAQ,UAAU,CAAC,QAAQ,IAAI,MAAM,KAAK,KAAK,KAAK,KAAK,MAAM,MAAS;EAC1E,IAAI,OAAO,WAAW,GAAG;EAEzB,cAAc,IAAI,cAAc,OAAO;EACvC,KAAK,MAAM,SAAS,QAAQ,QAAQ,IAAI,MAAM,KAAK;EACnD,4BAA4B;GAC3B,MAAM,UAAyB,CAAC;GAEhC,KAAK,MAAM,SAAS,QAAQ;IAC3B,MAAM,QAAQ,QAAQ,cAAc,KAAK,KAAK,IAAI;IAElD,IAAI,CAAC,OAAO,QAAQ,OAAO,MAAM,KAAK;SACjC,IAAI,iBAAiB,KAAK,CAAC,CAAC,iBAAiB,MAAM,KAAK,MAAM,IAClE,QAAQ,KAAK,uBAAuB,MAAM,OAAO;GAEnD;GACA,IAAI,QAAQ,WAAW,GAAG;GAE1B,QAAQ,KACP,IAAI,KAAK,UAAU,wBAAwB,QAAQ,KAAK,OAAO,EAAE,mBAClE;EACD,CAAC;CACF;CAIA,AAAU,cAAuC,CAEjD;CAEA,AAAU,cAAc,QAAiB,SAAwB;EAChE,MAAM,SAAS,UAAU,KAAK,aAAa,IAAI,CAAC;EAEhD,KAAK,MAAM,QAAQ,kBAAkB;GAEpC,IAAI,SAAS,qBAAqB,OAAO,SAAS,GAAG;GAErD,MAAM,QAAQ,KAAK,aAAa,IAAI;GAEpC,eAAe,QAAQ,MAAM,WAAW,UAAU,OAAO,QAAQ,MAAS;EAC3E;EACA,IAAI,OAAO,SAAS,GAAG,gBAAgB,QAAQ,MAAM;CACtD;CAGA,AAAU,UAAmB;EAC5B,OAAO,KAAK,gBAAgB;CAC7B;CAEA,AAAU,aAAsB;EAC/B,OAAO,KAAK,YAAY,KAAK,QAAQ,WAAW;CACjD;CAGA,AAAU,YAAY,SAAkB,QAA2B;EAClE,MAAM,aAAmB;GACxB,WAAW,MAAM,OAAO;GAEvB,cACC,MACA,CAAC,GAAG,KAAK,UAAU,CAAC,CAAC,QAAQ,UAAU,UAAU,OAAO,CACzD;EACF;EACA,MAAM,WAAW,IAAI,iBAAiB,IAAI;EAE1C,KAAK;EACL,SAAS,QAAQ,MAAM,EAAE,WAAW,KAAK,CAAC;EAC1C,OAAO,iBACN,eACM;GACL,SAAS,WAAW;EACrB,GACA,EAAE,MAAM,KAAK,CACd;CACD;CAEA,AAAU,eAA+B;EACxC,OAAO,CAAC;CACT;CAEA,AAAU,gBAAgB,MAAc,UAA0B;EACjE,MAAM,SAAS,SAAS,KAAK,aAAa,IAAI,CAAC;EAE/C,OAAO,OAAO,SAAS,MAAM,IAAI,SAAS;CAC3C;CAEA,AAAU,wBAAwB,MAAkC;EACnE,MAAM,QAAQ,KAAK,gBAAgB,MAAM,GAAG;EAE5C,OAAO,OAAO,MAAM,KAAK,IAAI,SAAY;CAC1C;CAGA,AAAU,QAAQ,MAAc,OAA2D;EAC1F,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,UAAU,MAAM;GAC7E,KAAK,gBAAgB,IAAI;GACzB;EACD;EAEA,KAAK,aAAa,MAAM,UAAU,OAAO,KAAK,MAAM,SAAS,CAAC;CAC/D;CAEA,AAAU,YAAY,OAAe,MAAqB;EACzD,IAAI,MAAM,KAAK,WAAW,OAAO,IAAI,KAAK;OACrC,KAAK,WAAW,OAAO,OAAO,KAAK;CACzC;CAGA,AAAU,kBAAkB,GAAG,OAAgC;EAC9D,KAAK,MAAM,QAAQ,OAAO;GACzB,IAAI,CAAC,OAAO,OAAO,MAAM,IAAI,GAAG;GAEhC,MAAM,QAAQ,KAAK;GAEnB,QAAQ,eAAe,MAAM,IAAI;GACjC,KAAK,QAAQ;EACd;CACD;CAEA,WAAoC;EACnC,MAAM,OAAO,KAAK,YAAY;EAC9B,MAAM,SACL,gBAAgB,aAAa,KAAK,gBAAgB,KAAK,cAAc;EAEtE,OAAO,kBAAkB,eAAe,KAAK,SAAS,MAAM,IAAI,SAAS;CAC1E;AACD"}
1
+ {"version":3,"file":"sonic-element.js","names":[],"sources":["../../../src/elements/sonic-element.ts"],"sourcesContent":["import { toNumber } from '#lib/math.ts';\nimport { appendOnce, checkChildren } from '#lib/owned-control.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\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\treadonly #states = new Set<string>();\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\tprotected hasState(state: string): boolean {\n\t\treturn this.#states.has(state);\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\tappendOnce(this, control);\n\t\t\tif (__DEV__)\n\t\t\t\tcheckChildren(\n\t\t\t\t\tthis,\n\t\t\t\t\t[...this.childNodes].filter((child) => child !== control),\n\t\t\t\t);\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 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) {\n\t\t\tthis.#states.add(state);\n\t\t\tthis.internals?.states.add(state);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#states.delete(state);\n\t\tthis.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#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"],"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(`
@@ -18,166 +16,60 @@ const renderPosition = template(`
18
16
  </button>
19
17
  `, HTMLButtonElement);
20
18
  const renderBare = template(`<button class="sonic-switch-position" role="switch" type="button"></button>`, HTMLButtonElement);
21
- const pressCentreZone = .08;
22
- function pressedIndex(index, count, offset) {
23
- if (count === 2) return index === 0 ? 1 : 0;
24
- const side = Math.abs(offset) < pressCentreZone ? 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
- }
19
+ const pressCenterZone = .08;
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 pressCentreZone = 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) < pressCentreZone ? 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":"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"}
@@ -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