@xsynaptic/sonic-ui 0.17.0 → 0.19.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 (178) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +122 -19
  3. package/dist/default/elements/button.js +2 -5
  4. package/dist/default/elements/dial.js +2 -7
  5. package/dist/default/elements/display-element.js +9 -12
  6. package/dist/default/elements/envelope.js +31 -78
  7. package/dist/default/elements/field-gesture.js +81 -0
  8. package/dist/default/elements/meter.js +4 -1
  9. package/dist/default/elements/number.js +0 -7
  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/segmented.js +0 -5
  14. package/dist/default/elements/slider.js +11 -20
  15. package/dist/default/elements/sonic-element.js +3 -1
  16. package/dist/default/elements/split.js +14 -13
  17. package/dist/default/elements/switch.js +31 -47
  18. package/dist/default/elements/toggle.js +20 -49
  19. package/dist/default/elements/value-element.js +72 -94
  20. package/dist/default/elements/value-gestures.js +22 -22
  21. package/dist/default/elements/wave-element.js +16 -20
  22. package/dist/default/elements/waveform.js +42 -241
  23. package/dist/default/elements/wavestrip.js +10 -5
  24. package/dist/default/elements/xy.js +67 -119
  25. package/dist/default/index.js +2 -1
  26. package/dist/default/lib/canvas-surface.js +25 -0
  27. package/dist/default/lib/clock.js +23 -0
  28. package/dist/default/lib/crossfade.js +1 -0
  29. package/dist/default/lib/envelope-curve.js +1 -0
  30. package/dist/default/lib/peaks-request.js +44 -0
  31. package/dist/default/lib/positions.js +58 -0
  32. package/dist/default/lib/scrub.js +12 -0
  33. package/dist/default/lib/spoken-duration.js +30 -0
  34. package/dist/default/lib/value-model.js +62 -0
  35. package/dist/default/lib/waveform-buckets.js +7 -1
  36. package/dist/default/lib/waveform-scene.js +108 -0
  37. package/dist/default/lib/zoom-gesture.js +97 -1
  38. package/dist/dev/elements/button.d.ts +2 -0
  39. package/dist/dev/elements/button.js +14 -5
  40. package/dist/dev/elements/button.js.map +1 -1
  41. package/dist/dev/elements/dial.d.ts +2 -0
  42. package/dist/dev/elements/dial.js +16 -7
  43. package/dist/dev/elements/dial.js.map +1 -1
  44. package/dist/dev/elements/display-element.d.ts +5 -3
  45. package/dist/dev/elements/display-element.js +11 -9
  46. package/dist/dev/elements/display-element.js.map +1 -1
  47. package/dist/dev/elements/envelope.js +48 -78
  48. package/dist/dev/elements/envelope.js.map +1 -1
  49. package/dist/dev/elements/field-gesture.js +82 -0
  50. package/dist/dev/elements/field-gesture.js.map +1 -0
  51. package/dist/dev/elements/meter.d.ts +10 -5
  52. package/dist/dev/elements/meter.js +5 -1
  53. package/dist/dev/elements/meter.js.map +1 -1
  54. package/dist/dev/elements/number.js +13 -7
  55. package/dist/dev/elements/number.js.map +1 -1
  56. package/dist/dev/elements/position-group.d.ts +12 -6
  57. package/dist/dev/elements/position-group.js +42 -26
  58. package/dist/dev/elements/position-group.js.map +1 -1
  59. package/dist/dev/elements/radio-group.d.ts +11 -7
  60. package/dist/dev/elements/radio-group.js +59 -56
  61. package/dist/dev/elements/radio-group.js.map +1 -1
  62. package/dist/dev/elements/scale.js +3 -3
  63. package/dist/dev/elements/scale.js.map +1 -1
  64. package/dist/dev/elements/segmented.js +12 -5
  65. package/dist/dev/elements/segmented.js.map +1 -1
  66. package/dist/dev/elements/slider.d.ts +4 -2
  67. package/dist/dev/elements/slider.js +25 -20
  68. package/dist/dev/elements/slider.js.map +1 -1
  69. package/dist/dev/elements/sonic-element.d.ts +13 -2
  70. package/dist/dev/elements/sonic-element.js +63 -11
  71. package/dist/dev/elements/sonic-element.js.map +1 -1
  72. package/dist/dev/elements/split.d.ts +1 -0
  73. package/dist/dev/elements/split.js +14 -13
  74. package/dist/dev/elements/split.js.map +1 -1
  75. package/dist/dev/elements/switch.d.ts +2 -2
  76. package/dist/dev/elements/switch.js +38 -47
  77. package/dist/dev/elements/switch.js.map +1 -1
  78. package/dist/dev/elements/toggle.js +32 -49
  79. package/dist/dev/elements/toggle.js.map +1 -1
  80. package/dist/dev/elements/value-element.d.ts +11 -5
  81. package/dist/dev/elements/value-element.js +72 -94
  82. package/dist/dev/elements/value-element.js.map +1 -1
  83. package/dist/dev/elements/value-gestures.js +22 -22
  84. package/dist/dev/elements/value-gestures.js.map +1 -1
  85. package/dist/dev/elements/wave-element.d.ts +8 -5
  86. package/dist/dev/elements/wave-element.js +30 -20
  87. package/dist/dev/elements/wave-element.js.map +1 -1
  88. package/dist/dev/elements/waveform.d.ts +20 -22
  89. package/dist/dev/elements/waveform.js +42 -241
  90. package/dist/dev/elements/waveform.js.map +1 -1
  91. package/dist/dev/elements/wavestrip.d.ts +5 -1
  92. package/dist/dev/elements/wavestrip.js +10 -5
  93. package/dist/dev/elements/wavestrip.js.map +1 -1
  94. package/dist/dev/elements/xy.js +81 -119
  95. package/dist/dev/elements/xy.js.map +1 -1
  96. package/dist/dev/index.d.ts +5 -3
  97. package/dist/dev/index.js +2 -1
  98. package/dist/dev/lib/canvas-surface.d.ts +1 -0
  99. package/dist/dev/lib/canvas-surface.js +25 -0
  100. package/dist/dev/lib/canvas-surface.js.map +1 -1
  101. package/dist/dev/lib/clock.d.ts +5 -0
  102. package/dist/dev/lib/clock.js +24 -0
  103. package/dist/dev/lib/clock.js.map +1 -0
  104. package/dist/dev/lib/crossfade.d.ts +1 -0
  105. package/dist/dev/lib/crossfade.js +1 -0
  106. package/dist/dev/lib/crossfade.js.map +1 -1
  107. package/dist/dev/lib/envelope-curve.d.ts +1 -0
  108. package/dist/dev/lib/envelope-curve.js +1 -0
  109. package/dist/dev/lib/envelope-curve.js.map +1 -1
  110. package/dist/dev/lib/mirror-children.js.map +1 -1
  111. package/dist/dev/lib/owned-control.js.map +1 -1
  112. package/dist/dev/lib/peaks-request.d.ts +5 -0
  113. package/dist/dev/lib/peaks-request.js +45 -0
  114. package/dist/dev/lib/peaks-request.js.map +1 -0
  115. package/dist/dev/lib/positions.d.ts +7 -0
  116. package/dist/dev/lib/positions.js +59 -0
  117. package/dist/dev/lib/positions.js.map +1 -0
  118. package/dist/dev/lib/scrub.d.ts +8 -0
  119. package/dist/dev/lib/scrub.js +13 -0
  120. package/dist/dev/lib/scrub.js.map +1 -0
  121. package/dist/dev/lib/spoken-duration.js +31 -0
  122. package/dist/dev/lib/spoken-duration.js.map +1 -0
  123. package/dist/dev/lib/time-regions.d.ts +1 -0
  124. package/dist/dev/lib/time-regions.js.map +1 -1
  125. package/dist/dev/lib/units.d.ts +8 -0
  126. package/dist/dev/lib/value-model.js +63 -0
  127. package/dist/dev/lib/value-model.js.map +1 -0
  128. package/dist/dev/lib/waveform-buckets.d.ts +2 -1
  129. package/dist/dev/lib/waveform-buckets.js +7 -1
  130. package/dist/dev/lib/waveform-buckets.js.map +1 -1
  131. package/dist/dev/lib/waveform-scene.d.ts +11 -0
  132. package/dist/dev/lib/waveform-scene.js +109 -0
  133. package/dist/dev/lib/waveform-scene.js.map +1 -0
  134. package/dist/dev/lib/zoom-gesture.js +97 -1
  135. package/dist/dev/lib/zoom-gesture.js.map +1 -1
  136. package/dist/skins/amber.css +1 -0
  137. package/dist/skins/ivory.css +1 -0
  138. package/dist/skins/lime.css +1 -0
  139. package/dist/skins/slate.css +1 -0
  140. package/dist/styles/button.css +45 -11
  141. package/dist/styles/dial.css +10 -9
  142. package/dist/styles/envelope.css +10 -4
  143. package/dist/styles/led.css +5 -1
  144. package/dist/styles/material/arc.css +3 -2
  145. package/dist/styles/material/bracket.css +2 -2
  146. package/dist/styles/material/cap.css +33 -12
  147. package/dist/styles/material/core.css +4 -0
  148. package/dist/styles/material/glass.css +1 -1
  149. package/dist/styles/material/groove.css +4 -3
  150. package/dist/styles/material/keycap.css +3 -2
  151. package/dist/styles/material/lens.css +1 -1
  152. package/dist/styles/material/pane.css +5 -4
  153. package/dist/styles/material/readout.css +6 -6
  154. package/dist/styles/material/well.css +2 -2
  155. package/dist/styles/meter.css +2 -7
  156. package/dist/styles/number.css +5 -1
  157. package/dist/styles/panel.css +4 -3
  158. package/dist/styles/ring.css +1 -1
  159. package/dist/styles/screen.css +5 -1
  160. package/dist/styles/segmented.css +17 -4
  161. package/dist/styles/slider.css +9 -3
  162. package/dist/styles/spectrum.css +16 -3
  163. package/dist/styles/switch.css +35 -10
  164. package/dist/styles/toggle.css +14 -7
  165. package/dist/styles/waveform.css +17 -5
  166. package/dist/styles/wavestrip.css +11 -3
  167. package/dist/styles/xy.css +9 -5
  168. package/package.json +1 -6
  169. package/dist/default/dat.js +0 -51
  170. package/dist/default/lib/asked-value.js +0 -21
  171. package/dist/default/lib/fill-watch.js +0 -29
  172. package/dist/dev/dat.d.ts +0 -19
  173. package/dist/dev/dat.js +0 -52
  174. package/dist/dev/dat.js.map +0 -1
  175. package/dist/dev/lib/asked-value.js +0 -22
  176. package/dist/dev/lib/asked-value.js.map +0 -1
  177. package/dist/dev/lib/fill-watch.js +0 -30
  178. package/dist/dev/lib/fill-watch.js.map +0 -1
