@xsynaptic/sonic-ui 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +112 -19
  3. package/dist/default/dat.js +1 -0
  4. package/dist/default/elements/button.js +2 -0
  5. package/dist/default/elements/dial.js +2 -0
  6. package/dist/default/elements/display-element.js +9 -7
  7. package/dist/default/elements/envelope.js +31 -72
  8. package/dist/default/elements/field-gesture.js +81 -0
  9. package/dist/default/elements/meter.js +4 -0
  10. package/dist/default/elements/position-group.js +42 -26
  11. package/dist/default/elements/radio-group.js +59 -56
  12. package/dist/default/elements/scale.js +3 -3
  13. package/dist/default/elements/slider.js +11 -13
  14. package/dist/default/elements/split.js +14 -13
  15. package/dist/default/elements/switch.js +31 -46
  16. package/dist/default/elements/toggle.js +20 -44
  17. package/dist/default/elements/value-element.js +65 -91
  18. package/dist/default/elements/value-gestures.js +22 -22
  19. package/dist/default/elements/wave-element.js +9 -12
  20. package/dist/default/elements/waveform.js +42 -241
  21. package/dist/default/elements/wavestrip.js +10 -5
  22. package/dist/default/elements/xy.js +67 -112
  23. package/dist/default/lib/canvas-surface.js +25 -0
  24. package/dist/default/lib/crossfade.js +1 -0
  25. package/dist/default/lib/envelope-curve.js +1 -0
  26. package/dist/default/lib/peaks-request.js +44 -0
  27. package/dist/default/lib/positions.js +58 -0
  28. package/dist/default/lib/scrub.js +12 -0
  29. package/dist/default/lib/value-model.js +62 -0
  30. package/dist/default/lib/waveform-scene.js +108 -0
  31. package/dist/default/lib/zoom-gesture.js +97 -1
  32. package/dist/dev/dat.d.ts +1 -0
  33. package/dist/dev/dat.js +1 -0
  34. package/dist/dev/dat.js.map +1 -1
  35. package/dist/dev/elements/button.d.ts +2 -0
  36. package/dist/dev/elements/button.js +2 -0
  37. package/dist/dev/elements/button.js.map +1 -1
  38. package/dist/dev/elements/dial.d.ts +2 -0
  39. package/dist/dev/elements/dial.js +2 -0
  40. package/dist/dev/elements/dial.js.map +1 -1
  41. package/dist/dev/elements/display-element.d.ts +5 -3
  42. package/dist/dev/elements/display-element.js +9 -7
  43. package/dist/dev/elements/display-element.js.map +1 -1
  44. package/dist/dev/elements/envelope.js +31 -72
  45. package/dist/dev/elements/envelope.js.map +1 -1
  46. package/dist/dev/elements/field-gesture.js +82 -0
  47. package/dist/dev/elements/field-gesture.js.map +1 -0
  48. package/dist/dev/elements/meter.d.ts +10 -5
  49. package/dist/dev/elements/meter.js +4 -0
  50. package/dist/dev/elements/meter.js.map +1 -1
  51. package/dist/dev/elements/position-group.d.ts +12 -6
  52. package/dist/dev/elements/position-group.js +42 -26
  53. package/dist/dev/elements/position-group.js.map +1 -1
  54. package/dist/dev/elements/radio-group.d.ts +11 -7
  55. package/dist/dev/elements/radio-group.js +59 -56
  56. package/dist/dev/elements/radio-group.js.map +1 -1
  57. package/dist/dev/elements/scale.js +3 -3
  58. package/dist/dev/elements/scale.js.map +1 -1
  59. package/dist/dev/elements/slider.d.ts +4 -2
  60. package/dist/dev/elements/slider.js +11 -13
  61. package/dist/dev/elements/slider.js.map +1 -1
  62. package/dist/dev/elements/split.d.ts +1 -0
  63. package/dist/dev/elements/split.js +14 -13
  64. package/dist/dev/elements/split.js.map +1 -1
  65. package/dist/dev/elements/switch.d.ts +2 -2
  66. package/dist/dev/elements/switch.js +31 -46
  67. package/dist/dev/elements/switch.js.map +1 -1
  68. package/dist/dev/elements/toggle.js +20 -44
  69. package/dist/dev/elements/toggle.js.map +1 -1
  70. package/dist/dev/elements/value-element.d.ts +5 -5
  71. package/dist/dev/elements/value-element.js +65 -91
  72. package/dist/dev/elements/value-element.js.map +1 -1
  73. package/dist/dev/elements/value-gestures.js +22 -22
  74. package/dist/dev/elements/value-gestures.js.map +1 -1
  75. package/dist/dev/elements/wave-element.d.ts +6 -4
  76. package/dist/dev/elements/wave-element.js +9 -12
  77. package/dist/dev/elements/wave-element.js.map +1 -1
  78. package/dist/dev/elements/waveform.d.ts +19 -21
  79. package/dist/dev/elements/waveform.js +42 -241
  80. package/dist/dev/elements/waveform.js.map +1 -1
  81. package/dist/dev/elements/wavestrip.d.ts +5 -1
  82. package/dist/dev/elements/wavestrip.js +10 -5
  83. package/dist/dev/elements/wavestrip.js.map +1 -1
  84. package/dist/dev/elements/xy.js +67 -112
  85. package/dist/dev/elements/xy.js.map +1 -1
  86. package/dist/dev/index.d.ts +2 -1
  87. package/dist/dev/lib/canvas-surface.d.ts +1 -0
  88. package/dist/dev/lib/canvas-surface.js +25 -0
  89. package/dist/dev/lib/canvas-surface.js.map +1 -1
  90. package/dist/dev/lib/crossfade.d.ts +1 -0
  91. package/dist/dev/lib/crossfade.js +1 -0
  92. package/dist/dev/lib/crossfade.js.map +1 -1
  93. package/dist/dev/lib/envelope-curve.d.ts +1 -0
  94. package/dist/dev/lib/envelope-curve.js +1 -0
  95. package/dist/dev/lib/envelope-curve.js.map +1 -1
  96. package/dist/dev/lib/peaks-request.d.ts +5 -0
  97. package/dist/dev/lib/peaks-request.js +45 -0
  98. package/dist/dev/lib/peaks-request.js.map +1 -0
  99. package/dist/dev/lib/positions.d.ts +7 -0
  100. package/dist/dev/lib/positions.js +59 -0
  101. package/dist/dev/lib/positions.js.map +1 -0
  102. package/dist/dev/lib/scrub.d.ts +8 -0
  103. package/dist/dev/lib/scrub.js +13 -0
  104. package/dist/dev/lib/scrub.js.map +1 -0
  105. package/dist/dev/lib/time-regions.d.ts +1 -0
  106. package/dist/dev/lib/time-regions.js.map +1 -1
  107. package/dist/dev/lib/units.d.ts +8 -0
  108. package/dist/dev/lib/value-model.js +63 -0
  109. package/dist/dev/lib/value-model.js.map +1 -0
  110. package/dist/dev/lib/waveform-buckets.d.ts +1 -0
  111. package/dist/dev/lib/waveform-buckets.js.map +1 -1
  112. package/dist/dev/lib/waveform-scene.d.ts +11 -0
  113. package/dist/dev/lib/waveform-scene.js +109 -0
  114. package/dist/dev/lib/waveform-scene.js.map +1 -0
  115. package/dist/dev/lib/zoom-gesture.js +97 -1
  116. package/dist/dev/lib/zoom-gesture.js.map +1 -1
  117. package/dist/styles/button.css +16 -6
  118. package/dist/styles/waveform.css +2 -1
  119. package/package.json +1 -1
  120. package/dist/default/lib/asked-value.js +0 -21
  121. package/dist/default/lib/fill-watch.js +0 -29
  122. package/dist/dev/lib/asked-value.js +0 -22
  123. package/dist/dev/lib/asked-value.js.map +0 -1
  124. package/dist/dev/lib/fill-watch.js +0 -30
  125. package/dist/dev/lib/fill-watch.js.map +0 -1
