@xsynaptic/sonic-ui 0.24.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -2
- package/README.md +5 -3
- package/dist/default/define/region.js +7 -0
- package/dist/default/define.js +1 -0
- package/dist/default/elements/envelope.js +11 -11
- package/dist/default/elements/form-element.js +10 -0
- package/dist/default/elements/readout.js +3 -2
- package/dist/default/elements/region-layer.js +251 -0
- package/dist/default/elements/region.js +157 -0
- package/dist/default/elements/sonic-element.js +26 -4
- package/dist/default/elements/split.js +2 -2
- package/dist/default/elements/toggle.js +2 -2
- package/dist/default/elements/value-element.js +9 -5
- package/dist/default/elements/value-gestures.js +7 -5
- package/dist/default/elements/wave-element.js +12 -3
- package/dist/default/elements/waveform.js +38 -2
- package/dist/default/elements/wavestrip.js +90 -17
- package/dist/default/elements/xy.js +3 -11
- package/dist/default/index.js +2 -1
- package/dist/default/lib/clock.js +1 -0
- package/dist/default/lib/drag-step.js +1 -1
- package/dist/default/lib/marker-band.js +40 -29
- package/dist/default/lib/region-drag.js +48 -0
- package/dist/default/lib/spoken-duration.js +3 -0
- package/dist/dev/define/region.d.ts +2 -0
- package/dist/dev/define/region.js +8 -0
- package/dist/dev/define/region.js.map +1 -0
- package/dist/dev/define.d.ts +3 -1
- package/dist/dev/define.js +1 -0
- package/dist/dev/elements/envelope.js +11 -11
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/form-element.d.ts +1 -0
- package/dist/dev/elements/form-element.js +10 -0
- package/dist/dev/elements/form-element.js.map +1 -1
- package/dist/dev/elements/readout.js +3 -2
- package/dist/dev/elements/readout.js.map +1 -1
- package/dist/dev/elements/region-layer.js +252 -0
- package/dist/dev/elements/region-layer.js.map +1 -0
- package/dist/dev/elements/region.d.ts +29 -0
- package/dist/dev/elements/region.js +165 -0
- package/dist/dev/elements/region.js.map +1 -0
- package/dist/dev/elements/sonic-element.d.ts +1 -0
- package/dist/dev/elements/sonic-element.js +27 -5
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/split.js +2 -2
- package/dist/dev/elements/split.js.map +1 -1
- package/dist/dev/elements/toggle.js +2 -2
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-element.d.ts +3 -1
- package/dist/dev/elements/value-element.js +9 -5
- package/dist/dev/elements/value-element.js.map +1 -1
- package/dist/dev/elements/value-gestures.js +7 -5
- package/dist/dev/elements/value-gestures.js.map +1 -1
- package/dist/dev/elements/wave-element.d.ts +7 -1
- package/dist/dev/elements/wave-element.js +12 -3
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +7 -0
- package/dist/dev/elements/waveform.js +38 -2
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/wavestrip.d.ts +17 -1
- package/dist/dev/elements/wavestrip.js +90 -17
- package/dist/dev/elements/wavestrip.js.map +1 -1
- package/dist/dev/elements/xy.js +3 -11
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/index.d.ts +3 -2
- package/dist/dev/index.js +2 -1
- package/dist/dev/lib/clock.js +1 -0
- package/dist/dev/lib/clock.js.map +1 -1
- package/dist/dev/lib/drag-step.js +1 -1
- package/dist/dev/lib/drag-step.js.map +1 -1
- package/dist/dev/lib/marker-band.d.ts +7 -0
- package/dist/dev/lib/marker-band.js +40 -29
- package/dist/dev/lib/marker-band.js.map +1 -1
- package/dist/dev/lib/region-drag.js +49 -0
- package/dist/dev/lib/region-drag.js.map +1 -0
- package/dist/dev/lib/spoken-duration.js +3 -0
- package/dist/dev/lib/spoken-duration.js.map +1 -1
- package/dist/styles/button.css +42 -15
- package/dist/styles/controls.css +1 -0
- package/dist/styles/dial.css +48 -33
- package/dist/styles/envelope.css +7 -6
- package/dist/styles/led.css +28 -13
- package/dist/styles/material/arc.css +2 -2
- package/dist/styles/material/cap.css +25 -15
- package/dist/styles/material/core.css +53 -1
- package/dist/styles/material/glass.css +12 -2
- package/dist/styles/material/groove.css +3 -3
- package/dist/styles/material/keycap.css +5 -5
- package/dist/styles/material/lens.css +1 -1
- package/dist/styles/material/pane.css +21 -11
- package/dist/styles/material/readout.css +23 -10
- package/dist/styles/material/{bracket.css → reticle.css} +15 -8
- package/dist/styles/material/scale.css +4 -4
- package/dist/styles/material/value.css +12 -3
- package/dist/styles/material/well.css +2 -2
- package/dist/styles/material.css +1 -1
- package/dist/styles/meter.css +24 -11
- package/dist/styles/number.css +2 -1
- package/dist/styles/panel.css +5 -6
- package/dist/styles/region.css +88 -0
- package/dist/styles/ring.css +5 -4
- package/dist/styles/screen.css +7 -5
- package/dist/styles/segmented.css +8 -7
- package/dist/styles/slider.css +40 -32
- package/dist/styles/spectrum.css +9 -1
- package/dist/styles/switch.css +52 -53
- package/dist/styles/toggle.css +33 -38
- package/dist/styles/waveform.css +5 -4
- package/dist/styles/wavestrip.css +15 -12
- package/dist/styles/xy.css +19 -19
- package/package.json +3 -2
|
@@ -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\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\n// A padding or margin the sheets leave at zero only where `ratio` is zero\ninterface BoxProbe {\n\tproperty: string;\n\tratio?: `--_sonic-${string}`;\n\tselector?: string;\n}\n\nfunction isLost(drawn: Element, { token }: StyleProbe): boolean {\n\treturn getComputedStyle(drawn).getPropertyValue(token) === '';\n}\n\nconst hostBoxProperties = [\n\t'margin-top',\n\t'margin-right',\n\t'margin-bottom',\n\t'margin-left',\n\t'padding-top',\n\t'padding-right',\n\t'padding-bottom',\n\t'padding-left',\n\t'width',\n\t'height',\n];\n\n// An environment with no computed styles answers '', which is no box property\nfunction readHostBox(host: Element): string | undefined {\n\tconst style = getComputedStyle(host);\n\tif (style.display === 'none') return undefined;\n\tif (style.display !== 'contents') return 'display';\n\n\treturn hostBoxProperties.find(\n\t\t(property) => !['', '0px', 'auto'].includes(style.getPropertyValue(property)),\n\t);\n}\n\nfunction isZeroed(control: Element, { property, ratio, selector }: BoxProbe): boolean {\n\tconst part = selector === undefined ? control : control.querySelector(selector);\n\tif (!part || getComputedStyle(part).getPropertyValue(property) !== '0px') return false;\n\n\treturn ratio === undefined || Number(getComputedStyle(control).getPropertyValue(ratio)) !== 0;\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, box?: BoxProbe): 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/core.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\tconst isResetChecked = !box || checked.has('reset');\n\t\tconst isBoxChecked = isResetChecked && checked.has('host');\n\t\tif (isBoxChecked && 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 (isLost(drawn, probe)) missing.push(`@xsynaptic/sonic-ui/${probe.sheet}`);\n\t\t\t}\n\t\t\tif (missing.length === 0) {\n\t\t\t\tthis.#checkBox(control, checked, box);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tchecked.add('host').add('reset');\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// Once a class; a sheet that is missing has already said why the control looks wrong\n\t#checkBox(control: HTMLElement, checked: Set<string>, box: BoxProbe | undefined): void {\n\t\tif (!__DEV__ || !control.isConnected) return;\n\n\t\tif (!checked.has('host')) this.#checkHost(checked);\n\t\tif (!box || checked.has('reset') || !isZeroed(control, box)) return;\n\n\t\tchecked.add('reset');\n\t\tconsole.warn(\n\t\t\t`<${this.localName}> has lost its ${box.property} to a rule outside a layer, which beats every sonic rule; import that stylesheet into a layer declared before sonic`,\n\t\t);\n\t}\n\n\t#checkHost(checked: Set<string>): void {\n\t\tif (!__DEV__) return;\n\n\t\tconst hostBox = readHostBox(this);\n\t\tif (hostBox === undefined) return;\n\n\t\tchecked.add('host');\n\t\tconsole.warn(\n\t\t\thostBox === 'display'\n\t\t\t\t? `<${this.localName}> is given a display, but it has to stay display: contents; lay it out through a parent or a wrapper`\n\t\t\t\t: `<${this.localName}> is display: contents and has no box, so its ${hostBox} does nothing; set it on a parent or a wrapper`,\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
|
+
{"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\ndeclare const __DEV__: boolean;\n\nconst namingAttributes = ['aria-describedby', 'aria-label', 'aria-labelledby'];\n\nconst checkedSheets = new WeakMap<object, Set<string>>();\n\n// Before `:state()`, `states` is missing or refuses an ident without the legacy dashes\nconst canMatchStates = CSS.supports('selector(:state(a))');\n\nconst canReferElements = 'ariaLabelledByElements' in Element.prototype;\n\ninterface StyleProbe {\n\tselector?: string;\n\tsheet: string;\n\ttoken: string;\n}\n\n// A padding or margin the sheets leave at zero only where `ratio` is zero\ninterface BoxProbe {\n\tproperty: string;\n\tratio?: `--_sonic-${string}`;\n\tselector?: string;\n}\n\nfunction isLost(drawn: Element, { token }: StyleProbe): boolean {\n\treturn getComputedStyle(drawn).getPropertyValue(token) === '';\n}\n\nconst hostBoxProperties = [\n\t'margin-top',\n\t'margin-right',\n\t'margin-bottom',\n\t'margin-left',\n\t'padding-top',\n\t'padding-right',\n\t'padding-bottom',\n\t'padding-left',\n\t'width',\n\t'height',\n];\n\n// An environment with no computed styles answers '', which is no box property\nfunction readHostBox(host: Element): string | undefined {\n\tconst style = getComputedStyle(host);\n\tif (style.display === 'none') return undefined;\n\tif (style.display !== 'contents') return 'display';\n\n\treturn hostBoxProperties.find(\n\t\t(property) => !['', '0px', 'auto'].includes(style.getPropertyValue(property)),\n\t);\n}\n\nfunction isZeroed(control: Element, { property, ratio, selector }: BoxProbe): boolean {\n\tconst part = selector === undefined ? control : control.querySelector(selector);\n\tif (!part || getComputedStyle(part).getPropertyValue(property) !== '0px') return false;\n\n\treturn ratio === undefined || Number(getComputedStyle(control).getPropertyValue(ratio)) !== 0;\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\tstatic #labelCount = 0;\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 #customStates = canMatchStates ? this.internals?.states : undefined;\n\n\treadonly #states = new Set<string>();\n\n\tstatic #labelId(label: Element): string {\n\t\tif (label.id === '') {\n\t\t\tSonicElement.#labelCount += 1;\n\t\t\tlabel.id = `sonic-label-${String(SonicElement.#labelCount)}`;\n\t\t}\n\n\t\treturn label.id;\n\t}\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, box?: BoxProbe): 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/core.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\tconst isResetChecked = !box || checked.has('reset');\n\t\tconst isBoxChecked = isResetChecked && checked.has('host');\n\t\tif (isBoxChecked && 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 (isLost(drawn, probe)) missing.push(`@xsynaptic/sonic-ui/${probe.sheet}`);\n\t\t\t}\n\t\t\tif (missing.length === 0) {\n\t\t\t\tthis.#checkBox(control, checked, box);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tchecked.add('host').add('reset');\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) this.#writeLabels(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 && !this.usesChild(child)),\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.#customStates?.add(state);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#states.delete(state);\n\t\tthis.#customStates?.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\tprotected usesChild(_child: Node): boolean {\n\t\treturn false;\n\t}\n\n\t// Once a class; a sheet that is missing has already said why the control looks wrong\n\t#checkBox(control: HTMLElement, checked: Set<string>, box: BoxProbe | undefined): void {\n\t\tif (!__DEV__ || !control.isConnected) return;\n\n\t\tif (!checked.has('host')) this.#checkHost(checked);\n\t\tif (!box || checked.has('reset') || !isZeroed(control, box)) return;\n\n\t\tchecked.add('reset');\n\t\tconsole.warn(\n\t\t\t`<${this.localName}> has lost its ${box.property} to a rule outside a layer, which beats every sonic rule; import that stylesheet into a layer declared before sonic`,\n\t\t);\n\t}\n\n\t#checkHost(checked: Set<string>): void {\n\t\tif (!__DEV__) return;\n\n\t\tconst hostBox = readHostBox(this);\n\t\tif (hostBox === undefined) return;\n\n\t\tchecked.add('host');\n\t\tconsole.warn(\n\t\t\thostBox === 'display'\n\t\t\t\t? `<${this.localName}> is given a display, but it has to stay display: contents; lay it out through a parent or a wrapper`\n\t\t\t\t: `<${this.localName}> is display: contents and has no box, so its ${hostBox} does nothing; set it on a parent or a wrapper`,\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\t#writeLabels(target: Element, labels: Array<Element>): void {\n\t\tif (canReferElements) {\n\t\t\twriteLabelledBy(target, labels);\n\t\t\treturn;\n\t\t}\n\n\t\tconst ids = labels.map((label) => SonicElement.#labelId(label));\n\n\t\twriteAttribute(target, 'aria-labelledby', ids.join(' '));\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ const modeNames = /* @__PURE__ */ new Set([
|
|
|
8
8
|
"equal",
|
|
9
9
|
"proportional"
|
|
10
10
|
]);
|
|
11
|
-
const
|
|
11
|
+
const gridTolerance = 1e-9;
|
|
12
12
|
const lockAttributes = ["data-sonic-locked", "disabled"];
|
|
13
13
|
function isMode(name) {
|
|
14
14
|
return name !== null && modeNames.has(name);
|
|
@@ -19,7 +19,7 @@ function isLocked(member) {
|
|
|
19
19
|
function gridLimits(mapping, [low, high]) {
|
|
20
20
|
const bottom = mapping.snap(low);
|
|
21
21
|
const top = mapping.snap(high);
|
|
22
|
-
return [bottom < low -
|
|
22
|
+
return [bottom < low - gridTolerance ? mapping.keyTarget("ArrowRight", bottom) ?? bottom : bottom, top > high + gridTolerance ? mapping.keyTarget("ArrowLeft", top) ?? top : top];
|
|
23
23
|
}
|
|
24
24
|
function modelOf(member, value, isFree) {
|
|
25
25
|
const mapping = member.link.model.mapping();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"split.js","names":[],"sources":["../../../src/elements/split.ts"],"sourcesContent":["import type { ValueLink } from '#elements/value-element.ts';\nimport type { SplitMember, SplitMode } from '#lib/distribute.ts';\nimport type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { linkValue, SonicValueElement } from '#elements/value-element.ts';\nimport { distribute, splitLimits } from '#lib/distribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-split': SonicSplit;\n\t}\n}\n\ninterface Member {\n\telement: SonicValueElement;\n\tlink: ValueLink;\n\tseen: number;\n\tunwatch: () => void;\n}\n\ninterface Snapshot {\n\tmover: Member;\n\tvalues: Array<number>;\n}\n\nconst modeNames = new Set<string>(['cascade', 'equal', 'proportional']);\n\nconst gridSlop = 1e-9;\n\nconst lockAttributes = ['data-sonic-locked', 'disabled'];\n\nfunction isMode(name: null | string): name is SplitMode {\n\treturn name !== null && modeNames.has(name);\n}\n\nfunction isLocked(member: Member): boolean {\n\treturn 'sonicLocked' in member.element.dataset || member.link.isDisabled();\n}\n\nfunction gridLimits(mapping: ValueMapping, [low, high]: [number, number]): [number, number] {\n\tconst bottom = mapping.snap(low);\n\tconst top = mapping.snap(high);\n\n\treturn [\n\t\tbottom < low - gridSlop ? (mapping.keyTarget('ArrowRight', bottom) ?? bottom) : bottom,\n\t\ttop > high + gridSlop ? (mapping.keyTarget('ArrowLeft', top) ?? top) : top,\n\t];\n}\n\nfunction modelOf(member: Member, value: number, isFree: boolean): SplitMember {\n\tconst mapping = member.link.model.mapping();\n\tconst [first, last] = mapping.bounds;\n\n\treturn {\n\t\tisFree,\n\t\tmax: Math.max(first, last),\n\t\tmin: Math.min(first, last),\n\t\tsnap: mapping.snap,\n\t\tstepFrom: (from, direction) =>\n\t\t\tmapping.keyTarget(direction > 0 ? 'ArrowRight' : 'ArrowLeft', from) ?? from,\n\t\tvalue,\n\t};\n}\n\nexport class SonicSplit extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t'mode',\n\t\t'total',\n\t];\n\n\tget mode(): SplitMode {\n\t\tconst name = this.getAttribute('mode');\n\n\t\treturn isMode(name) ? name : 'proportional';\n\t}\n\n\tset mode(name: SplitMode | undefined) {\n\t\tthis.reflect('mode', name);\n\t}\n\n\t/** What the members add up to; unset, it is their sum when they connect */\n\tget total(): number | undefined {\n\t\treturn this.optionalNumberAttribute('total');\n\t}\n\n\tset total(value: number | undefined) {\n\t\tthis.reflect('total', value);\n\t}\n\n\t#isQueued = false;\n\n\t#isTotalKept = false;\n\n\t#isWarned = false;\n\n\t#isWriting = false;\n\n\t#members: Array<Member> = [];\n\n\t#snapshot: Snapshot | undefined;\n\n\t#total = 0;\n\n\treadonly #waiting = new Set<string>();\n\n\tattributeChangedCallback(name: string): void {\n\t\tif ((name === 'mode' || name === 'total') && this.isBound()) this.#bind();\n\t\tif (name === 'disabled' && this.isBound()) this.#limit();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('mode', 'total');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst observer = new MutationObserver((records) => {\n\t\t\tconst isLockChange = records.some(\n\t\t\t\t(record) => record.attributeName !== null && lockAttributes.includes(record.attributeName),\n\t\t\t);\n\n\t\t\tif (isLockChange || !this.#isSameMembers(this.#find())) this.#bind();\n\t\t});\n\n\t\tif (this.internals) this.internals.role = 'group';\n\t\tobserver.observe(this, {\n\t\t\tattributeFilter: lockAttributes,\n\t\t\tattributes: true,\n\t\t\tchildList: true,\n\t\t\tsubtree: true,\n\t\t});\n\t\tthis.addEventListener(\n\t\t\t'change',\n\t\t\t(event) => {\n\t\t\t\tthis.#release(event);\n\t\t\t},\n\t\t\t{ signal },\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\tthis.#isTotalKept = false;\n\t\t\t\tthis.#unbind();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tthis.#bind();\n\t}\n\n\t#bind(): void {\n\t\tconst known = new Set(this.#members.map(({ element }) => element));\n\n\t\tthis.#unbind();\n\t\tthis.#members = this.#find().map((element) => this.#join(element));\n\n\t\tconst values = this.#members.map(({ seen }) => seen);\n\t\tconst sum = values.reduce((total, value) => total + value, 0);\n\n\t\tthis.#total = this.total ?? (this.#isTotalKept ? this.#total : sum);\n\t\tthis.#isTotalKept = this.#members.length > 0;\n\t\tif (this.#members.length > 0 && sum !== this.#total) {\n\t\t\tthis.#spread(values, known);\n\t\t}\n\t\tthis.#limit();\n\t\tthis.#warnReach();\n\t}\n\n\t#compensate(): void {\n\t\tthis.#isQueued = false;\n\t\tfor (const mover of this.#members) {\n\t\t\tif (mover.link.model.value !== mover.seen) this.#move(mover);\n\t\t}\n\t}\n\n\t#find(): Array<SonicValueElement> {\n\t\tconst found: Array<SonicValueElement> = [];\n\n\t\tfor (const element of this.querySelectorAll('*')) {\n\t\t\tif (element.closest('sonic-split') !== this) continue;\n\t\t\tif (element instanceof SonicValueElement) found.push(element);\n\t\t\telse this.#waitFor(element.localName);\n\t\t}\n\n\t\treturn found;\n\t}\n\n\t#isSameMembers(elements: Array<SonicValueElement>): boolean {\n\t\treturn (\n\t\t\telements.length === this.#members.length &&\n\t\t\telements.every((element, index) => this.#members[index]?.element === element)\n\t\t);\n\t}\n\n\t#join(element: SonicValueElement): Member {\n\t\tconst link = linkValue(element);\n\t\tconst unwatch = link.model.watch(() => {\n\t\t\tif (this.#isWriting || this.#isQueued) return;\n\n\t\t\tthis.#isQueued = true;\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tif (this.#isQueued) this.#compensate();\n\t\t\t});\n\t\t});\n\n\t\tlink.model.setLanding((target, direction) => this.#land(element, target, direction));\n\n\t\treturn { element, link, seen: link.model.value, unwatch };\n\t}\n\n\t#land(element: SonicValueElement, target: number, direction: -1 | 0 | 1): number {\n\t\tif (this.#isQueued) this.#compensate();\n\n\t\tconst index = this.#members.findIndex((member) => member.element === element);\n\t\tconst mover = this.#members[index];\n\t\tif (!mover) return target;\n\n\t\tconst values = distribute(\n\t\t\tthis.#model(\n\t\t\t\tthis.#members.map(({ seen }) => seen),\n\t\t\t\tmover,\n\t\t\t),\n\t\t\t{ mode: this.mode, total: this.#total },\n\t\t\t{ direction, index, target },\n\t\t);\n\n\t\treturn values[index] ?? target;\n\t}\n\n\t#limit(): void {\n\t\tconst isFrozen = this.isDisabled();\n\t\tconst values = this.#members.map(({ seen }) => seen);\n\n\t\tfor (const [index, member] of this.#members.entries()) {\n\t\t\tconst model = this.#model(values, member);\n\t\t\tconst value = values[index] ?? member.seen;\n\n\t\t\tmember.link.model.setLimit(\n\t\t\t\tisFrozen || isLocked(member)\n\t\t\t\t\t? [value, value]\n\t\t\t\t\t: gridLimits(member.link.model.mapping(), splitLimits(model, index, this.#total)),\n\t\t\t);\n\t\t}\n\t}\n\n\t#model(values: Array<number>, mover?: Member): Array<SplitMember> {\n\t\treturn this.#members.map((member, index) => {\n\t\t\tconst isFixed = member !== mover && (isLocked(member) || member.link.isHeld());\n\n\t\t\treturn modelOf(member, values[index] ?? member.seen, !isFixed);\n\t\t});\n\t}\n\n\t#move(mover: Member): void {\n\t\tconst index = this.#members.indexOf(mover);\n\t\tconst kept = this.#snapshot;\n\t\tconst snapshot =\n\t\t\tkept?.mover === mover && mover.link.isHeld()\n\t\t\t\t? kept\n\t\t\t\t: { mover, values: this.#members.map(({ seen }) => seen) };\n\n\t\tthis.#snapshot = snapshot;\n\t\tthis.#write(\n\t\t\tdistribute(\n\t\t\t\tthis.#model(snapshot.values, mover),\n\t\t\t\t{ mode: this.mode, total: this.#total },\n\t\t\t\t{ index, target: mover.link.model.value },\n\t\t\t),\n\t\t\tmover,\n\t\t);\n\t\tthis.#limit();\n\t}\n\n\t#release(event: Event): void {\n\t\tif (this.#isWriting) return;\n\t\tif (this.#isQueued) this.#compensate();\n\n\t\tconst snapshot = this.#snapshot;\n\t\tif (snapshot?.mover.element !== event.target) return;\n\n\t\tthis.#snapshot = undefined;\n\n\t\tconst changed = this.#members.filter(\n\t\t\t(member, index) =>\n\t\t\t\tmember !== snapshot.mover && member.link.model.value !== snapshot.values[index],\n\t\t);\n\n\t\tqueueMicrotask(() => {\n\t\t\tthis.#isWriting = true;\n\t\t\ttry {\n\t\t\t\tfor (const { link } of changed) link.change();\n\t\t\t} finally {\n\t\t\t\tthis.#isWriting = false;\n\t\t\t}\n\t\t});\n\t}\n\n\t#spread(values: Array<number>, known: Set<SonicValueElement>): void {\n\t\tconst model = this.#model(values);\n\t\tconst joined = this.#members.map(({ element }) => known.size > 0 && !known.has(element));\n\t\tconst isAnyKnownFree = model.some((member, index) => member.isFree && joined[index] !== true);\n\t\tconst spreadOver = model.map((member, index) =>\n\t\t\tisAnyKnownFree && joined[index] === true ? { ...member, isFree: false } : member,\n\t\t);\n\n\t\tthis.#write(distribute(spreadOver, { mode: this.mode, total: this.#total }));\n\t}\n\n\t#unbind(): void {\n\t\tfor (const { link, unwatch } of this.#members) {\n\t\t\tunwatch();\n\t\t\tlink.model.setLanding(undefined);\n\t\t\tlink.model.setLimit(undefined);\n\t\t}\n\t\tthis.#members = [];\n\t\tthis.#snapshot = undefined;\n\t\tthis.#isQueued = false;\n\t}\n\n\t#waitFor(tag: string): void {\n\t\tif (!tag.includes('-') || customElements.get(tag) || this.#waiting.has(tag)) return;\n\n\t\tthis.#waiting.add(tag);\n\t\tvoid customElements.whenDefined(tag).then(() => {\n\t\t\tthis.#waiting.delete(tag);\n\t\t\tif (this.isBound()) this.#bind();\n\t\t});\n\t}\n\n\t#warnReach(): void {\n\t\tif (!__DEV__) return;\n\t\tif (this.#isWarned || this.#members.length === 0) return;\n\n\t\tconst models = this.#model(this.#members.map(({ seen }) => seen));\n\t\tconst low = models.reduce((total, { min }) => total + min, 0);\n\t\tconst high = models.reduce((total, { max }) => total + max, 0);\n\t\tif (this.#total >= low && this.#total <= high) return;\n\n\t\tthis.#isWarned = true;\n\t\tconsole.warn(\n\t\t\t`<sonic-split> cannot reach its total of ${String(this.#total)}; its members reach from ${String(low)} to ${String(high)}`,\n\t\t);\n\t}\n\n\t#write(values: Array<number>, mover?: Member): void {\n\t\tthis.#isWriting = true;\n\t\ttry {\n\t\t\tfor (const [index, member] of this.#members.entries()) {\n\t\t\t\tconst value = values[index];\n\t\t\t\tif (value === undefined || value === member.link.model.value) continue;\n\n\t\t\t\tmember.link.model.setLimit(undefined);\n\t\t\t\tmember.link.input(value, member === mover);\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.#isWriting = false;\n\t\t}\n\t\tfor (const member of this.#members) member.seen = member.link.model.value;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"split.js","names":[],"sources":["../../../src/elements/split.ts"],"sourcesContent":["import type { ValueLink } from '#elements/value-element.ts';\nimport type { SplitMember, SplitMode } from '#lib/distribute.ts';\nimport type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { linkValue, SonicValueElement } from '#elements/value-element.ts';\nimport { distribute, splitLimits } from '#lib/distribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-split': SonicSplit;\n\t}\n}\n\ninterface Member {\n\telement: SonicValueElement;\n\tlink: ValueLink;\n\tseen: number;\n\tunwatch: () => void;\n}\n\ninterface Snapshot {\n\tmover: Member;\n\tvalues: Array<number>;\n}\n\nconst modeNames = new Set<string>(['cascade', 'equal', 'proportional']);\n\nconst gridTolerance = 1e-9;\n\nconst lockAttributes = ['data-sonic-locked', 'disabled'];\n\nfunction isMode(name: null | string): name is SplitMode {\n\treturn name !== null && modeNames.has(name);\n}\n\nfunction isLocked(member: Member): boolean {\n\treturn 'sonicLocked' in member.element.dataset || member.link.isDisabled();\n}\n\nfunction gridLimits(mapping: ValueMapping, [low, high]: [number, number]): [number, number] {\n\tconst bottom = mapping.snap(low);\n\tconst top = mapping.snap(high);\n\n\treturn [\n\t\tbottom < low - gridTolerance ? (mapping.keyTarget('ArrowRight', bottom) ?? bottom) : bottom,\n\t\ttop > high + gridTolerance ? (mapping.keyTarget('ArrowLeft', top) ?? top) : top,\n\t];\n}\n\nfunction modelOf(member: Member, value: number, isFree: boolean): SplitMember {\n\tconst mapping = member.link.model.mapping();\n\tconst [first, last] = mapping.bounds;\n\n\treturn {\n\t\tisFree,\n\t\tmax: Math.max(first, last),\n\t\tmin: Math.min(first, last),\n\t\tsnap: mapping.snap,\n\t\tstepFrom: (from, direction) =>\n\t\t\tmapping.keyTarget(direction > 0 ? 'ArrowRight' : 'ArrowLeft', from) ?? from,\n\t\tvalue,\n\t};\n}\n\nexport class SonicSplit extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicElement.observedAttributes,\n\t\t'mode',\n\t\t'total',\n\t];\n\n\tget mode(): SplitMode {\n\t\tconst name = this.getAttribute('mode');\n\n\t\treturn isMode(name) ? name : 'proportional';\n\t}\n\n\tset mode(name: SplitMode | undefined) {\n\t\tthis.reflect('mode', name);\n\t}\n\n\t/** What the members add up to; unset, it is their sum when they connect */\n\tget total(): number | undefined {\n\t\treturn this.optionalNumberAttribute('total');\n\t}\n\n\tset total(value: number | undefined) {\n\t\tthis.reflect('total', value);\n\t}\n\n\t#isQueued = false;\n\n\t#isTotalKept = false;\n\n\t#isWarned = false;\n\n\t#isWriting = false;\n\n\t#members: Array<Member> = [];\n\n\t#snapshot: Snapshot | undefined;\n\n\t#total = 0;\n\n\treadonly #waiting = new Set<string>();\n\n\tattributeChangedCallback(name: string): void {\n\t\tif ((name === 'mode' || name === 'total') && this.isBound()) this.#bind();\n\t\tif (name === 'disabled' && this.isBound()) this.#limit();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('mode', 'total');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst observer = new MutationObserver((records) => {\n\t\t\tconst isLockChange = records.some(\n\t\t\t\t(record) => record.attributeName !== null && lockAttributes.includes(record.attributeName),\n\t\t\t);\n\n\t\t\tif (isLockChange || !this.#isSameMembers(this.#find())) this.#bind();\n\t\t});\n\n\t\tif (this.internals) this.internals.role = 'group';\n\t\tobserver.observe(this, {\n\t\t\tattributeFilter: lockAttributes,\n\t\t\tattributes: true,\n\t\t\tchildList: true,\n\t\t\tsubtree: true,\n\t\t});\n\t\tthis.addEventListener(\n\t\t\t'change',\n\t\t\t(event) => {\n\t\t\t\tthis.#release(event);\n\t\t\t},\n\t\t\t{ signal },\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\tthis.#isTotalKept = false;\n\t\t\t\tthis.#unbind();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tthis.#bind();\n\t}\n\n\t#bind(): void {\n\t\tconst known = new Set(this.#members.map(({ element }) => element));\n\n\t\tthis.#unbind();\n\t\tthis.#members = this.#find().map((element) => this.#join(element));\n\n\t\tconst values = this.#members.map(({ seen }) => seen);\n\t\tconst sum = values.reduce((total, value) => total + value, 0);\n\n\t\tthis.#total = this.total ?? (this.#isTotalKept ? this.#total : sum);\n\t\tthis.#isTotalKept = this.#members.length > 0;\n\t\tif (this.#members.length > 0 && sum !== this.#total) {\n\t\t\tthis.#spread(values, known);\n\t\t}\n\t\tthis.#limit();\n\t\tthis.#warnReach();\n\t}\n\n\t#compensate(): void {\n\t\tthis.#isQueued = false;\n\t\tfor (const mover of this.#members) {\n\t\t\tif (mover.link.model.value !== mover.seen) this.#move(mover);\n\t\t}\n\t}\n\n\t#find(): Array<SonicValueElement> {\n\t\tconst found: Array<SonicValueElement> = [];\n\n\t\tfor (const element of this.querySelectorAll('*')) {\n\t\t\tif (element.closest('sonic-split') !== this) continue;\n\t\t\tif (element instanceof SonicValueElement) found.push(element);\n\t\t\telse this.#waitFor(element.localName);\n\t\t}\n\n\t\treturn found;\n\t}\n\n\t#isSameMembers(elements: Array<SonicValueElement>): boolean {\n\t\treturn (\n\t\t\telements.length === this.#members.length &&\n\t\t\telements.every((element, index) => this.#members[index]?.element === element)\n\t\t);\n\t}\n\n\t#join(element: SonicValueElement): Member {\n\t\tconst link = linkValue(element);\n\t\tconst unwatch = link.model.watch(() => {\n\t\t\tif (this.#isWriting || this.#isQueued) return;\n\n\t\t\tthis.#isQueued = true;\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tif (this.#isQueued) this.#compensate();\n\t\t\t});\n\t\t});\n\n\t\tlink.model.setLanding((target, direction) => this.#land(element, target, direction));\n\n\t\treturn { element, link, seen: link.model.value, unwatch };\n\t}\n\n\t#land(element: SonicValueElement, target: number, direction: -1 | 0 | 1): number {\n\t\tif (this.#isQueued) this.#compensate();\n\n\t\tconst index = this.#members.findIndex((member) => member.element === element);\n\t\tconst mover = this.#members[index];\n\t\tif (!mover) return target;\n\n\t\tconst values = distribute(\n\t\t\tthis.#model(\n\t\t\t\tthis.#members.map(({ seen }) => seen),\n\t\t\t\tmover,\n\t\t\t),\n\t\t\t{ mode: this.mode, total: this.#total },\n\t\t\t{ direction, index, target },\n\t\t);\n\n\t\treturn values[index] ?? target;\n\t}\n\n\t#limit(): void {\n\t\tconst isFrozen = this.isDisabled();\n\t\tconst values = this.#members.map(({ seen }) => seen);\n\n\t\tfor (const [index, member] of this.#members.entries()) {\n\t\t\tconst model = this.#model(values, member);\n\t\t\tconst value = values[index] ?? member.seen;\n\n\t\t\tmember.link.model.setLimit(\n\t\t\t\tisFrozen || isLocked(member)\n\t\t\t\t\t? [value, value]\n\t\t\t\t\t: gridLimits(member.link.model.mapping(), splitLimits(model, index, this.#total)),\n\t\t\t);\n\t\t}\n\t}\n\n\t#model(values: Array<number>, mover?: Member): Array<SplitMember> {\n\t\treturn this.#members.map((member, index) => {\n\t\t\tconst isFixed = member !== mover && (isLocked(member) || member.link.isHeld());\n\n\t\t\treturn modelOf(member, values[index] ?? member.seen, !isFixed);\n\t\t});\n\t}\n\n\t#move(mover: Member): void {\n\t\tconst index = this.#members.indexOf(mover);\n\t\tconst kept = this.#snapshot;\n\t\tconst snapshot =\n\t\t\tkept?.mover === mover && mover.link.isHeld()\n\t\t\t\t? kept\n\t\t\t\t: { mover, values: this.#members.map(({ seen }) => seen) };\n\n\t\tthis.#snapshot = snapshot;\n\t\tthis.#write(\n\t\t\tdistribute(\n\t\t\t\tthis.#model(snapshot.values, mover),\n\t\t\t\t{ mode: this.mode, total: this.#total },\n\t\t\t\t{ index, target: mover.link.model.value },\n\t\t\t),\n\t\t\tmover,\n\t\t);\n\t\tthis.#limit();\n\t}\n\n\t#release(event: Event): void {\n\t\tif (this.#isWriting) return;\n\t\tif (this.#isQueued) this.#compensate();\n\n\t\tconst snapshot = this.#snapshot;\n\t\tif (snapshot?.mover.element !== event.target) return;\n\n\t\tthis.#snapshot = undefined;\n\n\t\tconst changed = this.#members.filter(\n\t\t\t(member, index) =>\n\t\t\t\tmember !== snapshot.mover && member.link.model.value !== snapshot.values[index],\n\t\t);\n\n\t\tqueueMicrotask(() => {\n\t\t\tthis.#isWriting = true;\n\t\t\ttry {\n\t\t\t\tfor (const { link } of changed) link.change();\n\t\t\t} finally {\n\t\t\t\tthis.#isWriting = false;\n\t\t\t}\n\t\t});\n\t}\n\n\t#spread(values: Array<number>, known: Set<SonicValueElement>): void {\n\t\tconst model = this.#model(values);\n\t\tconst joined = this.#members.map(({ element }) => known.size > 0 && !known.has(element));\n\t\tconst isAnyKnownFree = model.some((member, index) => member.isFree && joined[index] !== true);\n\t\tconst spreadOver = model.map((member, index) =>\n\t\t\tisAnyKnownFree && joined[index] === true ? { ...member, isFree: false } : member,\n\t\t);\n\n\t\tthis.#write(distribute(spreadOver, { mode: this.mode, total: this.#total }));\n\t}\n\n\t#unbind(): void {\n\t\tfor (const { link, unwatch } of this.#members) {\n\t\t\tunwatch();\n\t\t\tlink.model.setLanding(undefined);\n\t\t\tlink.model.setLimit(undefined);\n\t\t}\n\t\tthis.#members = [];\n\t\tthis.#snapshot = undefined;\n\t\tthis.#isQueued = false;\n\t}\n\n\t#waitFor(tag: string): void {\n\t\tif (!tag.includes('-') || customElements.get(tag) || this.#waiting.has(tag)) return;\n\n\t\tthis.#waiting.add(tag);\n\t\tvoid customElements.whenDefined(tag).then(() => {\n\t\t\tthis.#waiting.delete(tag);\n\t\t\tif (this.isBound()) this.#bind();\n\t\t});\n\t}\n\n\t#warnReach(): void {\n\t\tif (!__DEV__) return;\n\t\tif (this.#isWarned || this.#members.length === 0) return;\n\n\t\tconst models = this.#model(this.#members.map(({ seen }) => seen));\n\t\tconst low = models.reduce((total, { min }) => total + min, 0);\n\t\tconst high = models.reduce((total, { max }) => total + max, 0);\n\t\tif (this.#total >= low && this.#total <= high) return;\n\n\t\tthis.#isWarned = true;\n\t\tconsole.warn(\n\t\t\t`<sonic-split> cannot reach its total of ${String(this.#total)}; its members reach from ${String(low)} to ${String(high)}`,\n\t\t);\n\t}\n\n\t#write(values: Array<number>, mover?: Member): void {\n\t\tthis.#isWriting = true;\n\t\ttry {\n\t\t\tfor (const [index, member] of this.#members.entries()) {\n\t\t\t\tconst value = values[index];\n\t\t\t\tif (value === undefined || value === member.link.model.value) continue;\n\n\t\t\t\tmember.link.model.setLimit(undefined);\n\t\t\t\tmember.link.input(value, member === mover);\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.#isWriting = false;\n\t\t}\n\t\tfor (const member of this.#members) member.seen = member.link.model.value;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -19,7 +19,7 @@ const renderPosition = template(`
|
|
|
19
19
|
</button>
|
|
20
20
|
`, HTMLButtonElement);
|
|
21
21
|
const renderBare = template(`<button class="sonic-toggle-position" role="switch" type="button"></button>`, HTMLButtonElement);
|
|
22
|
-
const
|
|
22
|
+
const pressTolerance = 2;
|
|
23
23
|
var SonicToggle = class extends SonicPositionGroupElement {
|
|
24
24
|
bare = renderBare();
|
|
25
25
|
defaultOrientation = "horizontal";
|
|
@@ -78,7 +78,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
|
|
|
78
78
|
if (!drag.hasMoved && moved < drag.thresholdPx) return;
|
|
79
79
|
drag.hasMoved = true;
|
|
80
80
|
this.toggleState("dragging", true);
|
|
81
|
-
if (moved >=
|
|
81
|
+
if (moved >= pressTolerance * drag.thresholdPx) drag.isPress = false;
|
|
82
82
|
if (this.#slideTo(positionAt(drag.travel, pointer), event.pointerId)) drag.isPress = false;
|
|
83
83
|
},
|
|
84
84
|
release: (drag) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toggle.js","names":[],"sources":["../../../src/elements/toggle.ts"],"sourcesContent":["import type { ToggleTravel } from '#lib/toggle-travel.ts';\n\nimport { SonicPositionGroupElement } from '#elements/position-group.ts';\nimport { isUnder } from '#elements/radio-group.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { enabledEnds } from '#lib/positions.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { positionAt, travelFrom } from '#lib/toggle-travel.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-toggle': SonicToggle;\n\t}\n}\n\n// fallow-ignore-next-line code-duplication -- each control draws its own parts\nconst renderToggle = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-toggle\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-toggle-well\"></span>\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-toggle-cap\"></span>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst renderPosition = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-toggle-position\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-toggle-legend\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst renderBare = template(\n\t/* HTML */ `<button class=\"sonic-toggle-position\" role=\"switch\" type=\"button\"></button>`,\n\tHTMLButtonElement,\n);\n\nconst pressSlop = 2;\n\ninterface CapDrag {\n\thasMoved: boolean;\n\tisHolding: boolean;\n\tisPress: boolean;\n\tmarked: HTMLButtonElement;\n\tposition: number;\n\tstart: number;\n\tthresholdPx: number;\n\ttravel: ToggleTravel;\n}\n\nexport class SonicToggle extends SonicPositionGroupElement {\n\tprotected readonly bare = renderBare();\n\n\tprotected readonly defaultOrientation = 'horizontal';\n\n\tprotected readonly group = renderToggle();\n\n\treadonly #cap = requireChild(this.group, '.sonic-toggle-cap', HTMLSpanElement);\n\n\t#draggedTo: number | undefined;\n\n\t#hasWarnedOfLegends = false;\n\n\treadonly #well = requireChild(this.group, '.sonic-toggle-well', HTMLSpanElement);\n\n\t// Every press is the toggle's own, so the base's slide-off never starts\n\tprotected override claimPress(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(this.group, 'toggle.css', {\n\t\t\t\tproperty: 'margin-top',\n\t\t\t\tratio: '--_sonic-toggle-gap-ratio',\n\t\t\t\tselector: '.sonic-toggle-cap',\n\t\t\t});\n\t\tthis.bare.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A pointer's press is taken on its release, before the click it sends\n\t\t\t\tif (event instanceof PointerEvent && event.pointerType !== '') return;\n\n\t\t\t\tthis.setChecked(!this.checked);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindCap(signal);\n\t}\n\n\tprotected override isMirrored(child: Node): boolean {\n\t\treturn super.isMirrored(child) || child === this.#fixedLegend();\n\t}\n\n\tprotected override placeCopies(copies: Array<Node>): void {\n\t\tconst positions = copies.filter((copy) => super.isMirrored(copy));\n\n\t\tplaceChildren(\n\t\t\tthis.#cap,\n\t\t\tcopies.filter((copy) => !positions.includes(copy)),\n\t\t);\n\t\tsuper.placeCopies(positions);\n\t}\n\n\tprotected override render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tif (__DEV__) this.#checkLegends();\n\t\tthis.group.style.setProperty('--_sonic-toggle-count', String(this.positions().length));\n\t\tthis.#placeCap(this.#draggedTo ?? this.#restingAt());\n\t\tsuper.render();\n\t\tfor (const [index, option] of this.options().entries()) {\n\t\t\toption.style.setProperty('--_sonic-toggle-index', String(index));\n\t\t}\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderPosition();\n\t}\n\n\t#bindCap(signal: AbortSignal): void {\n\t\tbindDrag<CapDrag>(\n\t\t\tthis.group,\n\t\t\t{\n\t\t\t\tgrab: (event) => this.#grab(event),\n\t\t\t\tlift: (drag, event) => {\n\t\t\t\t\tif (!drag.isPress || drag.isHolding || this.isDisabled()) return;\n\t\t\t\t\tif (!isUnder(this.group, event.clientX, event.clientY)) return;\n\n\t\t\t\t\tthis.#press(drag.position);\n\t\t\t\t},\n\t\t\t\tmove: (drag, event) => {\n\t\t\t\t\t// A held finger wobbles, and a drag back across halfway would let go\n\t\t\t\t\tif (drag.isHolding || this.isDisabled()) return;\n\t\t\t\t\tconst pointer = this.along(event);\n\t\t\t\t\tconst moved = Math.abs(pointer - drag.start);\n\t\t\t\t\tif (!drag.hasMoved && moved < drag.thresholdPx) return;\n\n\t\t\t\t\tdrag.hasMoved = true;\n\t\t\t\t\tthis.toggleState('dragging', true);\n\t\t\t\t\tif (moved >= pressSlop * drag.thresholdPx) drag.isPress = false;\n\t\t\t\t\tif (this.#slideTo(positionAt(drag.travel, pointer), event.pointerId))\n\t\t\t\t\t\tdrag.isPress = false;\n\t\t\t\t},\n\t\t\t\trelease: (drag) => {\n\t\t\t\t\tdelete drag.marked.dataset.sonicPressed;\n\t\t\t\t\tthis.#draggedTo = undefined;\n\t\t\t\t\tthis.toggleState('dragging', false);\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#checkLegends(): void {\n\t\tif (!__DEV__ || this.#hasWarnedOfLegends) return;\n\n\t\tconst count = this.options().length;\n\t\tif (count <= 3 || this.getAttribute('legends') !== 'outside') return;\n\n\t\tthis.#hasWarnedOfLegends = true;\n\t\tconsole.warn(\n\t\t\t`<sonic-toggle> lays out legends=\"outside\" for two or three positions, and this one has ${String(count)}`,\n\t\t);\n\t}\n\n\t#fixedLegend(): Element | undefined {\n\t\treturn [...this.children].find((child) => child !== this.group && !super.isMirrored(child));\n\t}\n\n\t#grab(event: PointerEvent): CapDrag | undefined {\n\t\tconst position = this.pressedIndex(event.target);\n\t\tif (this.isDisabled() || position < 0) return undefined;\n\n\t\tconst isOnCap = isUnder(this.#cap, event.clientX, event.clientY);\n\t\tconst travel = this.#travel(event, isOnCap);\n\t\tif (!travel || (!isOnCap && this.isOptionDisabled(position))) return undefined;\n\n\t\tconst marked = this.options()[position] ?? this.bare;\n\n\t\tmarked.toggleAttribute('data-sonic-pressed', true);\n\n\t\treturn {\n\t\t\thasMoved: false,\n\t\t\tisHolding: this.#holdFrom(position, event.pointerId),\n\t\t\tisPress: true,\n\t\t\tmarked,\n\t\t\tposition,\n\t\t\tstart: this.along(event),\n\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\ttravel,\n\t\t};\n\t}\n\n\t#holdFrom(position: number, pointerId: number): boolean {\n\t\tconst target = this.pressTarget(position);\n\t\tif (!this.isMomentary(target) || this.isOptionDisabled(target)) return false;\n\n\t\tthis.hold(target, pointerId);\n\n\t\treturn true;\n\t}\n\n\t#placeCap(at: number): void {\n\t\tthis.group.style.setProperty('--_sonic-toggle-at', String(at));\n\t}\n\n\t#press(position: number): void {\n\t\tconst target = this.pressTarget(position);\n\t\tif (this.isOptionDisabled(target)) return;\n\n\t\tthis.select(target);\n\t\tthis.refocusAt(target);\n\t}\n\n\t#restingAt(): number {\n\t\treturn Math.max(0, this.checkedIndex());\n\t}\n\n\t#slideTo(at: number, pointerId: number): boolean {\n\t\tconst crossed = Math.round(at);\n\n\t\tthis.#draggedTo = at;\n\t\tthis.#placeCap(at);\n\t\tif (this.isChecked(crossed)) return false;\n\n\t\tthis.releaseHold();\n\t\tif (this.isOptionDisabled(crossed) || this.isChecked(crossed)) return false;\n\n\t\tif (this.isMomentary(crossed)) this.hold(crossed, pointerId);\n\t\telse this.select(crossed);\n\t\tthis.refocusAt(crossed);\n\n\t\treturn true;\n\t}\n\n\t#travel(event: PointerEvent, isOnCap: boolean): ToggleTravel | undefined {\n\t\tconst [first, last] = enabledEnds(this.positions());\n\t\tif (first < 0) return undefined;\n\n\t\tconst box = this.#well.getBoundingClientRect();\n\t\tconst [start, length, thickness] =\n\t\t\tthis.orientation === 'vertical'\n\t\t\t\t? [box.top, box.height, box.width]\n\t\t\t\t: [box.left, box.width, box.height];\n\n\t\treturn travelFrom({\n\t\t\tat: this.#restingAt(),\n\t\t\tcount: this.positions().length,\n\t\t\tfirst,\n\t\t\tisOnCap,\n\t\t\tlast,\n\t\t\tpointer: this.along(event),\n\t\t\twell: { length, start, thickness },\n\t\t});\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toggle.js","names":[],"sources":["../../../src/elements/toggle.ts"],"sourcesContent":["import type { ToggleTravel } from '#lib/toggle-travel.ts';\n\nimport { SonicPositionGroupElement } from '#elements/position-group.ts';\nimport { isUnder } from '#elements/radio-group.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { enabledEnds } from '#lib/positions.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { positionAt, travelFrom } from '#lib/toggle-travel.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-toggle': SonicToggle;\n\t}\n}\n\n// fallow-ignore-next-line code-duplication -- each control draws its own parts\nconst renderToggle = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-toggle\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-toggle-well\"></span>\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-toggle-cap\"></span>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst renderPosition = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-toggle-position\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-toggle-legend\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst renderBare = template(\n\t/* HTML */ `<button class=\"sonic-toggle-position\" role=\"switch\" type=\"button\"></button>`,\n\tHTMLButtonElement,\n);\n\nconst pressTolerance = 2;\n\ninterface CapDrag {\n\thasMoved: boolean;\n\tisHolding: boolean;\n\tisPress: boolean;\n\tmarked: HTMLButtonElement;\n\tposition: number;\n\tstart: number;\n\tthresholdPx: number;\n\ttravel: ToggleTravel;\n}\n\nexport class SonicToggle extends SonicPositionGroupElement {\n\tprotected readonly bare = renderBare();\n\n\tprotected readonly defaultOrientation = 'horizontal';\n\n\tprotected readonly group = renderToggle();\n\n\treadonly #cap = requireChild(this.group, '.sonic-toggle-cap', HTMLSpanElement);\n\n\t#draggedTo: number | undefined;\n\n\t#hasWarnedOfLegends = false;\n\n\treadonly #well = requireChild(this.group, '.sonic-toggle-well', HTMLSpanElement);\n\n\t// Every press is the toggle's own, so the base's slide-off never starts\n\tprotected override claimPress(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(this.group, 'toggle.css', {\n\t\t\t\tproperty: 'margin-top',\n\t\t\t\tratio: '--_sonic-toggle-gap-ratio',\n\t\t\t\tselector: '.sonic-toggle-cap',\n\t\t\t});\n\t\tthis.bare.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A pointer's press is taken on its release, before the click it sends\n\t\t\t\tif (event instanceof PointerEvent && event.pointerType !== '') return;\n\n\t\t\t\tthis.setChecked(!this.checked);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindCap(signal);\n\t}\n\n\tprotected override isMirrored(child: Node): boolean {\n\t\treturn super.isMirrored(child) || child === this.#fixedLegend();\n\t}\n\n\tprotected override placeCopies(copies: Array<Node>): void {\n\t\tconst positions = copies.filter((copy) => super.isMirrored(copy));\n\n\t\tplaceChildren(\n\t\t\tthis.#cap,\n\t\t\tcopies.filter((copy) => !positions.includes(copy)),\n\t\t);\n\t\tsuper.placeCopies(positions);\n\t}\n\n\tprotected override render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tif (__DEV__) this.#checkLegends();\n\t\tthis.group.style.setProperty('--_sonic-toggle-count', String(this.positions().length));\n\t\tthis.#placeCap(this.#draggedTo ?? this.#restingAt());\n\t\tsuper.render();\n\t\tfor (const [index, option] of this.options().entries()) {\n\t\t\toption.style.setProperty('--_sonic-toggle-index', String(index));\n\t\t}\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderPosition();\n\t}\n\n\t#bindCap(signal: AbortSignal): void {\n\t\tbindDrag<CapDrag>(\n\t\t\tthis.group,\n\t\t\t{\n\t\t\t\tgrab: (event) => this.#grab(event),\n\t\t\t\tlift: (drag, event) => {\n\t\t\t\t\tif (!drag.isPress || drag.isHolding || this.isDisabled()) return;\n\t\t\t\t\tif (!isUnder(this.group, event.clientX, event.clientY)) return;\n\n\t\t\t\t\tthis.#press(drag.position);\n\t\t\t\t},\n\t\t\t\tmove: (drag, event) => {\n\t\t\t\t\t// A held finger wobbles, and a drag back across halfway would let go\n\t\t\t\t\tif (drag.isHolding || this.isDisabled()) return;\n\t\t\t\t\tconst pointer = this.along(event);\n\t\t\t\t\tconst moved = Math.abs(pointer - drag.start);\n\t\t\t\t\tif (!drag.hasMoved && moved < drag.thresholdPx) return;\n\n\t\t\t\t\tdrag.hasMoved = true;\n\t\t\t\t\tthis.toggleState('dragging', true);\n\t\t\t\t\tif (moved >= pressTolerance * drag.thresholdPx) drag.isPress = false;\n\t\t\t\t\tif (this.#slideTo(positionAt(drag.travel, pointer), event.pointerId))\n\t\t\t\t\t\tdrag.isPress = false;\n\t\t\t\t},\n\t\t\t\trelease: (drag) => {\n\t\t\t\t\tdelete drag.marked.dataset.sonicPressed;\n\t\t\t\t\tthis.#draggedTo = undefined;\n\t\t\t\t\tthis.toggleState('dragging', false);\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#checkLegends(): void {\n\t\tif (!__DEV__ || this.#hasWarnedOfLegends) return;\n\n\t\tconst count = this.options().length;\n\t\tif (count <= 3 || this.getAttribute('legends') !== 'outside') return;\n\n\t\tthis.#hasWarnedOfLegends = true;\n\t\tconsole.warn(\n\t\t\t`<sonic-toggle> lays out legends=\"outside\" for two or three positions, and this one has ${String(count)}`,\n\t\t);\n\t}\n\n\t#fixedLegend(): Element | undefined {\n\t\treturn [...this.children].find((child) => child !== this.group && !super.isMirrored(child));\n\t}\n\n\t#grab(event: PointerEvent): CapDrag | undefined {\n\t\tconst position = this.pressedIndex(event.target);\n\t\tif (this.isDisabled() || position < 0) return undefined;\n\n\t\tconst isOnCap = isUnder(this.#cap, event.clientX, event.clientY);\n\t\tconst travel = this.#travel(event, isOnCap);\n\t\tif (!travel || (!isOnCap && this.isOptionDisabled(position))) return undefined;\n\n\t\tconst marked = this.options()[position] ?? this.bare;\n\n\t\tmarked.toggleAttribute('data-sonic-pressed', true);\n\n\t\treturn {\n\t\t\thasMoved: false,\n\t\t\tisHolding: this.#holdFrom(position, event.pointerId),\n\t\t\tisPress: true,\n\t\t\tmarked,\n\t\t\tposition,\n\t\t\tstart: this.along(event),\n\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\ttravel,\n\t\t};\n\t}\n\n\t#holdFrom(position: number, pointerId: number): boolean {\n\t\tconst target = this.pressTarget(position);\n\t\tif (!this.isMomentary(target) || this.isOptionDisabled(target)) return false;\n\n\t\tthis.hold(target, pointerId);\n\n\t\treturn true;\n\t}\n\n\t#placeCap(at: number): void {\n\t\tthis.group.style.setProperty('--_sonic-toggle-at', String(at));\n\t}\n\n\t#press(position: number): void {\n\t\tconst target = this.pressTarget(position);\n\t\tif (this.isOptionDisabled(target)) return;\n\n\t\tthis.select(target);\n\t\tthis.refocusAt(target);\n\t}\n\n\t#restingAt(): number {\n\t\treturn Math.max(0, this.checkedIndex());\n\t}\n\n\t#slideTo(at: number, pointerId: number): boolean {\n\t\tconst crossed = Math.round(at);\n\n\t\tthis.#draggedTo = at;\n\t\tthis.#placeCap(at);\n\t\tif (this.isChecked(crossed)) return false;\n\n\t\tthis.releaseHold();\n\t\tif (this.isOptionDisabled(crossed) || this.isChecked(crossed)) return false;\n\n\t\tif (this.isMomentary(crossed)) this.hold(crossed, pointerId);\n\t\telse this.select(crossed);\n\t\tthis.refocusAt(crossed);\n\n\t\treturn true;\n\t}\n\n\t#travel(event: PointerEvent, isOnCap: boolean): ToggleTravel | undefined {\n\t\tconst [first, last] = enabledEnds(this.positions());\n\t\tif (first < 0) return undefined;\n\n\t\tconst box = this.#well.getBoundingClientRect();\n\t\tconst [start, length, thickness] =\n\t\t\tthis.orientation === 'vertical'\n\t\t\t\t? [box.top, box.height, box.width]\n\t\t\t\t: [box.left, box.width, box.height];\n\n\t\treturn travelFrom({\n\t\t\tat: this.#restingAt(),\n\t\t\tcount: this.positions().length,\n\t\t\tfirst,\n\t\t\tisOnCap,\n\t\t\tlast,\n\t\t\tpointer: this.along(event),\n\t\t\twell: { length, start, thickness },\n\t\t});\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -11,6 +11,7 @@ declare global {
|
|
|
11
11
|
}
|
|
12
12
|
interface HoverBinding {
|
|
13
13
|
canShow?: () => boolean;
|
|
14
|
+
changed?: () => void;
|
|
14
15
|
place: () => void;
|
|
15
16
|
valueAt: (event: PointerEvent) => number | undefined;
|
|
16
17
|
}
|
|
@@ -77,7 +78,7 @@ export declare abstract class SonicValueElement extends SonicFormElement {
|
|
|
77
78
|
connectedCallback(): void;
|
|
78
79
|
protected abandonHold(): void;
|
|
79
80
|
protected bindGestures(control: HTMLElement, signal: AbortSignal, grab: (event: PointerEvent) => undefined | ValueAxis): void;
|
|
80
|
-
protected bindHover(control: HTMLElement, signal: AbortSignal, { canShow, place, valueAt }: HoverBinding): void;
|
|
81
|
+
protected bindHover(control: HTMLElement, signal: AbortSignal, { canShow, changed, place, valueAt }: HoverBinding): void;
|
|
81
82
|
protected controlOrientation(): 'horizontal' | 'vertical' | undefined;
|
|
82
83
|
protected controlRole(): 'slider' | 'spinbutton';
|
|
83
84
|
protected abstract draw(): void;
|
|
@@ -94,6 +95,7 @@ export declare abstract class SonicValueElement extends SonicFormElement {
|
|
|
94
95
|
protected scrubChanged(): void;
|
|
95
96
|
protected scrubsKeyRepeat(): boolean;
|
|
96
97
|
protected scrubState(): ScrubState;
|
|
98
|
+
protected spokenText(value: number): string | undefined;
|
|
97
99
|
protected springTarget(): number | undefined;
|
|
98
100
|
protected tapTarget(): number | undefined;
|
|
99
101
|
protected travelPx(control: HTMLElement, property: `--_sonic-${string}`): number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { toNumber, trimFloat } from "../lib/math.js";
|
|
2
2
|
import { writeAttribute } from "../lib/write-attribute.js";
|
|
3
3
|
import { SonicFormElement } from "./form-element.js";
|
|
4
|
-
import { Readout } from "./readout.js";
|
|
4
|
+
import { Readout, isUnanchored } from "./readout.js";
|
|
5
5
|
import { readPxProperty } from "../lib/read-px-property.js";
|
|
6
6
|
import { parseNumberList } from "../lib/number-list.js";
|
|
7
7
|
import { ValueGestures } from "./value-gestures.js";
|
|
@@ -268,7 +268,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
268
268
|
forwardNaming: (target, isNamed) => {
|
|
269
269
|
this.forwardNaming(target, isNamed);
|
|
270
270
|
},
|
|
271
|
-
hasEntry: () => this.entry !== "none",
|
|
271
|
+
hasEntry: () => !isUnanchored && this.entry !== "none",
|
|
272
272
|
input: (next) => this.input(next),
|
|
273
273
|
isDisabled: () => this.isDisabled(),
|
|
274
274
|
keyStep: () => this.keyStep,
|
|
@@ -286,16 +286,17 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
286
286
|
toggleState: (state, isOn) => {
|
|
287
287
|
this.toggleState(state, isOn);
|
|
288
288
|
}
|
|
289
|
-
}, control);
|
|
289
|
+
}, control, this.focusTarget());
|
|
290
290
|
this.#gestures.bind(signal, grab);
|
|
291
291
|
control.addEventListener("pointerup", (event) => {
|
|
292
292
|
this.#hover?.lift(event);
|
|
293
293
|
}, { signal });
|
|
294
294
|
}
|
|
295
|
-
bindHover(control, signal, { canShow = () => true, place, valueAt }) {
|
|
295
|
+
bindHover(control, signal, { canShow = () => true, changed, place, valueAt }) {
|
|
296
296
|
this.#hover = bindHoverPreview(control, {
|
|
297
297
|
canShow,
|
|
298
298
|
changed: () => {
|
|
299
|
+
changed?.();
|
|
299
300
|
this.dispatchEvent(new Event("sonic-hover", { bubbles: true }));
|
|
300
301
|
},
|
|
301
302
|
dismiss: () => {
|
|
@@ -367,6 +368,9 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
367
368
|
played: from === void 0 ? this.#model.value : this.#heldAt ?? from
|
|
368
369
|
};
|
|
369
370
|
}
|
|
371
|
+
spokenText(value) {
|
|
372
|
+
return (this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak)?.(value);
|
|
373
|
+
}
|
|
370
374
|
springTarget() {}
|
|
371
375
|
tapTarget() {}
|
|
372
376
|
travelPx(control, property) {
|
|
@@ -403,7 +407,7 @@ var SonicValueElement = class extends SonicFormElement {
|
|
|
403
407
|
writeAttribute(control, "aria-valuemax", String(high));
|
|
404
408
|
writeAttribute(control, "aria-valuenow", String(this.#spokenNow()));
|
|
405
409
|
if (orientation) writeAttribute(control, "aria-orientation", orientation);
|
|
406
|
-
writeAttribute(control, "aria-valuetext",
|
|
410
|
+
writeAttribute(control, "aria-valuetext", this.spokenText(this.#spokenNow()));
|
|
407
411
|
this.forwardNaming(control, true);
|
|
408
412
|
this.#renderDisabled(control);
|
|
409
413
|
this.renderReadout();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"value-element.js","names":[],"sources":["../../../src/elements/value-element.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { HoverPreview } from '#lib/hover-preview.ts';\nimport type { ScrubState } from '#lib/scrub.ts';\nimport type { ValueMapping, ValueSpec } from '#lib/value-mapping.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { Readout } from '#elements/readout.ts';\nimport { ValueGestures } from '#elements/value-gestures.ts';\nimport { bindHoverPreview } from '#lib/hover-preview.ts';\nimport { toNumber, trimFloat } from '#lib/math.ts';\nimport { parseNumberList } from '#lib/number-list.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { createValueModel } from '#lib/value-model.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-hover': Event;\n\t\t'sonic-reveal': Event;\n\t}\n}\n\ninterface HoverBinding {\n\tcanShow?: () => boolean;\n\tplace: () => void;\n\tvalueAt: (event: PointerEvent) => number | undefined;\n}\n\nexport interface ValueNotation {\n\tformat: (value: number) => string;\n\tparse: (text: string) => number;\n\tspeak?: (value: number) => string;\n}\n\nconst fallbackTravelPx = 160;\n\nlet instanceCount = 0;\n\nconst proportionAttributes = new Set(['max', 'midpoint', 'min', 'notched', 'positions', 'taper']);\n\nconst roleAttributes = [\n\t'aria-disabled',\n\t'aria-orientation',\n\t'aria-valuemax',\n\t'aria-valuemin',\n\t'aria-valuenow',\n\t'aria-valuetext',\n\t'role',\n\t'tabindex',\n];\n\nexport interface ValueLink {\n\tchange: () => void;\n\tinput: (next: number, isMover?: boolean) => boolean;\n\tisDisabled: () => boolean;\n\tisHeld: () => boolean;\n\tmodel: ValueModel;\n}\n\n// Filled from inside the class, so it reaches protected members and nothing lands on the element's public type\nlet link: (element: SonicValueElement) => ValueLink;\n\nexport function linkValue(element: SonicValueElement): ValueLink {\n\treturn link(element);\n}\n\nexport abstract class SonicValueElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'detent',\n\t\t'max',\n\t\t'midpoint',\n\t\t'min',\n\t\t'notched',\n\t\t'origin',\n\t\t'readout',\n\t\t'spoken-step',\n\t\t'step',\n\t\t'tabindex',\n\t\t'taper',\n\t\t'value',\n\t\t'positions',\n\t];\n\n\tstatic {\n\t\tlink = (element) => ({\n\t\t\tchange: () => {\n\t\t\t\telement.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\tinput: (next, isMover = false) => (isMover || !element.#isHeld()) && element.input(next),\n\t\t\tisDisabled: () => element.isDisabled(),\n\t\t\tisHeld: () => element.#isHeld(),\n\t\t\tmodel: element.#model,\n\t\t});\n\t}\n\n\tget cancelling(): boolean {\n\t\treturn this.hasState('cancelling');\n\t}\n\n\tget default(): number | undefined {\n\t\treturn this.optionalNumberAttribute('default');\n\t}\n\n\tset default(value: number | undefined) {\n\t\tthis.reflect('default', value);\n\t}\n\n\tget detent(): number | undefined {\n\t\treturn this.optionalNumberAttribute('detent');\n\t}\n\n\tset detent(value: number | undefined) {\n\t\tthis.reflect('detent', value);\n\t}\n\n\tget dimmed(): boolean {\n\t\treturn this.hasAttribute('dimmed');\n\t}\n\n\tset dimmed(isDimmed: boolean) {\n\t\tthis.reflect('dimmed', isDimmed);\n\t}\n\n\tget doublePress(): 'entry' | 'none' | 'reset' {\n\t\tconst gesture = this.getAttribute('double-press');\n\n\t\treturn gesture === 'none' || gesture === 'reset' ? gesture : 'entry';\n\t}\n\n\tset doublePress(gesture: 'entry' | 'none' | 'reset' | undefined) {\n\t\tthis.reflect('double-press', gesture);\n\t}\n\n\tget dragging(): boolean {\n\t\treturn this.hasState('dragging');\n\t}\n\n\tget entry(): 'none' | undefined {\n\t\treturn this.getAttribute('entry') === 'none' ? 'none' : undefined;\n\t}\n\n\tset entry(gesture: 'none' | undefined) {\n\t\tthis.reflect('entry', gesture);\n\t}\n\n\tget formatEntry(): ((value: number) => string) | undefined {\n\t\treturn this.#formatEntry;\n\t}\n\n\tset formatEntry(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatEntry = format;\n\t}\n\n\tget formatSpokenValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatSpokenValue;\n\t}\n\n\tset formatSpokenValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatSpokenValue = format;\n\t\tthis.render();\n\t}\n\n\tget formatValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\tget hoverValue(): number | undefined {\n\t\treturn this.#hover?.value();\n\t}\n\n\tget keyStep(): number | undefined {\n\t\treturn this.optionalNumberAttribute('key-step');\n\t}\n\n\tset keyStep(value: number | undefined) {\n\t\tthis.reflect('key-step', value);\n\t}\n\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 100);\n\t}\n\n\tset max(value: number | undefined) {\n\t\tthis.reflect('max', value);\n\t}\n\n\tget midpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('midpoint');\n\t}\n\n\tset midpoint(value: number | undefined) {\n\t\tthis.reflect('midpoint', value);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', 0);\n\t}\n\n\tset min(value: number | undefined) {\n\t\tthis.reflect('min', value);\n\t}\n\n\tget notched(): boolean {\n\t\treturn this.hasAttribute('notched');\n\t}\n\n\tset notched(isNotched: boolean) {\n\t\tthis.reflect('notched', isNotched);\n\t}\n\n\tget origin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('origin');\n\t}\n\n\tset origin(value: number | undefined) {\n\t\tthis.reflect('origin', value);\n\t}\n\n\tget parseValue(): ((text: string) => number) | undefined {\n\t\treturn this.#parseValue;\n\t}\n\n\tset parseValue(parse: ((text: string) => number) | undefined) {\n\t\tthis.#parseValue = parse;\n\t}\n\n\tget pointerType(): string | undefined {\n\t\treturn this.#gestures?.pointerType();\n\t}\n\n\t/** The values a stepped control stops at, spaced evenly along the travel whatever their spacing */\n\tget positions(): Array<number> | undefined {\n\t\treturn parseNumberList(this.getAttribute('positions'));\n\t}\n\n\tset positions(positions: Array<number> | undefined) {\n\t\tthis.reflect('positions', positions?.join(' '));\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isShown: boolean) {\n\t\tthis.reflect('readout', isShown);\n\t}\n\n\tget revealed(): boolean {\n\t\treturn this.hasState('revealed');\n\t}\n\n\t/** Rounds the value assistive technology hears, in value units; the value itself is untouched */\n\tget spokenStep(): number | undefined {\n\t\treturn this.optionalNumberAttribute('spoken-step');\n\t}\n\n\tset spokenStep(value: number | undefined) {\n\t\tthis.reflect('spoken-step', value);\n\t}\n\n\tget step(): number {\n\t\treturn this.numberAttribute('step', 1);\n\t}\n\n\tset step(value: number | undefined) {\n\t\tthis.reflect('step', value);\n\t}\n\n\tget taper(): 'linear' | 'log' {\n\t\treturn this.getAttribute('taper') === 'log' ? 'log' : 'linear';\n\t}\n\n\tset taper(curve: 'linear' | 'log' | undefined) {\n\t\tthis.reflect('taper', curve);\n\t}\n\n\tget value(): number {\n\t\treturn this.#model.value;\n\t}\n\n\tset value(next: null | number | undefined) {\n\t\t// A framework removes a prop by setting the property to `undefined`\n\t\tif (next === undefined || next === null) {\n\t\t\tthis.formResetCallback();\n\t\t\treturn;\n\t\t}\n\n\t\tconst value = toNumber(next);\n\n\t\tif (this.#isHeld()) {\n\t\t\tthis.#writeHeld(value);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#model.write(value)) this.render();\n\t}\n\n\tget valueText(): string {\n\t\treturn this.#textFor(this.#model.value);\n\t}\n\n\t// `Number.parseFloat`, as `Number('')` is 0; an emptied field should leave the value alone\n\tprotected readonly notation: ValueNotation = { format: String, parse: Number.parseFloat };\n\n\t#formatEntry: ((value: number) => string) | undefined;\n\n\t#formatSpokenValue: ((value: number) => string) | undefined;\n\n\t#formatValue: ((value: number) => string) | undefined;\n\n\t#gestures: undefined | ValueGestures;\n\n\t#heldAt: number | undefined;\n\n\t#hover: HoverPreview | undefined;\n\n\treadonly #instance = String((instanceCount += 1));\n\n\t// A subclass adding an input to `isWrapping()` has to observe it\n\treadonly #model = createValueModel(() => this.#spec());\n\n\t#parseValue: ((text: string) => number) | undefined;\n\n\t#readout: undefined | { anchor: HTMLElement; bubble: Readout };\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value' && this.#isHeld()) {\n\t\t\tthis.#writeHeld(this.numberAttribute('value', this.min));\n\t\t\treturn;\n\t\t}\n\t\tif (name === 'disabled' && this.isDisabled()) this.#gestures?.end();\n\n\t\tthis.#model.respec(this.numberAttribute('value', this.min), { forgetAsk: name === 'value' });\n\t\tif (proportionAttributes.has(name) && this.isBound()) this.proportionsChanged();\n\t\tthis.render();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'default',\n\t\t\t'dimmed',\n\t\t\t'doublePress',\n\t\t\t'entry',\n\t\t\t'max',\n\t\t\t'midpoint',\n\t\t\t'min',\n\t\t\t'notched',\n\t\t\t'origin',\n\t\t\t'readout',\n\t\t\t'spokenStep',\n\t\t\t'step',\n\t\t\t'taper',\n\t\t\t'positions',\n\t\t\t'detent',\n\t\t\t'formatEntry',\n\t\t\t'formatValue',\n\t\t\t'formatSpokenValue',\n\t\t\t'keyStep',\n\t\t\t'parseValue',\n\t\t\t'value',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected abandonHold(): void {\n\t\tthis.#gestures?.abandon();\n\t}\n\n\tprotected bindGestures(\n\t\tcontrol: HTMLElement,\n\t\tsignal: AbortSignal,\n\t\tgrab: (event: PointerEvent) => undefined | ValueAxis,\n\t): void {\n\t\tconst bubble = control.querySelector<HTMLElement>('[popover]');\n\n\t\tif (bubble) this.#readout = { anchor: control, bubble: new Readout(bubble) };\n\t\tthis.#gestures = new ValueGestures(\n\t\t\t{\n\t\t\t\tdefault: () => this.default,\n\t\t\t\tdetent: () => this.detent,\n\t\t\t\tdispatch: (type) => {\n\t\t\t\t\tthis.dispatchEvent(new Event(type, { bubbles: true }));\n\t\t\t\t},\n\t\t\t\tdoublePress: () => this.doublePress,\n\t\t\t\tentryId: `sonic-entry-${this.#instance}`,\n\t\t\t\tentryText: () => this.#entryText(),\n\t\t\t\tforwardNaming: (target, isNamed) => {\n\t\t\t\t\tthis.forwardNaming(target, isNamed);\n\t\t\t\t},\n\t\t\t\thasEntry: () => this.entry !== 'none',\n\t\t\t\tinput: (next) => this.input(next),\n\t\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\t\tkeyStep: () => this.keyStep,\n\t\t\t\tmodel: this.#model,\n\t\t\t\tparse: (text) => (this.#parseValue ?? this.notation.parse)(text),\n\t\t\t\trender: () => {\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t\tscrubChanged: () => {\n\t\t\t\t\tthis.#renderHold();\n\t\t\t\t},\n\t\t\t\tscrubsKeyRepeat: () => this.scrubsKeyRepeat(),\n\t\t\t\tspringTarget: () => this.springTarget(),\n\t\t\t\ttapTarget: () => this.tapTarget(),\n\t\t\t\ttoggleState: (state, isOn) => {\n\t\t\t\t\tthis.toggleState(state, isOn);\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontrol,\n\t\t);\n\t\tthis.#gestures.bind(signal, grab);\n\t\tcontrol.addEventListener(\n\t\t\t'pointerup',\n\t\t\t(event) => {\n\t\t\t\tthis.#hover?.lift(event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected bindHover(\n\t\tcontrol: HTMLElement,\n\t\tsignal: AbortSignal,\n\t\t{ canShow = () => true, place, valueAt }: HoverBinding,\n\t): void {\n\t\tthis.#hover = bindHoverPreview(\n\t\t\tcontrol,\n\t\t\t{\n\t\t\t\tcanShow,\n\t\t\t\tchanged: () => {\n\t\t\t\t\tthis.dispatchEvent(new Event('sonic-hover', { bubbles: true }));\n\t\t\t\t},\n\t\t\t\tdismiss: () => {\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t\tisTaken: () => this.#isEditing() || this.#isRevealed(),\n\t\t\t\tshow: () => {\n\t\t\t\t\tif (!this.readout) return;\n\n\t\t\t\t\tthis.renderReadout();\n\t\t\t\t\tplace();\n\t\t\t\t},\n\t\t\t\tvalueAt,\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\tprotected controlOrientation(): 'horizontal' | 'vertical' | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected controlRole(): 'slider' | 'spinbutton' {\n\t\treturn 'slider';\n\t}\n\n\tprotected abstract draw(): void;\n\n\tprotected abstract override focusTarget(): HTMLElement;\n\n\tprotected input(next: number): boolean {\n\t\tconst previous = this.#model.value;\n\n\t\tif (this.#model.input(next)) this.render();\n\t\tif (this.#model.value === previous) return false;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\tprotected isWrapping(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected mapping(): ValueMapping {\n\t\treturn this.#model.mapping();\n\t}\n\n\tprotected originValue(): number {\n\t\treturn this.origin ?? this.mapping().bounds[0];\n\t}\n\n\tprotected proportionsChanged(): void {\n\t\t// Runs when every proportion moves, never per input\n\t}\n\n\tprotected readoutValue(): number {\n\t\treturn this.#shown().value ?? this.#model.value;\n\t}\n\n\tprotected render(): void {\n\t\tif (this.isBound()) this.#renderControl();\n\t\tthis.#model.notify();\n\t}\n\n\tprotected renderReadout(): void {\n\t\tconst readout = this.#readout;\n\t\tif (!readout) return;\n\n\t\tconst { isOpen, value } = this.#shown();\n\t\tconst text = value === undefined ? undefined : this.#textFor(value);\n\n\t\treadout.bubble.show({ anchor: readout.anchor, isOpen: isOpen && text !== '', text });\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = Number(state);\n\t}\n\n\tprotected scrubChanged(): void {\n\t\t// Only a subclass draws a scrub\n\t}\n\n\tprotected scrubsKeyRepeat(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected scrubState(): ScrubState {\n\t\tconst from = this.#gestures?.scrubFrom();\n\n\t\treturn {\n\t\t\tfrom,\n\t\t\tisRevealed: this.#isRevealed(),\n\t\t\tplayed: from === undefined ? this.#model.value : (this.#heldAt ?? from),\n\t\t};\n\t}\n\n\tprotected springTarget(): number | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected tapTarget(): number | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected travelPx(control: HTMLElement, property: `--_sonic-${string}`): number {\n\t\treturn Math.max(1, readPxProperty(getComputedStyle(control), property, fallbackTravelPx));\n\t}\n\n\t#entryText(): string {\n\t\tconst value = this.#model.value;\n\n\t\tif (this.#formatEntry) return this.#formatEntry(value);\n\n\t\t// The notation's own parser may not read a consumer's `formatValue`\n\t\tif (!this.#parseValue) return this.notation.format(value);\n\n\t\tconst text = this.valueText;\n\n\t\tif (__DEV__ && !Number.isFinite(this.#parseValue(text))) {\n\t\t\tconsole.warn(\n\t\t\t\t`<${this.localName}> opens its entry with \"${text}\", which parseValue cannot read; set formatEntry`,\n\t\t\t);\n\t\t}\n\n\t\treturn text;\n\t}\n\n\t#isEditing(): boolean {\n\t\treturn this.#gestures?.isEditing() === true;\n\t}\n\n\t#isHeld(): boolean {\n\t\treturn this.#gestures?.isHeld() === true;\n\t}\n\n\t#isRevealed(): boolean {\n\t\treturn this.#gestures?.isRevealed() === true;\n\t}\n\n\t#renderAria(control: HTMLElement, orientation?: 'horizontal' | 'vertical'): void {\n\t\tthis.writeFormValue(String(this.#model.value), String(this.#model.value));\n\t\tif (this.#isEditing()) {\n\t\t\tfor (const name of roleAttributes) control.removeAttribute(name);\n\t\t\tthis.forwardNaming(control, false);\n\t\t\tthis.renderReadout();\n\t\t\treturn;\n\t\t}\n\n\t\tconst [low, high] = this.mapping().bounds;\n\n\t\twriteAttribute(control, 'role', this.controlRole());\n\t\twriteAttribute(control, 'aria-valuemin', String(low));\n\t\twriteAttribute(control, 'aria-valuemax', String(high));\n\t\twriteAttribute(control, 'aria-valuenow', String(this.#spokenNow()));\n\t\tif (orientation) writeAttribute(control, 'aria-orientation', orientation);\n\t\twriteAttribute(\n\t\t\tcontrol,\n\t\t\t'aria-valuetext',\n\t\t\t(this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak)?.(this.#spokenNow()),\n\t\t);\n\t\tthis.forwardNaming(control, true);\n\t\tthis.#renderDisabled(control);\n\t\tthis.renderReadout();\n\t}\n\n\t#renderControl(): void {\n\t\t// A reveal or typed entry takes the readout from a hover\n\t\tif (this.#isEditing() || this.#isRevealed()) this.#hover?.clear();\n\t\tthis.draw();\n\t\tthis.toggleState(\n\t\t\t'at-origin',\n\t\t\t!this.isWrapping() && this.#model.value === this.mapping().snap(this.originValue()),\n\t\t);\n\t\tthis.#renderAria(this.focusTarget(), this.controlOrientation());\n\t}\n\n\t#renderDisabled(control: HTMLElement): void {\n\t\tconst isDisabled = this.isDisabled();\n\n\t\twriteAttribute(control, 'aria-disabled', isDisabled ? 'true' : undefined);\n\t\t// The host is `display: contents`, so its tab stop is the control's\n\t\tconst stop = this.getAttribute('tabindex') === '-1' ? '-1' : '0';\n\n\t\twriteAttribute(control, 'tabindex', isDisabled ? undefined : stop);\n\t}\n\n\t#renderHold(): void {\n\t\tif (this.#gestures?.scrubFrom() === undefined) this.#heldAt = undefined;\n\t\tthis.#hover?.restore();\n\t\tthis.renderReadout();\n\t\tthis.scrubChanged();\n\t}\n\n\t#shown(): { isOpen: boolean; value: number | undefined } {\n\t\tif (this.#isEditing()) return { isOpen: true, value: undefined };\n\n\t\tconst isRevealed = this.#isRevealed();\n\t\tconst preview = isRevealed ? undefined : this.#hover?.value();\n\n\t\treturn {\n\t\t\tisOpen: (isRevealed || preview !== undefined) && this.readout,\n\t\t\tvalue: preview ?? this.#model.value,\n\t\t};\n\t}\n\n\t#spec(): ValueSpec {\n\t\tconst { detent, midpoint, positions } = this;\n\n\t\treturn {\n\t\t\t...(detent === undefined ? {} : { detent }),\n\t\t\t...(positions ? { positions } : {}),\n\t\t\tmax: this.max,\n\t\t\t...(midpoint === undefined ? {} : { midpoint }),\n\t\t\tisNotched: this.notched,\n\t\t\tisWrapping: this.isWrapping(),\n\t\t\tmin: this.min,\n\t\t\tstep: this.step,\n\t\t\ttaper: this.taper,\n\t\t};\n\t}\n\n\t#spokenNow(): number {\n\t\tconst step = this.spokenStep;\n\n\t\treturn step !== undefined && step > 0\n\t\t\t? trimFloat(Math.round(this.#model.value / step) * step)\n\t\t\t: this.#model.value;\n\t}\n\n\t#textFor(value: number): string {\n\t\treturn (this.#formatValue ?? this.notation.format)(value);\n\t}\n\n\t#writeHeld(next: number): void {\n\t\tthis.#heldAt = next;\n\t\tthis.scrubChanged();\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"value-element.js","names":[],"sources":["../../../src/elements/value-element.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { HoverPreview } from '#lib/hover-preview.ts';\nimport type { ScrubState } from '#lib/scrub.ts';\nimport type { ValueMapping, ValueSpec } from '#lib/value-mapping.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { isUnanchored, Readout } from '#elements/readout.ts';\nimport { ValueGestures } from '#elements/value-gestures.ts';\nimport { bindHoverPreview } from '#lib/hover-preview.ts';\nimport { toNumber, trimFloat } from '#lib/math.ts';\nimport { parseNumberList } from '#lib/number-list.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { createValueModel } from '#lib/value-model.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-hover': Event;\n\t\t'sonic-reveal': Event;\n\t}\n}\n\ninterface HoverBinding {\n\tcanShow?: () => boolean;\n\tchanged?: () => void;\n\tplace: () => void;\n\tvalueAt: (event: PointerEvent) => number | undefined;\n}\n\nexport interface ValueNotation {\n\tformat: (value: number) => string;\n\tparse: (text: string) => number;\n\tspeak?: (value: number) => string;\n}\n\nconst fallbackTravelPx = 160;\n\nlet instanceCount = 0;\n\nconst proportionAttributes = new Set(['max', 'midpoint', 'min', 'notched', 'positions', 'taper']);\n\nconst roleAttributes = [\n\t'aria-disabled',\n\t'aria-orientation',\n\t'aria-valuemax',\n\t'aria-valuemin',\n\t'aria-valuenow',\n\t'aria-valuetext',\n\t'role',\n\t'tabindex',\n];\n\nexport interface ValueLink {\n\tchange: () => void;\n\tinput: (next: number, isMover?: boolean) => boolean;\n\tisDisabled: () => boolean;\n\tisHeld: () => boolean;\n\tmodel: ValueModel;\n}\n\n// Filled from inside the class, so it reaches protected members and nothing lands on the element's public type\nlet link: (element: SonicValueElement) => ValueLink;\n\nexport function linkValue(element: SonicValueElement): ValueLink {\n\treturn link(element);\n}\n\nexport abstract class SonicValueElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'detent',\n\t\t'max',\n\t\t'midpoint',\n\t\t'min',\n\t\t'notched',\n\t\t'origin',\n\t\t'readout',\n\t\t'spoken-step',\n\t\t'step',\n\t\t'tabindex',\n\t\t'taper',\n\t\t'value',\n\t\t'positions',\n\t];\n\n\tstatic {\n\t\tlink = (element) => ({\n\t\t\tchange: () => {\n\t\t\t\telement.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\tinput: (next, isMover = false) => (isMover || !element.#isHeld()) && element.input(next),\n\t\t\tisDisabled: () => element.isDisabled(),\n\t\t\tisHeld: () => element.#isHeld(),\n\t\t\tmodel: element.#model,\n\t\t});\n\t}\n\n\tget cancelling(): boolean {\n\t\treturn this.hasState('cancelling');\n\t}\n\n\tget default(): number | undefined {\n\t\treturn this.optionalNumberAttribute('default');\n\t}\n\n\tset default(value: number | undefined) {\n\t\tthis.reflect('default', value);\n\t}\n\n\tget detent(): number | undefined {\n\t\treturn this.optionalNumberAttribute('detent');\n\t}\n\n\tset detent(value: number | undefined) {\n\t\tthis.reflect('detent', value);\n\t}\n\n\tget dimmed(): boolean {\n\t\treturn this.hasAttribute('dimmed');\n\t}\n\n\tset dimmed(isDimmed: boolean) {\n\t\tthis.reflect('dimmed', isDimmed);\n\t}\n\n\tget doublePress(): 'entry' | 'none' | 'reset' {\n\t\tconst gesture = this.getAttribute('double-press');\n\n\t\treturn gesture === 'none' || gesture === 'reset' ? gesture : 'entry';\n\t}\n\n\tset doublePress(gesture: 'entry' | 'none' | 'reset' | undefined) {\n\t\tthis.reflect('double-press', gesture);\n\t}\n\n\tget dragging(): boolean {\n\t\treturn this.hasState('dragging');\n\t}\n\n\tget entry(): 'none' | undefined {\n\t\treturn this.getAttribute('entry') === 'none' ? 'none' : undefined;\n\t}\n\n\tset entry(gesture: 'none' | undefined) {\n\t\tthis.reflect('entry', gesture);\n\t}\n\n\tget formatEntry(): ((value: number) => string) | undefined {\n\t\treturn this.#formatEntry;\n\t}\n\n\tset formatEntry(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatEntry = format;\n\t}\n\n\tget formatSpokenValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatSpokenValue;\n\t}\n\n\tset formatSpokenValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatSpokenValue = format;\n\t\tthis.render();\n\t}\n\n\tget formatValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\tget hoverValue(): number | undefined {\n\t\treturn this.#hover?.value();\n\t}\n\n\tget keyStep(): number | undefined {\n\t\treturn this.optionalNumberAttribute('key-step');\n\t}\n\n\tset keyStep(value: number | undefined) {\n\t\tthis.reflect('key-step', value);\n\t}\n\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 100);\n\t}\n\n\tset max(value: number | undefined) {\n\t\tthis.reflect('max', value);\n\t}\n\n\tget midpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('midpoint');\n\t}\n\n\tset midpoint(value: number | undefined) {\n\t\tthis.reflect('midpoint', value);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', 0);\n\t}\n\n\tset min(value: number | undefined) {\n\t\tthis.reflect('min', value);\n\t}\n\n\tget notched(): boolean {\n\t\treturn this.hasAttribute('notched');\n\t}\n\n\tset notched(isNotched: boolean) {\n\t\tthis.reflect('notched', isNotched);\n\t}\n\n\tget origin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('origin');\n\t}\n\n\tset origin(value: number | undefined) {\n\t\tthis.reflect('origin', value);\n\t}\n\n\tget parseValue(): ((text: string) => number) | undefined {\n\t\treturn this.#parseValue;\n\t}\n\n\tset parseValue(parse: ((text: string) => number) | undefined) {\n\t\tthis.#parseValue = parse;\n\t}\n\n\tget pointerType(): string | undefined {\n\t\treturn this.#gestures?.pointerType();\n\t}\n\n\t/** The values a stepped control stops at, spaced evenly along the travel whatever their spacing */\n\tget positions(): Array<number> | undefined {\n\t\treturn parseNumberList(this.getAttribute('positions'));\n\t}\n\n\tset positions(positions: Array<number> | undefined) {\n\t\tthis.reflect('positions', positions?.join(' '));\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isShown: boolean) {\n\t\tthis.reflect('readout', isShown);\n\t}\n\n\tget revealed(): boolean {\n\t\treturn this.hasState('revealed');\n\t}\n\n\t/** Rounds the value assistive technology hears, in value units; the value itself is untouched */\n\tget spokenStep(): number | undefined {\n\t\treturn this.optionalNumberAttribute('spoken-step');\n\t}\n\n\tset spokenStep(value: number | undefined) {\n\t\tthis.reflect('spoken-step', value);\n\t}\n\n\tget step(): number {\n\t\treturn this.numberAttribute('step', 1);\n\t}\n\n\tset step(value: number | undefined) {\n\t\tthis.reflect('step', value);\n\t}\n\n\tget taper(): 'linear' | 'log' {\n\t\treturn this.getAttribute('taper') === 'log' ? 'log' : 'linear';\n\t}\n\n\tset taper(curve: 'linear' | 'log' | undefined) {\n\t\tthis.reflect('taper', curve);\n\t}\n\n\tget value(): number {\n\t\treturn this.#model.value;\n\t}\n\n\tset value(next: null | number | undefined) {\n\t\t// A framework removes a prop by setting the property to `undefined`\n\t\tif (next === undefined || next === null) {\n\t\t\tthis.formResetCallback();\n\t\t\treturn;\n\t\t}\n\n\t\tconst value = toNumber(next);\n\n\t\tif (this.#isHeld()) {\n\t\t\tthis.#writeHeld(value);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#model.write(value)) this.render();\n\t}\n\n\tget valueText(): string {\n\t\treturn this.#textFor(this.#model.value);\n\t}\n\n\t// `Number.parseFloat`, as `Number('')` is 0; an emptied field should leave the value alone\n\tprotected readonly notation: ValueNotation = { format: String, parse: Number.parseFloat };\n\n\t#formatEntry: ((value: number) => string) | undefined;\n\n\t#formatSpokenValue: ((value: number) => string) | undefined;\n\n\t#formatValue: ((value: number) => string) | undefined;\n\n\t#gestures: undefined | ValueGestures;\n\n\t#heldAt: number | undefined;\n\n\t#hover: HoverPreview | undefined;\n\n\treadonly #instance = String((instanceCount += 1));\n\n\t// A subclass adding an input to `isWrapping()` has to observe it\n\treadonly #model = createValueModel(() => this.#spec());\n\n\t#parseValue: ((text: string) => number) | undefined;\n\n\t#readout: undefined | { anchor: HTMLElement; bubble: Readout };\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value' && this.#isHeld()) {\n\t\t\tthis.#writeHeld(this.numberAttribute('value', this.min));\n\t\t\treturn;\n\t\t}\n\t\tif (name === 'disabled' && this.isDisabled()) this.#gestures?.end();\n\n\t\tthis.#model.respec(this.numberAttribute('value', this.min), { forgetAsk: name === 'value' });\n\t\tif (proportionAttributes.has(name) && this.isBound()) this.proportionsChanged();\n\t\tthis.render();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'default',\n\t\t\t'dimmed',\n\t\t\t'doublePress',\n\t\t\t'entry',\n\t\t\t'max',\n\t\t\t'midpoint',\n\t\t\t'min',\n\t\t\t'notched',\n\t\t\t'origin',\n\t\t\t'readout',\n\t\t\t'spokenStep',\n\t\t\t'step',\n\t\t\t'taper',\n\t\t\t'positions',\n\t\t\t'detent',\n\t\t\t'formatEntry',\n\t\t\t'formatValue',\n\t\t\t'formatSpokenValue',\n\t\t\t'keyStep',\n\t\t\t'parseValue',\n\t\t\t'value',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected abandonHold(): void {\n\t\tthis.#gestures?.abandon();\n\t}\n\n\tprotected bindGestures(\n\t\tcontrol: HTMLElement,\n\t\tsignal: AbortSignal,\n\t\tgrab: (event: PointerEvent) => undefined | ValueAxis,\n\t): void {\n\t\tconst bubble = control.querySelector<HTMLElement>('[popover]');\n\n\t\tif (bubble) this.#readout = { anchor: control, bubble: new Readout(bubble) };\n\t\tthis.#gestures = new ValueGestures(\n\t\t\t{\n\t\t\t\tdefault: () => this.default,\n\t\t\t\tdetent: () => this.detent,\n\t\t\t\tdispatch: (type) => {\n\t\t\t\t\tthis.dispatchEvent(new Event(type, { bubbles: true }));\n\t\t\t\t},\n\t\t\t\tdoublePress: () => this.doublePress,\n\t\t\t\tentryId: `sonic-entry-${this.#instance}`,\n\t\t\t\tentryText: () => this.#entryText(),\n\t\t\t\tforwardNaming: (target, isNamed) => {\n\t\t\t\t\tthis.forwardNaming(target, isNamed);\n\t\t\t\t},\n\t\t\t\thasEntry: () => !isUnanchored && this.entry !== 'none',\n\t\t\t\tinput: (next) => this.input(next),\n\t\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\t\tkeyStep: () => this.keyStep,\n\t\t\t\tmodel: this.#model,\n\t\t\t\tparse: (text) => (this.#parseValue ?? this.notation.parse)(text),\n\t\t\t\trender: () => {\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t\tscrubChanged: () => {\n\t\t\t\t\tthis.#renderHold();\n\t\t\t\t},\n\t\t\t\tscrubsKeyRepeat: () => this.scrubsKeyRepeat(),\n\t\t\t\tspringTarget: () => this.springTarget(),\n\t\t\t\ttapTarget: () => this.tapTarget(),\n\t\t\t\ttoggleState: (state, isOn) => {\n\t\t\t\t\tthis.toggleState(state, isOn);\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontrol,\n\t\t\tthis.focusTarget(),\n\t\t);\n\t\tthis.#gestures.bind(signal, grab);\n\t\tcontrol.addEventListener(\n\t\t\t'pointerup',\n\t\t\t(event) => {\n\t\t\t\tthis.#hover?.lift(event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected bindHover(\n\t\tcontrol: HTMLElement,\n\t\tsignal: AbortSignal,\n\t\t{ canShow = () => true, changed, place, valueAt }: HoverBinding,\n\t): void {\n\t\tthis.#hover = bindHoverPreview(\n\t\t\tcontrol,\n\t\t\t{\n\t\t\t\tcanShow,\n\t\t\t\tchanged: () => {\n\t\t\t\t\tchanged?.();\n\t\t\t\t\tthis.dispatchEvent(new Event('sonic-hover', { bubbles: true }));\n\t\t\t\t},\n\t\t\t\tdismiss: () => {\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t\tisTaken: () => this.#isEditing() || this.#isRevealed(),\n\t\t\t\tshow: () => {\n\t\t\t\t\tif (!this.readout) return;\n\n\t\t\t\t\tthis.renderReadout();\n\t\t\t\t\tplace();\n\t\t\t\t},\n\t\t\t\tvalueAt,\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\tprotected controlOrientation(): 'horizontal' | 'vertical' | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected controlRole(): 'slider' | 'spinbutton' {\n\t\treturn 'slider';\n\t}\n\n\tprotected abstract draw(): void;\n\n\tprotected abstract override focusTarget(): HTMLElement;\n\n\tprotected input(next: number): boolean {\n\t\tconst previous = this.#model.value;\n\n\t\tif (this.#model.input(next)) this.render();\n\t\tif (this.#model.value === previous) return false;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\tprotected isWrapping(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected mapping(): ValueMapping {\n\t\treturn this.#model.mapping();\n\t}\n\n\tprotected originValue(): number {\n\t\treturn this.origin ?? this.mapping().bounds[0];\n\t}\n\n\tprotected proportionsChanged(): void {\n\t\t// Runs when every proportion moves, never per input\n\t}\n\n\tprotected readoutValue(): number {\n\t\treturn this.#shown().value ?? this.#model.value;\n\t}\n\n\tprotected render(): void {\n\t\tif (this.isBound()) this.#renderControl();\n\t\tthis.#model.notify();\n\t}\n\n\tprotected renderReadout(): void {\n\t\tconst readout = this.#readout;\n\t\tif (!readout) return;\n\n\t\tconst { isOpen, value } = this.#shown();\n\t\tconst text = value === undefined ? undefined : this.#textFor(value);\n\n\t\treadout.bubble.show({ anchor: readout.anchor, isOpen: isOpen && text !== '', text });\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = Number(state);\n\t}\n\n\tprotected scrubChanged(): void {\n\t\t// Only a subclass draws a scrub\n\t}\n\n\tprotected scrubsKeyRepeat(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected scrubState(): ScrubState {\n\t\tconst from = this.#gestures?.scrubFrom();\n\n\t\treturn {\n\t\t\tfrom,\n\t\t\tisRevealed: this.#isRevealed(),\n\t\t\tplayed: from === undefined ? this.#model.value : (this.#heldAt ?? from),\n\t\t};\n\t}\n\n\tprotected spokenText(value: number): string | undefined {\n\t\treturn (this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak)?.(value);\n\t}\n\n\tprotected springTarget(): number | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected tapTarget(): number | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected travelPx(control: HTMLElement, property: `--_sonic-${string}`): number {\n\t\treturn Math.max(1, readPxProperty(getComputedStyle(control), property, fallbackTravelPx));\n\t}\n\n\t#entryText(): string {\n\t\tconst value = this.#model.value;\n\n\t\tif (this.#formatEntry) return this.#formatEntry(value);\n\n\t\t// The notation's own parser may not read a consumer's `formatValue`\n\t\tif (!this.#parseValue) return this.notation.format(value);\n\n\t\tconst text = this.valueText;\n\n\t\tif (__DEV__ && !Number.isFinite(this.#parseValue(text))) {\n\t\t\tconsole.warn(\n\t\t\t\t`<${this.localName}> opens its entry with \"${text}\", which parseValue cannot read; set formatEntry`,\n\t\t\t);\n\t\t}\n\n\t\treturn text;\n\t}\n\n\t#isEditing(): boolean {\n\t\treturn this.#gestures?.isEditing() === true;\n\t}\n\n\t#isHeld(): boolean {\n\t\treturn this.#gestures?.isHeld() === true;\n\t}\n\n\t#isRevealed(): boolean {\n\t\treturn this.#gestures?.isRevealed() === true;\n\t}\n\n\t#renderAria(control: HTMLElement, orientation?: 'horizontal' | 'vertical'): void {\n\t\tthis.writeFormValue(String(this.#model.value), String(this.#model.value));\n\t\tif (this.#isEditing()) {\n\t\t\tfor (const name of roleAttributes) control.removeAttribute(name);\n\t\t\tthis.forwardNaming(control, false);\n\t\t\tthis.renderReadout();\n\t\t\treturn;\n\t\t}\n\n\t\tconst [low, high] = this.mapping().bounds;\n\n\t\twriteAttribute(control, 'role', this.controlRole());\n\t\twriteAttribute(control, 'aria-valuemin', String(low));\n\t\twriteAttribute(control, 'aria-valuemax', String(high));\n\t\twriteAttribute(control, 'aria-valuenow', String(this.#spokenNow()));\n\t\tif (orientation) writeAttribute(control, 'aria-orientation', orientation);\n\t\twriteAttribute(control, 'aria-valuetext', this.spokenText(this.#spokenNow()));\n\t\tthis.forwardNaming(control, true);\n\t\tthis.#renderDisabled(control);\n\t\tthis.renderReadout();\n\t}\n\n\t#renderControl(): void {\n\t\t// A reveal or typed entry takes the readout from a hover\n\t\tif (this.#isEditing() || this.#isRevealed()) this.#hover?.clear();\n\t\tthis.draw();\n\t\tthis.toggleState(\n\t\t\t'at-origin',\n\t\t\t!this.isWrapping() && this.#model.value === this.mapping().snap(this.originValue()),\n\t\t);\n\t\tthis.#renderAria(this.focusTarget(), this.controlOrientation());\n\t}\n\n\t#renderDisabled(control: HTMLElement): void {\n\t\tconst isDisabled = this.isDisabled();\n\n\t\twriteAttribute(control, 'aria-disabled', isDisabled ? 'true' : undefined);\n\t\t// The host is `display: contents`, so its tab stop is the control's\n\t\tconst stop = this.getAttribute('tabindex') === '-1' ? '-1' : '0';\n\n\t\twriteAttribute(control, 'tabindex', isDisabled ? undefined : stop);\n\t}\n\n\t#renderHold(): void {\n\t\tif (this.#gestures?.scrubFrom() === undefined) this.#heldAt = undefined;\n\t\tthis.#hover?.restore();\n\t\tthis.renderReadout();\n\t\tthis.scrubChanged();\n\t}\n\n\t#shown(): { isOpen: boolean; value: number | undefined } {\n\t\tif (this.#isEditing()) return { isOpen: true, value: undefined };\n\n\t\tconst isRevealed = this.#isRevealed();\n\t\tconst preview = isRevealed ? undefined : this.#hover?.value();\n\n\t\treturn {\n\t\t\tisOpen: (isRevealed || preview !== undefined) && this.readout,\n\t\t\tvalue: preview ?? this.#model.value,\n\t\t};\n\t}\n\n\t#spec(): ValueSpec {\n\t\tconst { detent, midpoint, positions } = this;\n\n\t\treturn {\n\t\t\t...(detent === undefined ? {} : { detent }),\n\t\t\t...(positions ? { positions } : {}),\n\t\t\tmax: this.max,\n\t\t\t...(midpoint === undefined ? {} : { midpoint }),\n\t\t\tisNotched: this.notched,\n\t\t\tisWrapping: this.isWrapping(),\n\t\t\tmin: this.min,\n\t\t\tstep: this.step,\n\t\t\ttaper: this.taper,\n\t\t};\n\t}\n\n\t#spokenNow(): number {\n\t\tconst step = this.spokenStep;\n\n\t\treturn step !== undefined && step > 0\n\t\t\t? trimFloat(Math.round(this.#model.value / step) * step)\n\t\t\t: this.#model.value;\n\t}\n\n\t#textFor(value: number): string {\n\t\treturn (this.#formatValue ?? this.notation.format)(value);\n\t}\n\n\t#writeHeld(next: number): void {\n\t\tthis.#heldAt = next;\n\t\tthis.scrubChanged();\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -18,14 +18,16 @@ var ValueGestures = class {
|
|
|
18
18
|
});
|
|
19
19
|
#control;
|
|
20
20
|
#entry;
|
|
21
|
+
#focus;
|
|
21
22
|
#host;
|
|
22
23
|
#isRevealed = false;
|
|
23
24
|
#keyScrub;
|
|
24
25
|
#pointerDrag;
|
|
25
26
|
#pointerType;
|
|
26
|
-
constructor(host, control) {
|
|
27
|
+
constructor(host, control, focus) {
|
|
27
28
|
this.#host = host;
|
|
28
29
|
this.#control = control;
|
|
30
|
+
this.#focus = focus;
|
|
29
31
|
}
|
|
30
32
|
abandon() {
|
|
31
33
|
const drag = this.#dragging();
|
|
@@ -46,7 +48,7 @@ var ValueGestures = class {
|
|
|
46
48
|
if (isMenuPress(event) || host.isDisabled() || entry.isOpen) return;
|
|
47
49
|
host.toggleState("springing", false);
|
|
48
50
|
if (event.pointerType === "touch") event.preventDefault();
|
|
49
|
-
focusByPointer(
|
|
51
|
+
focusByPointer(this.#focus);
|
|
50
52
|
this.#pointerType = event.pointerType;
|
|
51
53
|
if (isResetPress(event)) {
|
|
52
54
|
this.#reset();
|
|
@@ -135,14 +137,14 @@ var ValueGestures = class {
|
|
|
135
137
|
}
|
|
136
138
|
});
|
|
137
139
|
this.#entry = entry;
|
|
138
|
-
entry.bind(
|
|
140
|
+
entry.bind(this.#focus, signal);
|
|
139
141
|
return entry;
|
|
140
142
|
}
|
|
141
143
|
#bindKeys(entry, signal) {
|
|
142
144
|
const control = this.#control;
|
|
143
145
|
const host = this.#host;
|
|
144
146
|
control.addEventListener("keydown", (event) => {
|
|
145
|
-
if (host.isDisabled() || event.defaultPrevented || event.target !==
|
|
147
|
+
if (host.isDisabled() || event.defaultPrevented || event.target !== this.#focus) return;
|
|
146
148
|
host.toggleState("springing", false);
|
|
147
149
|
if (event.key === "Enter") {
|
|
148
150
|
if (host.springTarget() !== void 0 || !host.hasEntry()) return;
|
|
@@ -164,7 +166,7 @@ var ValueGestures = class {
|
|
|
164
166
|
if (isMeta && this.#dragging()) return;
|
|
165
167
|
if (isMeta || host.model.mapping().keyTarget(event.key, host.model.value) !== void 0) this.#springBack();
|
|
166
168
|
}, { signal });
|
|
167
|
-
|
|
169
|
+
this.#focus.addEventListener("blur", () => {
|
|
168
170
|
this.#endKeyScrub();
|
|
169
171
|
this.#concealKeyReveal();
|
|
170
172
|
this.#springBack();
|