@@ -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);
@@ -34,7 +35,13 @@ var SonicSwitch = class extends SonicPositionGroupElement {
34
35
  connect(signal) {
35
36
  const group = this.group;
36
37
  super.connect(signal);
37
- this.checkStyles(group, "switch.css", ["cap"]);
38
+ this.checkStyles(group, "switch.css", {
39
+ box: {
40
+ property: "padding-top",
41
+ selector: ".sonic-switch-label"
42
+ },
43
+ material: ["cap"]
44
+ });
38
45
  this.bare.addEventListener("click", (event) => {
39
46
  const isSwallowed = this.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== "";
40
47
  this.#isClickSwallowed = false;
@@ -45,20 +52,14 @@ var SonicSwitch = class extends SonicPositionGroupElement {
45
52
  }, { signal });
46
53
  this.#bindBat(group, signal);
47
54
  }
48
- releaseTarget(option) {
49
- const target = this.pressTarget(option);
55
+ releaseTarget(index) {
56
+ const target = this.pressTarget(index);
50
57
  return this.isMomentary(target) || this.isOptionDisabled(target) ? void 0 : target;
51
58
  }
52
59
  render() {
53
60
  if (!this.isBound()) return;
54
61
  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));
62
+ this.#renderThrow(this.checkedIndex());
62
63
  }
63
64
  renderOption() {
64
65
  return renderPosition();
@@ -66,7 +67,7 @@ var SonicSwitch = class extends SonicPositionGroupElement {
66
67
  #bindBat(group, signal) {
67
68
  bindDrag(group, {
68
69
  grab: (event) => {
69
- if (!(event.target === this.bare || this.optionOf(event.target) !== void 0) || this.isDisabled() || !this.#isOnBat(event)) return;
70
+ if (!(this.pressedIndex(event.target) >= 0) || this.isDisabled() || !this.#isOnBat(event)) return;
70
71
  return {
71
72
  hasMoved: false,
72
73
  last: this.along(event),
@@ -87,51 +88,41 @@ var SonicSwitch = class extends SonicPositionGroupElement {
87
88
  }
88
89
  }, signal);
89
90
  }
90
- #hasSprungFrom(option) {
91
- if (!option || !this.isMomentary(option)) return false;
91
+ #hasSprungFrom(index) {
92
+ if (!this.isMomentary(index)) return false;
92
93
  this.releaseHold();
93
- return this.value !== optionValue(option);
94
+ return !this.isChecked(index);
94
95
  }
95
96
  #isOnBat(event) {
96
97
  return isUnder(this.#bat, event.clientX, event.clientY);
97
98
  }
98
99
  #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();
100
+ const from = this.checkedIndex();
101
+ const target = soleStep(this.positions(), from) ?? this.#sideStep(from, event);
102
+ if (target === void 0) return;
103
+ this.chooseByClick(target);
104
+ this.refocusAt(this.checkedIndex());
117
105
  }
118
106
  #renderThrow(index) {
119
- const count = this.options().length;
107
+ const count = this.positions().length;
120
108
  const at = index < 0 || count < 2 ? 0 : 2 * index / (count - 1) - 1;
121
109
  this.group.style.setProperty("--_sonic-switch-at", String(at));
122
110
  }
111
+ #sideStep(from, event) {
112
+ const box = this.#bat.getBoundingClientRect();
113
+ const [start, size] = this.orientation === "horizontal" ? [box.left, box.width] : [box.top, box.height];
114
+ const offset = (this.along(event) - start) / size - .5;
115
+ const side = Math.abs(offset) < pressCenterZone ? 1 : Math.sign(offset);
116
+ const toward = this.stepFrom(from, side);
117
+ return toward !== void 0 && toward !== from ? toward : this.stepFrom(from, -side);
118
+ }
123
119
  #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);
120
+ const from = this.checkedIndex();
121
+ const target = this.stepFrom(from, direction);
122
+ if (target === void 0 || target === from || this.#hasSprungFrom(from)) return;
123
+ if (this.isMomentary(target)) this.hold(target, pointerId);
124
+ else this.select(target);
125
+ this.refocusAt(this.checkedIndex());
135
126
  }
136
127
  };
137
128
 
@@ -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 const __DEV__: boolean;\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\tif (__DEV__)\n\t\t\tthis.checkStyles(group, 'switch.css', {\n\t\t\t\tbox: {\n\t\t\t\t\tproperty: 'padding-top',\n\t\t\t\t\tselector: '.sonic-switch-label',\n\t\t\t\t},\n\t\t\t\tmaterial: ['cap'],\n\t\t\t});\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":"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"}
@@ -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
 
@@ -31,11 +32,18 @@ var SonicToggle = class extends SonicPositionGroupElement {
31
32
  }
32
33
  connect(signal) {
33
34
  super.connect(signal);
34
- this.checkStyles(this.group, "toggle.css", [
35
- "cap",
36
- "well",
37
- "keycap"
38
- ]);
35
+ this.checkStyles(this.group, "toggle.css", {
36
+ box: {
37
+ property: "margin-top",
38
+ ratio: "--_sonic-toggle-gap-ratio",
39
+ selector: ".sonic-toggle-cap"
40
+ },
41
+ material: [
42
+ "cap",
43
+ "well",
44
+ "keycap"
45
+ ]
46
+ });
39
47
  this.bare.addEventListener("click", (event) => {
40
48
  if (event instanceof PointerEvent && event.pointerType !== "") return;
41
49
  this.setChecked(!this.checked);
@@ -53,7 +61,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
53
61
  render() {
54
62
  if (!this.isBound()) return;
55
63
  this.#checkLegends();
56
- this.group.style.setProperty("--_sonic-toggle-count", String(this.#stopCount()));
64
+ this.group.style.setProperty("--_sonic-toggle-count", String(this.positions().length));
57
65
  this.#placeCap(this.#draggedTo ?? this.#restingAt());
58
66
  super.render();
59
67
  for (const [index, option] of this.options().entries()) option.style.setProperty("--_sonic-toggle-index", String(index));
@@ -78,7 +86,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
78
86
  this.#slideTo(positionAt(drag.travel, pointer), event.pointerId);
79
87
  },
80
88
  release: (drag) => {
81
- delete (drag.position ?? this.bare).dataset.sonicPressed;
89
+ delete drag.marked.dataset.sonicPressed;
82
90
  this.#draggedTo = void 0;
83
91
  this.toggleState("dragging", false);
84
92
  this.render();
@@ -92,25 +100,21 @@ var SonicToggle = class extends SonicPositionGroupElement {
92
100
  this.#hasWarnedOfLegends = true;
93
101
  console.warn(`<sonic-toggle> lays out legends="outside" for two or three positions, and this one has ${String(count)}`);
94
102
  }
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
103
  #fixedLegend() {
102
104
  return [...this.children].find((child) => child !== this.group && !super.isMirrored(child));
103
105
  }
104
106
  #grab(event) {
105
- const position = this.optionOf(event.target);
106
- if (this.isDisabled() || !position && event.target !== this.bare) return void 0;
107
+ const position = this.pressedIndex(event.target);
108
+ if (this.isDisabled() || position < 0) return void 0;
107
109
  const isOnCap = isUnder(this.#cap, event.clientX, event.clientY);
108
110
  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);
111
+ if (!travel || !isOnCap && this.isOptionDisabled(position)) return void 0;
112
+ const marked = this.options()[position] ?? this.bare;
113
+ marked.toggleAttribute("data-sonic-pressed", true);
111
114
  return {
112
115
  hasMoved: false,
113
116
  isHolding: this.#holdFrom(position, event.pointerId),
117
+ marked,
114
118
  position,
115
119
  start: this.along(event),
116
120
  thresholdPx: dragThresholdPx(event.pointerType),
@@ -118,57 +122,36 @@ var SonicToggle = class extends SonicPositionGroupElement {
118
122
  };
119
123
  }
120
124
  #holdFrom(position, pointerId) {
121
- const target = position && this.pressTarget(position);
122
- if (!target || !this.isMomentary(target) || this.isOptionDisabled(target)) return false;
125
+ const target = this.pressTarget(position);
126
+ if (!this.isMomentary(target) || this.isOptionDisabled(target)) return false;
123
127
  this.hold(target, pointerId);
124
128
  return true;
125
129
  }
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
130
  #placeCap(at) {
133
131
  this.group.style.setProperty("--_sonic-toggle-at", String(at));
134
132
  }
135
133
  #press(position) {
136
- if (!position) {
137
- this.setChecked(!this.checked);
138
- return;
139
- }
140
134
  const target = this.pressTarget(position);
141
135
  if (this.isOptionDisabled(target)) return;
142
136
  this.select(target);
143
- this.#refocus(target);
144
- }
145
- #refocus(position) {
146
- if (this.group.matches(":focus-within")) position.focus();
137
+ this.refocusAt(target);
147
138
  }
148
139
  #restingAt() {
149
- if (this.isBare()) return this.checked ? this.#onAt() : 1 - this.#onAt();
150
- return Math.max(0, this.checkedIndex(this.options()));
140
+ return Math.max(0, this.checkedIndex());
151
141
  }
152
142
  #slideTo(at, pointerId) {
153
- const crossed = this.options()[Math.round(at)];
143
+ const crossed = Math.round(at);
154
144
  this.#draggedTo = at;
155
145
  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;
146
+ if (this.isChecked(crossed)) return;
161
147
  this.releaseHold();
162
- if (this.isOptionDisabled(crossed) || optionValue(crossed) === this.value) return;
148
+ if (this.isOptionDisabled(crossed) || this.isChecked(crossed)) return;
163
149
  if (this.isMomentary(crossed)) this.hold(crossed, pointerId);
164
150
  else this.select(crossed);
165
- this.#refocus(crossed);
166
- }
167
- #stopCount() {
168
- return this.options().length || 2;
151
+ this.refocusAt(crossed);
169
152
  }