@@ -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\n// eslint-disable-next-line unicorn/consistent-boolean-name -- the name bundlers and other kits use\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.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\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#signal: AbortSignal | undefined;\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.#signal) this.#bind();\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\tthis.#signal = signal;\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.#signal = undefined;\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.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.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.land((target, direction) => this.#land(element, target, direction));\n\n\t\treturn { element, link, seen: link.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 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.limit(\n\t\t\t\tisLocked(member)\n\t\t\t\t\t? [value, value]\n\t\t\t\t\t: gridLimits(member.link.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.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.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 { element } of changed) {\n\t\t\t\t\telement.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t}\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.land(undefined);\n\t\t\tlink.limit(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.#signal) 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.value()) continue;\n\n\t\t\t\tmember.link.limit(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.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\n// eslint-disable-next-line unicorn/consistent-boolean-name -- the name bundlers and other kits use\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#signal: AbortSignal | undefined;\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.#signal) this.#bind();\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\tthis.#signal = signal;\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.#signal = undefined;\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 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\tisLocked(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 { element } of changed) {\n\t\t\t\t\telement.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t\t}\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.#signal) 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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"}
@@ -10,9 +10,9 @@ export declare class SonicSwitch extends SonicPositionGroupElement {
10
10
  protected readonly bare: HTMLButtonElement;
11
11
  protected readonly defaultOrientation = "vertical";
12
12
  protected readonly group: HTMLDivElement;
13
- protected claimPress(option: HTMLButtonElement, event: PointerEvent): boolean;
13
+ protected claimPress(index: number, event: PointerEvent): boolean;
14
14
  protected connect(signal: AbortSignal): void;
15
- protected releaseTarget(option: HTMLButtonElement): HTMLButtonElement | undefined;
15
+ protected releaseTarget(index: number): number | undefined;
16
16
  protected render(): void;
17
17
  protected renderOption(): HTMLButtonElement;
18
18
  }
@@ -1,6 +1,7 @@
1
1
  import { requireChild, template } from "../lib/render.js";
2
2
  import { bindDrag } from "../lib/pointer-drag.js";
3
- import { isUnder, optionValue } from "./radio-group.js";
3
+ import { soleStep } from "../lib/positions.js";
4
+ import { isUnder } from "./radio-group.js";
4
5
  import { SonicPositionGroupElement } from "./position-group.js";
5
6
 
6
7
  //#region src/elements/switch.ts
@@ -23,9 +24,9 @@ var SonicSwitch = class extends SonicPositionGroupElement {
23
24
  group = renderSwitch();
24
25
  #bat = requireChild(this.group, ".sonic-switch-bat", HTMLSpanElement);
25
26
  #isClickSwallowed = false;
26
- claimPress(option, event) {
27
+ claimPress(index, event) {
27
28
  if (this.#isOnBat(event)) return true;
28
- const target = this.pressTarget(option);
29
+ const target = this.pressTarget(index);
29
30
  if (this.isOptionDisabled(target) || !this.isMomentary(target)) return false;
30
31
  this.group.setPointerCapture(event.pointerId);
31
32
  this.hold(target, event.pointerId);
@@ -45,20 +46,14 @@ var SonicSwitch = class extends SonicPositionGroupElement {
45
46
  }, { signal });
46
47
  this.#bindBat(group, signal);
47
48
  }
48
- releaseTarget(option) {
49
- const target = this.pressTarget(option);
49
+ releaseTarget(index) {
50
+ const target = this.pressTarget(index);
50
51
  return this.isMomentary(target) || this.isOptionDisabled(target) ? void 0 : target;
51
52
  }
52
53
  render() {
53
54
  if (!this.isBound()) return;
54
55
  super.render();
55
- const options = this.options();
56
- if (options.length > 0) {
57
- this.#renderThrow(this.checkedIndex(options));
58
- return;
59
- }
60
- const onSide = this.orientation === "horizontal" ? 1 : -1;
61
- this.group.style.setProperty("--_sonic-switch-at", String(this.checked ? onSide : -onSide));
56
+ this.#renderThrow(this.checkedIndex());
62
57
  }
63
58
  renderOption() {
64
59
  return renderPosition();
@@ -66,7 +61,7 @@ var SonicSwitch = class extends SonicPositionGroupElement {
66
61
  #bindBat(group, signal) {
67
62
  bindDrag(group, {
68
63
  grab: (event) => {
69
- if (!(event.target === this.bare || this.optionOf(event.target) !== void 0) || this.isDisabled() || !this.#isOnBat(event)) return;
64
+ if (!(this.pressedIndex(event.target) >= 0) || this.isDisabled() || !this.#isOnBat(event)) return;
70
65
  return {
71
66
  hasMoved: false,
72
67
  last: this.along(event),
@@ -87,51 +82,41 @@ var SonicSwitch = class extends SonicPositionGroupElement {
87
82
  }
88
83
  }, signal);
89
84
  }
90
- #hasSprungFrom(option) {
91
- if (!option || !this.isMomentary(option)) return false;
85
+ #hasSprungFrom(index) {
86
+ if (!this.isMomentary(index)) return false;
92
87
  this.releaseHold();
93
- return this.value !== optionValue(option);
88
+ return !this.isChecked(index);
94
89
  }
95
90
  #isOnBat(event) {
96
91
  return isUnder(this.#bat, event.clientX, event.clientY);
97
92
  }
98
93
  #pressBat(event) {
99
- const options = this.options();
100
- if (options.length === 0) {
101
- this.setChecked(!this.checked);
102
- return;
103
- }
104
- const box = this.#bat.getBoundingClientRect();
105
- const [start, size] = this.orientation === "horizontal" ? [box.left, box.width] : [box.top, box.height];
106
- const offset = (this.along(event) - start) / size - .5;
107
- const index = this.checkedIndex(options);
108
- const side = Math.abs(offset) < pressCenterZone ? 1 : Math.sign(offset);
109
- const toward = this.stepFrom(index, side);
110
- const option = toward && toward !== options[index] ? toward : this.stepFrom(index, -side);
111
- if (!option) return;
112
- this.chooseByClick(option);
113
- this.#refocus(options);
114
- }
115
- #refocus(options) {
116
- if (this.group.matches(":focus-within")) options[this.checkedIndex(options)]?.focus();
94
+ const from = this.checkedIndex();
95
+ const target = soleStep(this.positions(), from) ?? this.#sideStep(from, event);
96
+ if (target === void 0) return;
97
+ this.chooseByClick(target);
98
+ this.refocusAt(this.checkedIndex());
117
99
  }
118
100
  #renderThrow(index) {
119
- const count = this.options().length;
101
+ const count = this.positions().length;
120
102
  const at = index < 0 || count < 2 ? 0 : 2 * index / (count - 1) - 1;
121
103
  this.group.style.setProperty("--_sonic-switch-at", String(at));
122
104
  }
105
+ #sideStep(from, event) {
106
+ const box = this.#bat.getBoundingClientRect();
107
+ const [start, size] = this.orientation === "horizontal" ? [box.left, box.width] : [box.top, box.height];
108
+ const offset = (this.along(event) - start) / size - .5;
109
+ const side = Math.abs(offset) < pressCenterZone ? 1 : Math.sign(offset);
110
+ const toward = this.stepFrom(from, side);
111
+ return toward !== void 0 && toward !== from ? toward : this.stepFrom(from, -side);
112
+ }
123
113
  #throwBy(direction, pointerId) {
124
- const options = this.options();
125
- if (options.length === 0) {
126
- this.setChecked(direction === (this.orientation === "horizontal" ? 1 : -1));
127
- return;
128
- }
129
- const index = this.checkedIndex(options);
130
- const option = this.stepFrom(index, direction);
131
- if (!option || option === options[index] || this.#hasSprungFrom(options[index])) return;
132
- if (this.isMomentary(option)) this.hold(option, pointerId);
133
- else this.select(option);
134
- this.#refocus(options);
114
+ const from = this.checkedIndex();
115
+ const target = this.stepFrom(from, direction);
116
+ if (target === void 0 || target === from || this.#hasSprungFrom(from)) return;
117
+ if (this.isMomentary(target)) this.hold(target, pointerId);
118
+ else this.select(target);
119
+ this.refocusAt(this.checkedIndex());
135
120
  }
136
121
  };
137
122
 
@@ -1 +1 @@
1
- {"version":3,"file":"switch.js","names":[],"sources":["../../../src/elements/switch.ts"],"sourcesContent":["import { SonicPositionGroupElement } from '#elements/position-group.ts';\nimport { isUnder, optionValue } from '#elements/radio-group.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-switch': SonicSwitch;\n\t}\n}\n\nconst renderSwitch = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-switch\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-switch-bushing\"></span>\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-switch-bat\"></span>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst renderPosition = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-switch-position\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-switch-label\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst renderBare = template(\n\t/* HTML */ `<button class=\"sonic-switch-position\" role=\"switch\" type=\"button\"></button>`,\n\tHTMLButtonElement,\n);\n\ninterface BatPress {\n\thasMoved: boolean;\n\tlast: number;\n\twidth: number;\n}\n\nconst pressCenterZone = 0.08;\n\nexport class SonicSwitch extends SonicPositionGroupElement {\n\tprotected readonly bare = renderBare();\n\n\tprotected readonly defaultOrientation = 'vertical';\n\n\tprotected readonly group = renderSwitch();\n\n\treadonly #bat = requireChild(this.group, '.sonic-switch-bat', HTMLSpanElement);\n\n\t#isClickSwallowed = false;\n\n\tprotected override claimPress(option: HTMLButtonElement, event: PointerEvent): boolean {\n\t\tif (this.#isOnBat(event)) return true;\n\n\t\tconst target = this.pressTarget(option);\n\t\tif (this.isOptionDisabled(target) || !this.isMomentary(target)) return false;\n\n\t\tthis.group.setPointerCapture(event.pointerId);\n\t\tthis.hold(target, event.pointerId);\n\n\t\treturn true;\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tconst group = this.group;\n\n\t\tsuper.connect(signal);\n\t\tthis.checkStyles(group, 'switch.css', ['cap']);\n\n\t\tthis.bare.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tconst isSwallowed =\n\t\t\t\t\tthis.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== '';\n\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t\tif (!isSwallowed) this.setChecked(!this.checked);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bare.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t() => {\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindBat(group, signal);\n\t}\n\n\tprotected override releaseTarget(option: HTMLButtonElement): HTMLButtonElement | undefined {\n\t\tconst target = this.pressTarget(option);\n\n\t\treturn this.isMomentary(target) || this.isOptionDisabled(target) ? undefined : target;\n\t}\n\n\tprotected override render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tsuper.render();\n\n\t\tconst options = this.options();\n\t\tif (options.length > 0) {\n\t\t\tthis.#renderThrow(this.checkedIndex(options));\n\t\t\treturn;\n\t\t}\n\n\t\tconst onSide = this.orientation === 'horizontal' ? 1 : -1;\n\n\t\tthis.group.style.setProperty('--_sonic-switch-at', String(this.checked ? onSide : -onSide));\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderPosition();\n\t}\n\n\t#bindBat(group: HTMLElement, signal: AbortSignal): void {\n\t\tbindDrag<BatPress>(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tconst isOnPosition =\n\t\t\t\t\t\tevent.target === this.bare || this.optionOf(event.target) !== undefined;\n\t\t\t\t\tif (!isOnPosition || this.isDisabled() || !this.#isOnBat(event)) return;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\thasMoved: false,\n\t\t\t\t\t\tlast: this.along(event),\n\t\t\t\t\t\twidth: this.#bat.getBoundingClientRect().width,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\tlift: (press, event) => {\n\t\t\t\t\tif (!press.hasMoved && !this.isDisabled()) this.#pressBat(event);\n\t\t\t\t\tthis.#isClickSwallowed = true;\n\t\t\t\t},\n\t\t\t\tmove: (press, event) => {\n\t\t\t\t\tif (this.isDisabled()) return;\n\n\t\t\t\t\tconst delta = this.along(event) - press.last;\n\t\t\t\t\tif (Math.abs(delta) < press.width / 4) return;\n\n\t\t\t\t\tpress.hasMoved = true;\n\t\t\t\t\tpress.last = this.along(event);\n\t\t\t\t\tthis.#throwBy(Math.sign(delta), event.pointerId);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#hasSprungFrom(option: HTMLButtonElement | undefined): boolean {\n\t\tif (!option || !this.isMomentary(option)) return false;\n\n\t\tthis.releaseHold();\n\n\t\treturn this.value !== optionValue(option);\n\t}\n\n\t#isOnBat(event: PointerEvent): boolean {\n\t\treturn isUnder(this.#bat, event.clientX, event.clientY);\n\t}\n\n\t#pressBat(event: PointerEvent): void {\n\t\tconst options = this.options();\n\t\tif (options.length === 0) {\n\t\t\tthis.setChecked(!this.checked);\n\t\t\treturn;\n\t\t}\n\n\t\tconst box = this.#bat.getBoundingClientRect();\n\t\tconst [start, size] =\n\t\t\tthis.orientation === 'horizontal' ? [box.left, box.width] : [box.top, box.height];\n\t\tconst offset = (this.along(event) - start) / size - 0.5;\n\t\tconst index = this.checkedIndex(options);\n\t\tconst side = Math.abs(offset) < pressCenterZone ? 1 : Math.sign(offset);\n\t\tconst toward = this.stepFrom(index, side);\n\t\tconst option = toward && toward !== options[index] ? toward : this.stepFrom(index, -side);\n\t\tif (!option) return;\n\n\t\tthis.chooseByClick(option);\n\t\tthis.#refocus(options);\n\t}\n\n\t#refocus(options: Array<HTMLButtonElement>): void {\n\t\tif (this.group.matches(':focus-within')) options[this.checkedIndex(options)]?.focus();\n\t}\n\n\t#renderThrow(index: number): void {\n\t\tconst count = this.options().length;\n\t\tconst at = index < 0 || count < 2 ? 0 : (2 * index) / (count - 1) - 1;\n\n\t\tthis.group.style.setProperty('--_sonic-switch-at', String(at));\n\t}\n\n\t#throwBy(direction: number, pointerId: number): void {\n\t\tconst options = this.options();\n\t\tif (options.length === 0) {\n\t\t\tthis.setChecked(direction === (this.orientation === 'horizontal' ? 1 : -1));\n\t\t\treturn;\n\t\t}\n\n\t\tconst index = this.checkedIndex(options);\n\t\tconst option = this.stepFrom(index, direction);\n\t\tif (!option || option === options[index] || this.#hasSprungFrom(options[index])) return;\n\n\t\tif (this.isMomentary(option)) this.hold(option, pointerId);\n\t\telse this.select(option);\n\t\tthis.#refocus(options);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"switch.js","names":[],"sources":["../../../src/elements/switch.ts"],"sourcesContent":["import { SonicPositionGroupElement } from '#elements/position-group.ts';\nimport { isUnder } from '#elements/radio-group.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { soleStep } from '#lib/positions.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-switch': SonicSwitch;\n\t}\n}\n\nconst renderSwitch = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-switch\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-switch-bushing\"></span>\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-switch-bat\"></span>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst renderPosition = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-switch-position\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-switch-label\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst renderBare = template(\n\t/* HTML */ `<button class=\"sonic-switch-position\" role=\"switch\" type=\"button\"></button>`,\n\tHTMLButtonElement,\n);\n\ninterface BatPress {\n\thasMoved: boolean;\n\tlast: number;\n\twidth: number;\n}\n\nconst pressCenterZone = 0.08;\n\nexport class SonicSwitch extends SonicPositionGroupElement {\n\tprotected readonly bare = renderBare();\n\n\tprotected readonly defaultOrientation = 'vertical';\n\n\tprotected readonly group = renderSwitch();\n\n\treadonly #bat = requireChild(this.group, '.sonic-switch-bat', HTMLSpanElement);\n\n\t#isClickSwallowed = false;\n\n\tprotected override claimPress(index: number, event: PointerEvent): boolean {\n\t\tif (this.#isOnBat(event)) return true;\n\n\t\tconst target = this.pressTarget(index);\n\t\tif (this.isOptionDisabled(target) || !this.isMomentary(target)) return false;\n\n\t\tthis.group.setPointerCapture(event.pointerId);\n\t\tthis.hold(target, event.pointerId);\n\n\t\treturn true;\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tconst group = this.group;\n\n\t\tsuper.connect(signal);\n\t\tthis.checkStyles(group, 'switch.css', ['cap']);\n\n\t\tthis.bare.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tconst isSwallowed =\n\t\t\t\t\tthis.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== '';\n\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t\tif (!isSwallowed) this.setChecked(!this.checked);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bare.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t() => {\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindBat(group, signal);\n\t}\n\n\tprotected override releaseTarget(index: number): number | undefined {\n\t\tconst target = this.pressTarget(index);\n\n\t\treturn this.isMomentary(target) || this.isOptionDisabled(target) ? undefined : target;\n\t}\n\n\tprotected override render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tsuper.render();\n\t\tthis.#renderThrow(this.checkedIndex());\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderPosition();\n\t}\n\n\t#bindBat(group: HTMLElement, signal: AbortSignal): void {\n\t\tbindDrag<BatPress>(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tconst isOnPosition = this.pressedIndex(event.target) >= 0;\n\t\t\t\t\tif (!isOnPosition || this.isDisabled() || !this.#isOnBat(event)) return;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\thasMoved: false,\n\t\t\t\t\t\tlast: this.along(event),\n\t\t\t\t\t\twidth: this.#bat.getBoundingClientRect().width,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\tlift: (press, event) => {\n\t\t\t\t\tif (!press.hasMoved && !this.isDisabled()) this.#pressBat(event);\n\t\t\t\t\tthis.#isClickSwallowed = true;\n\t\t\t\t},\n\t\t\t\tmove: (press, event) => {\n\t\t\t\t\tif (this.isDisabled()) return;\n\n\t\t\t\t\tconst delta = this.along(event) - press.last;\n\t\t\t\t\tif (Math.abs(delta) < press.width / 4) return;\n\n\t\t\t\t\tpress.hasMoved = true;\n\t\t\t\t\tpress.last = this.along(event);\n\t\t\t\t\tthis.#throwBy(Math.sign(delta), event.pointerId);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#hasSprungFrom(index: number): boolean {\n\t\tif (!this.isMomentary(index)) return false;\n\n\t\tthis.releaseHold();\n\n\t\treturn !this.isChecked(index);\n\t}\n\n\t#isOnBat(event: PointerEvent): boolean {\n\t\treturn isUnder(this.#bat, event.clientX, event.clientY);\n\t}\n\n\t#pressBat(event: PointerEvent): void {\n\t\tconst from = this.checkedIndex();\n\t\tconst target = soleStep(this.positions(), from) ?? this.#sideStep(from, event);\n\t\tif (target === undefined) return;\n\n\t\tthis.chooseByClick(target);\n\t\tthis.refocusAt(this.checkedIndex());\n\t}\n\n\t#renderThrow(index: number): void {\n\t\tconst count = this.positions().length;\n\t\tconst at = index < 0 || count < 2 ? 0 : (2 * index) / (count - 1) - 1;\n\n\t\tthis.group.style.setProperty('--_sonic-switch-at', String(at));\n\t}\n\n\t#sideStep(from: number, event: PointerEvent): number | undefined {\n\t\tconst box = this.#bat.getBoundingClientRect();\n\t\tconst [start, size] =\n\t\t\tthis.orientation === 'horizontal' ? [box.left, box.width] : [box.top, box.height];\n\t\tconst offset = (this.along(event) - start) / size - 0.5;\n\t\tconst side = Math.abs(offset) < pressCenterZone ? 1 : Math.sign(offset);\n\t\tconst toward = this.stepFrom(from, side);\n\n\t\treturn toward !== undefined && toward !== from ? toward : this.stepFrom(from, -side);\n\t}\n\n\t#throwBy(direction: number, pointerId: number): void {\n\t\tconst from = this.checkedIndex();\n\t\tconst target = this.stepFrom(from, direction);\n\t\tif (target === undefined || target === from || this.#hasSprungFrom(from)) return;\n\n\t\tif (this.isMomentary(target)) this.hold(target, pointerId);\n\t\telse this.select(target);\n\t\tthis.refocusAt(this.checkedIndex());\n\t}\n}\n"],"mappings":";;;;;;;AAYA,MAAM,eAAe,SACT;;;;;IAMX,cACD;AAEA,MAAM,iBAAiB,SACX;;;;IAKX,iBACD;AAEA,MAAM,aAAa,SACP,+EACX,iBACD;AAQA,MAAM,kBAAkB;AAExB,IAAa,cAAb,cAAiC,0BAA0B;CAC1D,AAAmB,OAAO,WAAW;CAErC,AAAmB,qBAAqB;CAExC,AAAmB,QAAQ,aAAa;CAExC,AAAS,OAAO,aAAa,KAAK,OAAO,qBAAqB,eAAe;CAE7E,oBAAoB;CAEpB,AAAmB,WAAW,OAAe,OAA8B;EAC1E,IAAI,KAAK,SAAS,KAAK,GAAG,OAAO;EAEjC,MAAM,SAAS,KAAK,YAAY,KAAK;EACrC,IAAI,KAAK,iBAAiB,MAAM,KAAK,CAAC,KAAK,YAAY,MAAM,GAAG,OAAO;EAEvE,KAAK,MAAM,kBAAkB,MAAM,SAAS;EAC5C,KAAK,KAAK,QAAQ,MAAM,SAAS;EAEjC,OAAO;CACR;CAEA,AAAmB,QAAQ,QAA2B;EACrD,MAAM,QAAQ,KAAK;EAEnB,MAAM,QAAQ,MAAM;EACpB,KAAK,YAAY,OAAO,cAAc,CAAC,KAAK,CAAC;EAE7C,KAAK,KAAK,iBACT,UACC,UAAU;GACV,MAAM,cACL,KAAK,qBAAqB,iBAAiB,gBAAgB,MAAM,gBAAgB;GAElF,KAAK,oBAAoB;GACzB,IAAI,CAAC,aAAa,KAAK,WAAW,CAAC,KAAK,OAAO;EAChD,GACA,EAAE,OAAO,CACV;EACA,KAAK,KAAK,iBACT,qBACM;GACL,KAAK,oBAAoB;EAC1B,GACA,EAAE,OAAO,CACV;EACA,KAAK,SAAS,OAAO,MAAM;CAC5B;CAEA,AAAmB,cAAc,OAAmC;EACnE,MAAM,SAAS,KAAK,YAAY,KAAK;EAErC,OAAO,KAAK,YAAY,MAAM,KAAK,KAAK,iBAAiB,MAAM,IAAI,SAAY;CAChF;CAEA,AAAmB,SAAe;EACjC,IAAI,CAAC,KAAK,QAAQ,GAAG;EAErB,MAAM,OAAO;EACb,KAAK,aAAa,KAAK,aAAa,CAAC;CACtC;CAEA,AAAU,eAAkC;EAC3C,OAAO,eAAe;CACvB;CAEA,SAAS,OAAoB,QAA2B;EACvD,SACC,OACA;GACC,OAAO,UAAU;IAEhB,IAAI,EADiB,KAAK,aAAa,MAAM,MAAM,KAAK,MACnC,KAAK,WAAW,KAAK,CAAC,KAAK,SAAS,KAAK,GAAG;IAEjE,OAAO;KACN,UAAU;KACV,MAAM,KAAK,MAAM,KAAK;KACtB,OAAO,KAAK,KAAK,sBAAsB,CAAC,CAAC;IAC1C;GACD;GACA,OAAO,OAAO,UAAU;IACvB,IAAI,CAAC,MAAM,YAAY,CAAC,KAAK,WAAW,GAAG,KAAK,UAAU,KAAK;IAC/D,KAAK,oBAAoB;GAC1B;GACA,OAAO,OAAO,UAAU;IACvB,IAAI,KAAK,WAAW,GAAG;IAEvB,MAAM,QAAQ,KAAK,MAAM,KAAK,IAAI,MAAM;IACxC,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,QAAQ,GAAG;IAEvC,MAAM,WAAW;IACjB,MAAM,OAAO,KAAK,MAAM,KAAK;IAC7B,KAAK,SAAS,KAAK,KAAK,KAAK,GAAG,MAAM,SAAS;GAChD;EACD,GACA,MACD;CACD;CAEA,eAAe,OAAwB;EACtC,IAAI,CAAC,KAAK,YAAY,KAAK,GAAG,OAAO;EAErC,KAAK,YAAY;EAEjB,OAAO,CAAC,KAAK,UAAU,KAAK;CAC7B;CAEA,SAAS,OAA8B;EACtC,OAAO,QAAQ,KAAK,MAAM,MAAM,SAAS,MAAM,OAAO;CACvD;CAEA,UAAU,OAA2B;EACpC,MAAM,OAAO,KAAK,aAAa;EAC/B,MAAM,SAAS,SAAS,KAAK,UAAU,GAAG,IAAI,KAAK,KAAK,UAAU,MAAM,KAAK;EAC7E,IAAI,WAAW,QAAW;EAE1B,KAAK,cAAc,MAAM;EACzB,KAAK,UAAU,KAAK,aAAa,CAAC;CACnC;CAEA,aAAa,OAAqB;EACjC,MAAM,QAAQ,KAAK,UAAU,CAAC,CAAC;EAC/B,MAAM,KAAK,QAAQ,KAAK,QAAQ,IAAI,IAAK,IAAI,SAAU,QAAQ,KAAK;EAEpE,KAAK,MAAM,MAAM,YAAY,sBAAsB,OAAO,EAAE,CAAC;CAC9D;CAEA,UAAU,MAAc,OAAyC;EAChE,MAAM,MAAM,KAAK,KAAK,sBAAsB;EAC5C,MAAM,CAAC,OAAO,QACb,KAAK,gBAAgB,eAAe,CAAC,IAAI,MAAM,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,MAAM;EACjF,MAAM,UAAU,KAAK,MAAM,KAAK,IAAI,SAAS,OAAO;EACpD,MAAM,OAAO,KAAK,IAAI,MAAM,IAAI,kBAAkB,IAAI,KAAK,KAAK,MAAM;EACtE,MAAM,SAAS,KAAK,SAAS,MAAM,IAAI;EAEvC,OAAO,WAAW,UAAa,WAAW,OAAO,SAAS,KAAK,SAAS,MAAM,CAAC,IAAI;CACpF;CAEA,SAAS,WAAmB,WAAyB;EACpD,MAAM,OAAO,KAAK,aAAa;EAC/B,MAAM,SAAS,KAAK,SAAS,MAAM,SAAS;EAC5C,IAAI,WAAW,UAAa,WAAW,QAAQ,KAAK,eAAe,IAAI,GAAG;EAE1E,IAAI,KAAK,YAAY,MAAM,GAAG,KAAK,KAAK,QAAQ,SAAS;OACpD,KAAK,OAAO,MAAM;EACvB,KAAK,UAAU,KAAK,aAAa,CAAC;CACnC;AACD"}
@@ -1,7 +1,8 @@
1
1
  import { placeChildren, requireChild, template } from "../lib/render.js";
2
2
  import { bindDrag } from "../lib/pointer-drag.js";
3
3
  import { dragThresholdPx } from "../lib/drag-step.js";
4
- import { isUnder, optionValue } from "./radio-group.js";
4
+ import { enabledEnds } from "../lib/positions.js";
5
+ import { isUnder } from "./radio-group.js";
5
6
  import { SonicPositionGroupElement } from "./position-group.js";
6
7
  import { positionAt, travelFrom } from "../lib/toggle-travel.js";
7
8
 
@@ -53,7 +54,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
53
54
  render() {
54
55
  if (!this.isBound()) return;
55
56
  this.#checkLegends();
56
- this.group.style.setProperty("--_sonic-toggle-count", String(this.#stopCount()));
57
+ this.group.style.setProperty("--_sonic-toggle-count", String(this.positions().length));
57
58
  this.#placeCap(this.#draggedTo ?? this.#restingAt());
58
59
  super.render();
59
60
  for (const [index, option] of this.options().entries()) option.style.setProperty("--_sonic-toggle-index", String(index));
@@ -78,7 +79,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
78
79
  this.#slideTo(positionAt(drag.travel, pointer), event.pointerId);
79
80
  },
80
81
  release: (drag) => {
81
- delete (drag.position ?? this.bare).dataset.sonicPressed;
82
+ delete drag.marked.dataset.sonicPressed;
82
83
  this.#draggedTo = void 0;
83
84
  this.toggleState("dragging", false);
84
85
  this.render();
@@ -92,25 +93,21 @@ var SonicToggle = class extends SonicPositionGroupElement {
92
93
  this.#hasWarnedOfLegends = true;
93
94
  console.warn(`<sonic-toggle> lays out legends="outside" for two or three positions, and this one has ${String(count)}`);
94
95
  }
95
- #enabledEnds() {
96
- if (this.isBare()) return [0, 1];
97
- const options = this.options();
98
- const isEnabled = (option) => !this.isOptionDisabled(option);
99
- return [options.findIndex(isEnabled), options.findLastIndex(isEnabled)];
100
- }
101
96
  #fixedLegend() {
102
97
  return [...this.children].find((child) => child !== this.group && !super.isMirrored(child));
103
98
  }
104
99
  #grab(event) {
105
- const position = this.optionOf(event.target);
106
- if (this.isDisabled() || !position && event.target !== this.bare) return void 0;
100
+ const position = this.pressedIndex(event.target);
101
+ if (this.isDisabled() || position < 0) return void 0;
107
102
  const isOnCap = isUnder(this.#cap, event.clientX, event.clientY);
108
103
  const travel = this.#travel(event, isOnCap);
109
- if (!travel || this.#isBlocked(position, isOnCap)) return void 0;
110
- (position ?? this.bare).toggleAttribute("data-sonic-pressed", true);
104
+ if (!travel || !isOnCap && this.isOptionDisabled(position)) return void 0;
105
+ const marked = this.options()[position] ?? this.bare;
106
+ marked.toggleAttribute("data-sonic-pressed", true);
111
107
  return {
112
108
  hasMoved: false,
113
109
  isHolding: this.#holdFrom(position, event.pointerId),
110
+ marked,
114
111
  position,
115
112
  start: this.along(event),
116
113
  thresholdPx: dragThresholdPx(event.pointerType),
@@ -118,57 +115,36 @@ var SonicToggle = class extends SonicPositionGroupElement {
118
115
  };
119
116
  }
120
117
  #holdFrom(position, pointerId) {
121
- const target = position && this.pressTarget(position);
122
- if (!target || !this.isMomentary(target) || this.isOptionDisabled(target)) return false;
118
+ const target = this.pressTarget(position);
119
+ if (!this.isMomentary(target) || this.isOptionDisabled(target)) return false;
123
120
  this.hold(target, pointerId);
124
121
  return true;
125
122
  }
126
- #isBlocked(position, isOnCap) {
127
- return !isOnCap && position !== void 0 && this.isOptionDisabled(position);
128
- }
129
- #onAt() {
130
- return this.orientation === "vertical" ? 0 : 1;
131
- }
132
123
  #placeCap(at) {
133
124
  this.group.style.setProperty("--_sonic-toggle-at", String(at));
134
125
  }
135
126
  #press(position) {
136
- if (!position) {
137
- this.setChecked(!this.checked);
138
- return;
139
- }
140
127
  const target = this.pressTarget(position);
141
128
  if (this.isOptionDisabled(target)) return;
142
129
  this.select(target);
143
- this.#refocus(target);
144
- }
145
- #refocus(position) {
146
- if (this.group.matches(":focus-within")) position.focus();
130
+ this.refocusAt(target);
147
131
  }
148
132
  #restingAt() {
149
- if (this.isBare()) return this.checked ? this.#onAt() : 1 - this.#onAt();
150
- return Math.max(0, this.checkedIndex(this.options()));
133
+ return Math.max(0, this.checkedIndex());
151
134
  }
152
135
  #slideTo(at, pointerId) {
153
- const crossed = this.options()[Math.round(at)];
136
+ const crossed = Math.round(at);
154
137
  this.#draggedTo = at;
155
138
  this.#placeCap(at);
156
- if (this.isBare()) {
157
- this.setChecked(Math.round(at) === this.#onAt());
158
- return;
159
- }
160
- if (!crossed || optionValue(crossed) === this.value) return;
139
+ if (this.isChecked(crossed)) return;
161
140
  this.releaseHold();
162
- if (this.isOptionDisabled(crossed) || optionValue(crossed) === this.value) return;
141
+ if (this.isOptionDisabled(crossed) || this.isChecked(crossed)) return;
163
142
  if (this.isMomentary(crossed)) this.hold(crossed, pointerId);
164
143
  else this.select(crossed);
165
- this.#refocus(crossed);
166
- }
167
- #stopCount() {
168
- return this.options().length || 2;
144
+ this.refocusAt(crossed);
169
145
  }