170
153
  #travel(event, isOnCap) {
171
- const [first, last] = this.#enabledEnds();
154
+ const [first, last] = enabledEnds(this.positions());
172
155
  if (first < 0) return void 0;
173
156
  const box = this.#well.getBoundingClientRect();
174
157
  const [start, length, thickness] = this.orientation === "vertical" ? [
@@ -182,7 +165,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
182
165
  ];
183
166
  return travelFrom({
184
167
  at: this.#restingAt(),
185
- count: this.#stopCount(),
168
+ count: this.positions().length,
186
169
  first,
187
170
  isOnCap,
188
171
  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\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\tif (__DEV__)\n\t\t\tthis.checkStyles(this.group, 'toggle.css', {\n\t\t\t\tbox: {\n\t\t\t\t\tproperty: 'margin-top',\n\t\t\t\t\tratio: '--_sonic-toggle-gap-ratio',\n\t\t\t\t\tselector: '.sonic-toggle-cap',\n\t\t\t\t},\n\t\t\t\tmaterial: ['cap', 'well', 'keycap'],\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.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 {
@@ -13,6 +14,11 @@ interface HoverBinding {
13
14
  place: () => void;
14
15
  valueAt: (event: PointerEvent) => number | undefined;
15
16
  }
17
+ export interface ValueNotation {
18
+ format: (value: number) => string;
19
+ parse: (text: string) => number;
20
+ speak?: (value: number) => string;
21
+ }
16
22
  export declare abstract class SonicValueElement extends SonicFormElement {
17
23
  #private;
18
24
  static readonly observedAttributes: string[];
@@ -50,11 +56,13 @@ export declare abstract class SonicValueElement extends SonicFormElement {
50
56
  get parseValue(): ((text: string) => number) | undefined;
51
57
  set parseValue(parse: ((text: string) => number) | undefined);
52
58
  get pointerType(): string | undefined;
59
+ /** The values a stepped control stops at, spaced evenly along the travel whatever their spacing */
53
60
  get positions(): Array<number> | undefined;
54
61
  set positions(positions: Array<number> | undefined);
55
62
  get readout(): boolean;
56
63
  set readout(isShown: boolean);
57
64
  get revealed(): boolean;
65
+ /** Rounds the value assistive technology hears, in value units; the value itself is untouched */
58
66
  get spokenStep(): number | undefined;
59
67
  set spokenStep(value: number | undefined);
60
68
  get step(): number;
@@ -64,6 +72,7 @@ export declare abstract class SonicValueElement extends SonicFormElement {
64
72
  get value(): number;
65
73
  set value(next: null | number | undefined);
66
74
  get valueText(): string;
75
+ protected readonly notation: ValueNotation;
67
76
  attributeChangedCallback(name: string): void;
68
77
  connectedCallback(): void;
69
78
  protected abandonHold(): void;
@@ -73,21 +82,18 @@ export declare abstract class SonicValueElement extends SonicFormElement {
73
82
  protected controlRole(): 'slider' | 'spinbutton';
74
83
  protected abstract draw(): void;
75
84
  protected abstract focusTarget(): HTMLElement;
76
- protected heldFrom(): number | undefined;
77
- protected heldWrite(next: number): void;
78
- protected holdChanged(): void;
79
85
  protected input(next: number): boolean;
80
- protected isRevealed(): boolean;
81
86
  protected isWrapping(): boolean;
82
87
  protected mapping(): ValueMapping;
83
88
  protected originValue(): number;
84
- protected playback(): number;
85
89
  protected proportionsChanged(): void;
86
90
  protected readoutValue(): number;
87
91
  protected render(): void;
88
92
  protected renderReadout(): void;
89
93
  protected restoreState(state: string): void;
94
+ protected scrubChanged(): void;
90
95
  protected scrubsKeyRepeat(): boolean;
96
+ protected scrubState(): ScrubState;
91
97
  protected springTarget(): number | undefined;
92
98
  protected tapTarget(): number | undefined;
93
99
  protected travelPx(control: HTMLElement, property: `--_sonic-${string}`): number;