170
146
  #travel(event, isOnCap) {
171
- const [first, last] = this.#enabledEnds();
147
+ const [first, last] = enabledEnds(this.positions());
172
148
  if (first < 0) return void 0;
173
149
  const box = this.#well.getBoundingClientRect();
174
150
  const [start, length, thickness] = this.orientation === "vertical" ? [
@@ -182,7 +158,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
182
158
  ];
183
159
  return travelFrom({
184
160
  at: this.#restingAt(),
185
- count: this.#stopCount(),
161
+ count: this.positions().length,
186
162
  first,
187
163
  isOnCap,
188
164
  last,
@@ -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, optionValue } from '#elements/radio-group.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { positionAt, travelFrom } from '#lib/toggle-travel.ts';\n\n// eslint-disable-next-line unicorn/consistent-boolean-name -- the name bundlers and other kits use\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\ninterface CapDrag {\n\thasMoved: boolean;\n\tisHolding: boolean;\n\tposition: HTMLButtonElement | undefined;\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\tthis.checkStyles(this.group, 'toggle.css', ['cap', 'well', 'keycap']);\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.#stopCount()));\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.hasMoved || 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\tif (!drag.hasMoved && Math.abs(pointer - drag.start) < 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\tthis.#slideTo(positionAt(drag.travel, pointer), event.pointerId);\n\t\t\t\t},\n\t\t\t\trelease: (drag) => {\n\t\t\t\t\tdelete (drag.position ?? this.bare).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#enabledEnds(): [number, number] {\n\t\tif (this.isBare()) return [0, 1];\n\n\t\tconst options = this.options();\n\t\tconst isEnabled = (option: HTMLButtonElement): boolean => !this.isOptionDisabled(option);\n\n\t\treturn [options.findIndex(isEnabled), options.findLastIndex(isEnabled)];\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.optionOf(event.target);\n\t\tif (this.isDisabled() || (!position && event.target !== this.bare)) 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 || this.#isBlocked(position, isOnCap)) return undefined;\n\n\t\t(position ?? this.bare).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\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: HTMLButtonElement | undefined, pointerId: number): boolean {\n\t\tconst target = position && this.pressTarget(position);\n\t\tif (!target || !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#isBlocked(position: HTMLButtonElement | undefined, isOnCap: boolean): boolean {\n\t\treturn !isOnCap && position !== undefined && this.isOptionDisabled(position);\n\t}\n\n\t// On is up, as on hardware\n\t#onAt(): number {\n\t\treturn this.orientation === 'vertical' ? 0 : 1;\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: HTMLButtonElement | undefined): void {\n\t\tif (!position) {\n\t\t\tthis.setChecked(!this.checked);\n\t\t\treturn;\n\t\t}\n\n\t\tconst target = this.pressTarget(position);\n\t\tif (this.isOptionDisabled(target)) return;\n\n\t\tthis.select(target);\n\t\tthis.#refocus(target);\n\t}\n\n\t#refocus(position: HTMLButtonElement): void {\n\t\tif (this.group.matches(':focus-within')) position.focus();\n\t}\n\n\t#restingAt(): number {\n\t\tif (this.isBare()) return this.checked ? this.#onAt() : 1 - this.#onAt();\n\n\t\treturn Math.max(0, this.checkedIndex(this.options()));\n\t}\n\n\t#slideTo(at: number, pointerId: number): void {\n\t\tconst crossed = this.options()[Math.round(at)];\n\n\t\tthis.#draggedTo = at;\n\t\tthis.#placeCap(at);\n\t\tif (this.isBare()) {\n\t\t\tthis.setChecked(Math.round(at) === this.#onAt());\n\t\t\treturn;\n\t\t}\n\t\tif (!crossed || optionValue(crossed) === this.value) return;\n\n\t\tthis.releaseHold();\n\t\tif (this.isOptionDisabled(crossed) || optionValue(crossed) === this.value) return;\n\n\t\tif (this.isMomentary(crossed)) this.hold(crossed, pointerId);\n\t\telse this.select(crossed);\n\t\tthis.#refocus(crossed);\n\t}\n\n\t#stopCount(): number {\n\t\treturn this.options().length || 2;\n\t}\n\n\t#travel(event: PointerEvent, isOnCap: boolean): ToggleTravel | undefined {\n\t\tconst [first, last] = this.#enabledEnds();\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.#stopCount(),\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\n// eslint-disable-next-line unicorn/consistent-boolean-name -- the name bundlers and other kits use\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\ninterface CapDrag {\n\thasMoved: boolean;\n\tisHolding: 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\tthis.checkStyles(this.group, 'toggle.css', ['cap', 'well', 'keycap']);\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.hasMoved || 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\tif (!drag.hasMoved && Math.abs(pointer - drag.start) < 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\tthis.#slideTo(positionAt(drag.travel, pointer), event.pointerId);\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\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): void {\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;\n\n\t\tthis.releaseHold();\n\t\tif (this.isOptionDisabled(crossed) || this.isChecked(crossed)) return;\n\n\t\tif (this.isMomentary(crossed)) this.hold(crossed, pointerId);\n\t\telse this.select(crossed);\n\t\tthis.refocusAt(crossed);\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,6 +1,7 @@
1
1
  import { SonicFormElement } from "./form-element.js";
2
2
  import { ValueMapping } from "../lib/value-mapping.js";
3
3
  import { ValueAxis } from "./value-gestures.js";
4
+ import { ScrubState } from "../lib/scrub.js";
4
5
  //#region src/elements/value-element.d.ts
5
6
  declare global {
6
7
  interface HTMLElementEventMap {
@@ -50,11 +51,13 @@ export declare abstract class SonicValueElement extends SonicFormElement {
50
51
  get parseValue(): ((text: string) => number) | undefined;
51
52
  set parseValue(parse: ((text: string) => number) | undefined);
52
53
  get pointerType(): string | undefined;
54
+ /** The values a stepped control stops at, spaced evenly along the travel whatever their spacing */
53
55
  get positions(): Array<number> | undefined;
54
56
  set positions(positions: Array<number> | undefined);
55
57
  get readout(): boolean;
56
58
  set readout(isShown: boolean);
57
59
  get revealed(): boolean;
60
+ /** Rounds the value assistive technology hears, in value units; the value itself is untouched */
58
61
  get spokenStep(): number | undefined;
59
62
  set spokenStep(value: number | undefined);
60
63
  get step(): number;
@@ -73,21 +76,18 @@ export declare abstract class SonicValueElement extends SonicFormElement {
73
76
  protected controlRole(): 'slider' | 'spinbutton';
74
77
  protected abstract draw(): void;
75
78
  protected abstract focusTarget(): HTMLElement;
76
- protected heldFrom(): number | undefined;
77
- protected heldWrite(next: number): void;
78
- protected holdChanged(): void;
79
79
  protected input(next: number): boolean;
80
- protected isRevealed(): boolean;
81
80
  protected isWrapping(): boolean;
82
81
  protected mapping(): ValueMapping;
83
82
  protected originValue(): number;
84
- protected playback(): number;
85
83
  protected proportionsChanged(): void;
86
84
  protected readoutValue(): number;
87
85
  protected render(): void;
88
86
  protected renderReadout(): void;
89
87
  protected restoreState(state: string): void;
88
+ protected scrubChanged(): void;
90
89
  protected scrubsKeyRepeat(): boolean;
90
+ protected scrubState(): ScrubState;
91
91
  protected springTarget(): number | undefined;
92
92
  protected tapTarget(): number | undefined;
93
93
  protected travelPx(control: HTMLElement, property: `--_sonic-${string}`): number;