@xsynaptic/sonic-ui 0.24.0 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CHANGELOG.md +34 -2
  2. package/README.md +5 -3
  3. package/dist/default/define/region.js +7 -0
  4. package/dist/default/define.js +1 -0
  5. package/dist/default/elements/envelope.js +11 -11
  6. package/dist/default/elements/form-element.js +10 -0
  7. package/dist/default/elements/readout.js +3 -2
  8. package/dist/default/elements/region-layer.js +251 -0
  9. package/dist/default/elements/region.js +157 -0
  10. package/dist/default/elements/sonic-element.js +26 -4
  11. package/dist/default/elements/split.js +2 -2
  12. package/dist/default/elements/toggle.js +2 -2
  13. package/dist/default/elements/value-element.js +9 -5
  14. package/dist/default/elements/value-gestures.js +7 -5
  15. package/dist/default/elements/wave-element.js +12 -3
  16. package/dist/default/elements/waveform.js +38 -2
  17. package/dist/default/elements/wavestrip.js +90 -17
  18. package/dist/default/elements/xy.js +3 -11
  19. package/dist/default/index.js +2 -1
  20. package/dist/default/lib/clock.js +1 -0
  21. package/dist/default/lib/drag-step.js +1 -1
  22. package/dist/default/lib/marker-band.js +40 -29
  23. package/dist/default/lib/region-drag.js +48 -0
  24. package/dist/default/lib/spoken-duration.js +3 -0
  25. package/dist/dev/define/region.d.ts +2 -0
  26. package/dist/dev/define/region.js +8 -0
  27. package/dist/dev/define/region.js.map +1 -0
  28. package/dist/dev/define.d.ts +3 -1
  29. package/dist/dev/define.js +1 -0
  30. package/dist/dev/elements/envelope.js +11 -11
  31. package/dist/dev/elements/envelope.js.map +1 -1
  32. package/dist/dev/elements/form-element.d.ts +1 -0
  33. package/dist/dev/elements/form-element.js +10 -0
  34. package/dist/dev/elements/form-element.js.map +1 -1
  35. package/dist/dev/elements/readout.js +3 -2
  36. package/dist/dev/elements/readout.js.map +1 -1
  37. package/dist/dev/elements/region-layer.js +252 -0
  38. package/dist/dev/elements/region-layer.js.map +1 -0
  39. package/dist/dev/elements/region.d.ts +29 -0
  40. package/dist/dev/elements/region.js +165 -0
  41. package/dist/dev/elements/region.js.map +1 -0
  42. package/dist/dev/elements/sonic-element.d.ts +1 -0
  43. package/dist/dev/elements/sonic-element.js +27 -5
  44. package/dist/dev/elements/sonic-element.js.map +1 -1
  45. package/dist/dev/elements/split.js +2 -2
  46. package/dist/dev/elements/split.js.map +1 -1
  47. package/dist/dev/elements/toggle.js +2 -2
  48. package/dist/dev/elements/toggle.js.map +1 -1
  49. package/dist/dev/elements/value-element.d.ts +3 -1
  50. package/dist/dev/elements/value-element.js +9 -5
  51. package/dist/dev/elements/value-element.js.map +1 -1
  52. package/dist/dev/elements/value-gestures.js +7 -5
  53. package/dist/dev/elements/value-gestures.js.map +1 -1
  54. package/dist/dev/elements/wave-element.d.ts +7 -1
  55. package/dist/dev/elements/wave-element.js +12 -3
  56. package/dist/dev/elements/wave-element.js.map +1 -1
  57. package/dist/dev/elements/waveform.d.ts +7 -0
  58. package/dist/dev/elements/waveform.js +38 -2
  59. package/dist/dev/elements/waveform.js.map +1 -1
  60. package/dist/dev/elements/wavestrip.d.ts +17 -1
  61. package/dist/dev/elements/wavestrip.js +90 -17
  62. package/dist/dev/elements/wavestrip.js.map +1 -1
  63. package/dist/dev/elements/xy.js +3 -11
  64. package/dist/dev/elements/xy.js.map +1 -1
  65. package/dist/dev/index.d.ts +3 -2
  66. package/dist/dev/index.js +2 -1
  67. package/dist/dev/lib/clock.js +1 -0
  68. package/dist/dev/lib/clock.js.map +1 -1
  69. package/dist/dev/lib/drag-step.js +1 -1
  70. package/dist/dev/lib/drag-step.js.map +1 -1
  71. package/dist/dev/lib/marker-band.d.ts +7 -0
  72. package/dist/dev/lib/marker-band.js +40 -29
  73. package/dist/dev/lib/marker-band.js.map +1 -1
  74. package/dist/dev/lib/region-drag.js +49 -0
  75. package/dist/dev/lib/region-drag.js.map +1 -0
  76. package/dist/dev/lib/spoken-duration.js +3 -0
  77. package/dist/dev/lib/spoken-duration.js.map +1 -1
  78. package/dist/styles/button.css +42 -15
  79. package/dist/styles/controls.css +1 -0
  80. package/dist/styles/dial.css +48 -33
  81. package/dist/styles/envelope.css +7 -6
  82. package/dist/styles/led.css +28 -13
  83. package/dist/styles/material/arc.css +2 -2
  84. package/dist/styles/material/cap.css +25 -15
  85. package/dist/styles/material/core.css +53 -1
  86. package/dist/styles/material/glass.css +12 -2
  87. package/dist/styles/material/groove.css +3 -3
  88. package/dist/styles/material/keycap.css +5 -5
  89. package/dist/styles/material/lens.css +1 -1
  90. package/dist/styles/material/pane.css +21 -11
  91. package/dist/styles/material/readout.css +23 -10
  92. package/dist/styles/material/{bracket.css → reticle.css} +15 -8
  93. package/dist/styles/material/scale.css +4 -4
  94. package/dist/styles/material/value.css +12 -3
  95. package/dist/styles/material/well.css +2 -2
  96. package/dist/styles/material.css +1 -1
  97. package/dist/styles/meter.css +24 -11
  98. package/dist/styles/number.css +2 -1
  99. package/dist/styles/panel.css +5 -6
  100. package/dist/styles/region.css +88 -0
  101. package/dist/styles/ring.css +5 -4
  102. package/dist/styles/screen.css +7 -5
  103. package/dist/styles/segmented.css +8 -7
  104. package/dist/styles/slider.css +40 -32
  105. package/dist/styles/spectrum.css +9 -1
  106. package/dist/styles/switch.css +52 -53
  107. package/dist/styles/toggle.css +33 -38
  108. package/dist/styles/waveform.css +5 -4
  109. package/dist/styles/wavestrip.css +15 -12
  110. package/dist/styles/xy.css +19 -19
  111. package/package.json +3 -2
@@ -2,7 +2,7 @@ import { readPxProperty } from "./read-px-property.js";
2
2
 
3
3
  //#region src/lib/marker-band.ts
4
4
  const kindPattern = /^[a-z][a-z0-9-]*$/;
5
- const snapSlopPx = 4;
5
+ const snapMarginPx = 4;
6
6
  function isKind(kind) {
7
7
  return kind !== void 0 && kindPattern.test(kind);
8
8
  }
@@ -13,45 +13,61 @@ function writeKind(part, kind, fallback) {
13
13
  }
14
14
  part.style.setProperty("--_sonic-marker", `var(--sonic-marker-${kind}, var(${fallback}))`);
15
15
  }
16
- function renderMarker(marker, [from, to], lane) {
16
+ function createPart(marker) {
17
17
  const part = document.createElement("div");
18
- part.className = lane === void 0 ? "sonic-wavestrip-region" : "sonic-wavestrip-marker";
19
- if (lane !== void 0) part.style.setProperty("--_sonic-marker-lane", String(lane));
20
- part.style.setProperty("--_sonic-marker-from", String(from));
21
- part.style.setProperty("--_sonic-marker-to", String(to));
18
+ part.className = marker.end === void 0 ? "sonic-wavestrip-marker" : "sonic-wavestrip-region";
22
19
  writeKind(part, marker.kind, "--_sonic-marker-default");
23
20
  if (marker.dimmed === true) part.dataset.sonicDimmed = "";
24
21
  return part;
25
22
  }
26
23
  function createMarkerBand(band) {
27
- let dots = [];
24
+ let built;
25
+ let placed = [];
28
26
  const px = (property) => readPxProperty(getComputedStyle(band), property, 0);
27
+ function build(markers, draw) {
28
+ const parts = markers.map((marker) => {
29
+ const part = createPart(marker);
30
+ if (draw && marker.end === void 0) {
31
+ part.style.setProperty("--_sonic-marker-paint", "transparent");
32
+ draw(marker, part);
33
+ }
34
+ return part;
35
+ });
36
+ band.replaceChildren(...parts);
37
+ built = {
38
+ draw,
39
+ markers,
40
+ parts
41
+ };
42
+ return parts;
43
+ }
29
44
  return {
30
- at: ({ x, y }, widthPx) => {
45
+ inReach: (point, widthPx) => {
31
46
  const size = px("--_sonic-marker-size");
32
47
  const step = px("--_sonic-marker-step");
33
- const nearest = nearestDot({
34
- x,
35
- y
36
- }, dots.map(({ at, lane }) => ({
48
+ return centersInReach(point, placed.map(({ at, lane }) => ({
37
49
  x: at * widthPx,
38
50
  y: size / 2 + lane * step
39
- })), size / 2 + snapSlopPx);
40
- return nearest === void 0 ? void 0 : dots[nearest]?.index;
51
+ })), size / 2 + snapMarginPx).flatMap((order) => placed[order]?.index ?? []);
41
52
  },
42
- render: (markers, proportions) => {
53
+ render: (markers, proportions, draw) => {
43
54
  const widthPx = band.getBoundingClientRect().width;
44
55
  const points = markers.flatMap((marker, index) => marker.end === void 0 ? [{
45
56
  at: proportions[index]?.[0] ?? 0,
46
57
  index
47
58
  }] : []);
48
59
  const lanes = markerLanes(points.map(({ at }) => at * widthPx), px("--_sonic-marker-step"));
49
- dots = points.map((point, order) => ({
60
+ const parts = built?.markers === markers && built.draw === draw ? built.parts : build(markers, draw);
61
+ placed = points.map((point, order) => ({
50
62
  ...point,
51
63
  lane: lanes[order] ?? 0
52
64
  }));
53
- const laneOf = new Map(dots.map(({ index, lane }) => [index, lane]));
54
- band.replaceChildren(...markers.map((marker, index) => renderMarker(marker, proportions[index] ?? [0, 0], laneOf.get(index))));
65
+ for (const [index, part] of parts.entries()) {
66
+ const [from, to] = proportions[index] ?? [0, 0];
67
+ part.style.setProperty("--_sonic-marker-from", String(from));
68
+ part.style.setProperty("--_sonic-marker-to", String(to));
69
+ }
70
+ for (const { index, lane } of placed) parts[index]?.style.setProperty("--_sonic-marker-lane", String(lane));
55
71
  }
56
72
  };
57
73
  }
@@ -64,18 +80,13 @@ function markerLanes(positions, minDistance) {
64
80
  return lane;
65
81
  });
66
82
  }
67
- function nearestDot(point, dots, reach) {
68
- let nearest;
69
- let nearestDistance = reach * reach;
70
- for (const [index, dot] of dots.entries()) {
71
- const distance = (dot.x - point.x) ** 2 + (dot.y - point.y) ** 2;
72
- if (distance > nearestDistance) continue;
73
- nearest = index;
74
- nearestDistance = distance;
75
- }
76
- return nearest;
83
+ function centersInReach(point, centers, reach) {
84
+ return centers.map((center, index) => ({
85
+ distance: (center.x - point.x) ** 2 + (center.y - point.y) ** 2,
86
+ index
87
+ })).filter(({ distance }) => distance <= reach * reach).toSorted((first, second) => first.distance - second.distance || second.index - first.index).map(({ index }) => index);
77
88
  }
78
89
 
79
90
  //#endregion
80
- export { createMarkerBand, isKind, markerLanes, nearestDot, writeKind };
91
+ export { centersInReach, createMarkerBand, isKind, markerLanes, writeKind };
81
92
  //# sourceMappingURL=marker-band.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"marker-band.js","names":[],"sources":["../../../src/lib/marker-band.ts"],"sourcesContent":["import { readPxProperty } from '#lib/read-px-property.ts';\n\ninterface MarkerLook {\n\tdimmed?: boolean;\n\tend?: number;\n\tkind?: string;\n}\n\ninterface Point {\n\tx: number;\n\ty: number;\n}\n\ninterface MarkerBand {\n\tat: (point: Point, widthPx: number) => number | undefined;\n\trender: (\n\t\tmarkers: ReadonlyArray<MarkerLook>,\n\t\tproportions: ReadonlyArray<[number, number]>,\n\t) => void;\n}\n\n// Interpolated into a style, so anything else falls back to the default colour\nconst kindPattern = /^[a-z][a-z0-9-]*$/;\n\nconst snapSlopPx = 4;\n\nexport function isKind(kind: string | undefined): kind is string {\n\treturn kind !== undefined && kindPattern.test(kind);\n}\n\nexport function writeKind(part: HTMLElement, kind: string | undefined, fallback: string): void {\n\tif (!isKind(kind)) {\n\t\tpart.style.removeProperty('--_sonic-marker');\n\t\treturn;\n\t}\n\n\tpart.style.setProperty('--_sonic-marker', `var(--sonic-marker-${kind}, var(${fallback}))`);\n}\n\nfunction renderMarker(\n\tmarker: MarkerLook,\n\t[from, to]: [number, number],\n\tlane: number | undefined,\n): HTMLElement {\n\tconst part = document.createElement('div');\n\n\tpart.className = lane === undefined ? 'sonic-wavestrip-region' : 'sonic-wavestrip-marker';\n\tif (lane !== undefined) part.style.setProperty('--_sonic-marker-lane', String(lane));\n\tpart.style.setProperty('--_sonic-marker-from', String(from));\n\tpart.style.setProperty('--_sonic-marker-to', String(to));\n\twriteKind(part, marker.kind, '--_sonic-marker-default');\n\tif (marker.dimmed === true) part.dataset.sonicDimmed = '';\n\n\treturn part;\n}\n\nexport function createMarkerBand(band: HTMLElement): MarkerBand {\n\tlet dots: Array<{ at: number; index: number; lane: number }> = [];\n\n\tconst px = (property: string): number => readPxProperty(getComputedStyle(band), property, 0);\n\n\treturn {\n\t\tat: ({ x, y }, widthPx) => {\n\t\t\tconst size = px('--_sonic-marker-size');\n\t\t\tconst step = px('--_sonic-marker-step');\n\t\t\tconst nearest = nearestDot(\n\t\t\t\t{ x, y },\n\t\t\t\tdots.map(({ at, lane }) => ({ x: at * widthPx, y: size / 2 + lane * step })),\n\t\t\t\tsize / 2 + snapSlopPx,\n\t\t\t);\n\n\t\t\treturn nearest === undefined ? undefined : dots[nearest]?.index;\n\t\t},\n\t\trender: (markers, proportions) => {\n\t\t\tconst widthPx = band.getBoundingClientRect().width;\n\t\t\tconst points = markers.flatMap((marker, index) =>\n\t\t\t\tmarker.end === undefined ? [{ at: proportions[index]?.[0] ?? 0, index }] : [],\n\t\t\t);\n\t\t\tconst lanes = markerLanes(\n\t\t\t\tpoints.map(({ at }) => at * widthPx),\n\t\t\t\tpx('--_sonic-marker-step'),\n\t\t\t);\n\n\t\t\tdots = points.map((point, order) => ({ ...point, lane: lanes[order] ?? 0 }));\n\n\t\t\tconst laneOf = new Map(dots.map(({ index, lane }) => [index, lane]));\n\n\t\t\tband.replaceChildren(\n\t\t\t\t...markers.map((marker, index) =>\n\t\t\t\t\trenderMarker(marker, proportions[index] ?? [0, 0], laneOf.get(index)),\n\t\t\t\t),\n\t\t\t);\n\t\t},\n\t};\n}\n\nexport function markerLanes(positions: ReadonlyArray<number>, minDistance: number): Array<number> {\n\tconst lastInLane: Array<number> = [];\n\n\treturn positions.map((position) => {\n\t\tconst free = lastInLane.findIndex((last) => position - last >= minDistance);\n\t\tconst lane = free === -1 ? lastInLane.length : free;\n\n\t\tlastInLane[lane] = position;\n\n\t\treturn lane;\n\t});\n}\n\nexport function nearestDot(\n\tpoint: Point,\n\tdots: ReadonlyArray<Point>,\n\treach: number,\n): number | undefined {\n\tlet nearest: number | undefined;\n\tlet nearestDistance = reach * reach;\n\n\tfor (const [index, dot] of dots.entries()) {\n\t\tconst distance = (dot.x - point.x) ** 2 + (dot.y - point.y) ** 2;\n\t\tif (distance > nearestDistance) continue;\n\n\t\tnearest = index;\n\t\tnearestDistance = distance;\n\t}\n\n\treturn nearest;\n}\n"],"mappings":";;;AAsBA,MAAM,cAAc;AAEpB,MAAM,aAAa;AAEnB,SAAgB,OAAO,MAA0C;CAChE,OAAO,SAAS,UAAa,YAAY,KAAK,IAAI;AACnD;AAEA,SAAgB,UAAU,MAAmB,MAA0B,UAAwB;CAC9F,IAAI,CAAC,OAAO,IAAI,GAAG;EAClB,KAAK,MAAM,eAAe,iBAAiB;EAC3C;CACD;CAEA,KAAK,MAAM,YAAY,mBAAmB,sBAAsB,KAAK,QAAQ,SAAS,GAAG;AAC1F;AAEA,SAAS,aACR,QACA,CAAC,MAAM,KACP,MACc;CACd,MAAM,OAAO,SAAS,cAAc,KAAK;CAEzC,KAAK,YAAY,SAAS,SAAY,2BAA2B;CACjE,IAAI,SAAS,QAAW,KAAK,MAAM,YAAY,wBAAwB,OAAO,IAAI,CAAC;CACnF,KAAK,MAAM,YAAY,wBAAwB,OAAO,IAAI,CAAC;CAC3D,KAAK,MAAM,YAAY,sBAAsB,OAAO,EAAE,CAAC;CACvD,UAAU,MAAM,OAAO,MAAM,yBAAyB;CACtD,IAAI,OAAO,WAAW,MAAM,KAAK,QAAQ,cAAc;CAEvD,OAAO;AACR;AAEA,SAAgB,iBAAiB,MAA+B;CAC/D,IAAI,OAA2D,CAAC;CAEhE,MAAM,MAAM,aAA6B,eAAe,iBAAiB,IAAI,GAAG,UAAU,CAAC;CAE3F,OAAO;EACN,KAAK,EAAE,GAAG,KAAK,YAAY;GAC1B,MAAM,OAAO,GAAG,sBAAsB;GACtC,MAAM,OAAO,GAAG,sBAAsB;GACtC,MAAM,UAAU,WACf;IAAE;IAAG;GAAE,GACP,KAAK,KAAK,EAAE,IAAI,YAAY;IAAE,GAAG,KAAK;IAAS,GAAG,OAAO,IAAI,OAAO;GAAK,EAAE,GAC3E,OAAO,IAAI,UACZ;GAEA,OAAO,YAAY,SAAY,SAAY,KAAK,QAAQ,EAAE;EAC3D;EACA,SAAS,SAAS,gBAAgB;GACjC,MAAM,UAAU,KAAK,sBAAsB,CAAC,CAAC;GAC7C,MAAM,SAAS,QAAQ,SAAS,QAAQ,UACvC,OAAO,QAAQ,SAAY,CAAC;IAAE,IAAI,YAAY,MAAM,GAAG,MAAM;IAAG;GAAM,CAAC,IAAI,CAAC,CAC7E;GACA,MAAM,QAAQ,YACb,OAAO,KAAK,EAAE,SAAS,KAAK,OAAO,GACnC,GAAG,sBAAsB,CAC1B;GAEA,OAAO,OAAO,KAAK,OAAO,WAAW;IAAE,GAAG;IAAO,MAAM,MAAM,UAAU;GAAE,EAAE;GAE3E,MAAM,SAAS,IAAI,IAAI,KAAK,KAAK,EAAE,OAAO,WAAW,CAAC,OAAO,IAAI,CAAC,CAAC;GAEnE,KAAK,gBACJ,GAAG,QAAQ,KAAK,QAAQ,UACvB,aAAa,QAAQ,YAAY,UAAU,CAAC,GAAG,CAAC,GAAG,OAAO,IAAI,KAAK,CAAC,CACrE,CACD;EACD;CACD;AACD;AAEA,SAAgB,YAAY,WAAkC,aAAoC;CACjG,MAAM,aAA4B,CAAC;CAEnC,OAAO,UAAU,KAAK,aAAa;EAClC,MAAM,OAAO,WAAW,WAAW,SAAS,WAAW,QAAQ,WAAW;EAC1E,MAAM,OAAO,SAAS,KAAK,WAAW,SAAS;EAE/C,WAAW,QAAQ;EAEnB,OAAO;CACR,CAAC;AACF;AAEA,SAAgB,WACf,OACA,MACA,OACqB;CACrB,IAAI;CACJ,IAAI,kBAAkB,QAAQ;CAE9B,KAAK,MAAM,CAAC,OAAO,QAAQ,KAAK,QAAQ,GAAG;EAC1C,MAAM,YAAY,IAAI,IAAI,MAAM,MAAM,KAAK,IAAI,IAAI,MAAM,MAAM;EAC/D,IAAI,WAAW,iBAAiB;EAEhC,UAAU;EACV,kBAAkB;CACnB;CAEA,OAAO;AACR"}
1
+ {"version":3,"file":"marker-band.js","names":[],"sources":["../../../src/lib/marker-band.ts"],"sourcesContent":["import { readPxProperty } from '#lib/read-px-property.ts';\n\ninterface MarkerLook {\n\tdimmed?: boolean;\n\tend?: number;\n\tkind?: string;\n}\n\nexport interface Point {\n\tx: number;\n\ty: number;\n}\n\ninterface MarkerBand<Marker extends MarkerLook> {\n\tinReach: (point: Point, widthPx: number) => Array<number>;\n\trender: (\n\t\tmarkers: ReadonlyArray<Marker>,\n\t\tproportions: ReadonlyArray<[number, number]>,\n\t\tdraw?: (marker: Marker, part: HTMLElement) => void,\n\t) => void;\n}\n\n// Interpolated into a style, so anything else falls back to the default colour\nconst kindPattern = /^[a-z][a-z0-9-]*$/;\n\nconst snapMarginPx = 4;\n\nexport function isKind(kind: string | undefined): kind is string {\n\treturn kind !== undefined && kindPattern.test(kind);\n}\n\nexport function writeKind(part: HTMLElement, kind: string | undefined, fallback: string): void {\n\tif (!isKind(kind)) {\n\t\tpart.style.removeProperty('--_sonic-marker');\n\t\treturn;\n\t}\n\n\tpart.style.setProperty('--_sonic-marker', `var(--sonic-marker-${kind}, var(${fallback}))`);\n}\n\nfunction createPart(marker: MarkerLook): HTMLElement {\n\tconst part = document.createElement('div');\n\n\tpart.className = marker.end === undefined ? 'sonic-wavestrip-marker' : 'sonic-wavestrip-region';\n\twriteKind(part, marker.kind, '--_sonic-marker-default');\n\tif (marker.dimmed === true) part.dataset.sonicDimmed = '';\n\n\treturn part;\n}\n\nexport function createMarkerBand<Marker extends MarkerLook>(band: HTMLElement): MarkerBand<Marker> {\n\ttype Draw = (marker: Marker, part: HTMLElement) => void;\n\n\tlet built:\n\t\t| undefined\n\t\t| { draw: Draw | undefined; markers: ReadonlyArray<Marker>; parts: Array<HTMLElement> };\n\tlet placed: Array<{ at: number; index: number; lane: number }> = [];\n\n\tconst px = (property: string): number => readPxProperty(getComputedStyle(band), property, 0);\n\n\tfunction build(markers: ReadonlyArray<Marker>, draw: Draw | undefined): Array<HTMLElement> {\n\t\tconst parts = markers.map((marker) => {\n\t\t\tconst part = createPart(marker);\n\n\t\t\tif (draw && marker.end === undefined) {\n\t\t\t\tpart.style.setProperty('--_sonic-marker-paint', 'transparent');\n\t\t\t\tdraw(marker, part);\n\t\t\t}\n\n\t\t\treturn part;\n\t\t});\n\n\t\tband.replaceChildren(...parts);\n\t\tbuilt = { draw, markers, parts };\n\n\t\treturn parts;\n\t}\n\n\treturn {\n\t\tinReach: (point, widthPx) => {\n\t\t\tconst size = px('--_sonic-marker-size');\n\t\t\tconst step = px('--_sonic-marker-step');\n\n\t\t\treturn centersInReach(\n\t\t\t\tpoint,\n\t\t\t\tplaced.map(({ at, lane }) => ({ x: at * widthPx, y: size / 2 + lane * step })),\n\t\t\t\tsize / 2 + snapMarginPx,\n\t\t\t).flatMap((order) => placed[order]?.index ?? []);\n\t\t},\n\t\trender: (markers, proportions, draw) => {\n\t\t\tconst widthPx = band.getBoundingClientRect().width;\n\t\t\tconst points = markers.flatMap((marker, index) =>\n\t\t\t\tmarker.end === undefined ? [{ at: proportions[index]?.[0] ?? 0, index }] : [],\n\t\t\t);\n\t\t\tconst lanes = markerLanes(\n\t\t\t\tpoints.map(({ at }) => at * widthPx),\n\t\t\t\tpx('--_sonic-marker-step'),\n\t\t\t);\n\t\t\t// A `markers` write always makes a new array, so the same array and renderer mean no write\n\t\t\tconst parts =\n\t\t\t\tbuilt?.markers === markers && built.draw === draw ? built.parts : build(markers, draw);\n\n\t\t\tplaced = points.map((point, order) => ({ ...point, lane: lanes[order] ?? 0 }));\n\t\t\tfor (const [index, part] of parts.entries()) {\n\t\t\t\tconst [from, to] = proportions[index] ?? [0, 0];\n\n\t\t\t\tpart.style.setProperty('--_sonic-marker-from', String(from));\n\t\t\t\tpart.style.setProperty('--_sonic-marker-to', String(to));\n\t\t\t}\n\t\t\tfor (const { index, lane } of placed) {\n\t\t\t\tparts[index]?.style.setProperty('--_sonic-marker-lane', String(lane));\n\t\t\t}\n\t\t},\n\t};\n}\n\nexport function markerLanes(positions: ReadonlyArray<number>, minDistance: number): Array<number> {\n\tconst lastInLane: Array<number> = [];\n\n\treturn positions.map((position) => {\n\t\tconst free = lastInLane.findIndex((last) => position - last >= minDistance);\n\t\tconst lane = free === -1 ? lastInLane.length : free;\n\n\t\tlastInLane[lane] = position;\n\n\t\treturn lane;\n\t});\n}\n\n// A later marker wins a tie, as it is drawn over the earlier one\nexport function centersInReach(\n\tpoint: Point,\n\tcenters: ReadonlyArray<Point>,\n\treach: number,\n): Array<number> {\n\treturn centers\n\t\t.map((center, index) => ({\n\t\t\tdistance: (center.x - point.x) ** 2 + (center.y - point.y) ** 2,\n\t\t\tindex,\n\t\t}))\n\t\t.filter(({ distance }) => distance <= reach * reach)\n\t\t.toSorted((first, second) => first.distance - second.distance || second.index - first.index)\n\t\t.map(({ index }) => index);\n}\n"],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { clamp, clampProportion, trimFloat } from "./math.js";
2
+ import { fineFactor } from "./drag-step.js";
3
+
4
+ //#region src/lib/region-drag.ts
5
+ function regionAt(bodies, x) {
6
+ const index = bodies.findLastIndex((body) => body !== void 0 && x >= body[0] && x <= body[1]);
7
+ return index === -1 ? void 0 : index;
8
+ }
9
+ function regionTravel({ end, start }, [low, high]) {
10
+ return [low, Math.max(low, trimFloat(high - (end - start)))];
11
+ }
12
+ function moveRegionTo(mapping, span, start) {
13
+ const moved = clamp(mapping.snap(start), ...regionTravel(span, mapping.bounds));
14
+ return {
15
+ end: trimFloat(moved + (span.end - span.start)),
16
+ start: moved
17
+ };
18
+ }
19
+ function startRegionDrag({ position, ...fixed }) {
20
+ return {
21
+ ...fixed,
22
+ isEngaged: false,
23
+ lastPosition: position,
24
+ startPosition: position,
25
+ travelPx: 0
26
+ };
27
+ }
28
+ function stepRegionDrag(mapping, state, move) {
29
+ if (!(state.isEngaged || Math.abs(move.position - state.startPosition) >= state.thresholdPx)) return {
30
+ span: void 0,
31
+ state
32
+ };
33
+ const pace = move.isFine ? fineFactor : 1;
34
+ const travelPx = state.travelPx + (move.position - state.lastPosition) * pace;
35
+ const at = mapping.proportionOf(state.from.start) + travelPx / state.widthPx;
36
+ return {
37
+ span: moveRegionTo(mapping, state.from, mapping.valueAt(clampProportion(at))),
38
+ state: {
39
+ ...state,
40
+ isEngaged: true,
41
+ lastPosition: move.position,
42
+ travelPx
43
+ }
44
+ };
45
+ }
46
+
47
+ //#endregion
48
+ export { moveRegionTo, regionAt, regionTravel, startRegionDrag, stepRegionDrag };
49
+ //# sourceMappingURL=region-drag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"region-drag.js","names":[],"sources":["../../../src/lib/region-drag.ts"],"sourcesContent":["import type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { fineFactor } from '#lib/drag-step.ts';\nimport { clamp, clampProportion, trimFloat } from '#lib/math.ts';\n\nexport interface RegionSpan {\n\tend: number;\n\tstart: number;\n}\n\nexport interface RegionDragState {\n\tfrom: RegionSpan;\n\tisEngaged: boolean;\n\tlastPosition: number;\n\tstartPosition: number;\n\tthresholdPx: number;\n\ttravelPx: number;\n\twidthPx: number;\n}\n\ninterface RegionDragStart {\n\tfrom: RegionSpan;\n\tposition: number;\n\tthresholdPx: number;\n\twidthPx: number;\n}\n\ninterface RegionDragStep {\n\tspan: RegionSpan | undefined;\n\tstate: RegionDragState;\n}\n\n// A later body is drawn over an earlier one; `undefined` holds the place of one that takes no press\nexport function regionAt(\n\tbodies: ReadonlyArray<[number, number] | undefined>,\n\tx: number,\n): number | undefined {\n\tconst index = bodies.findLastIndex((body) => body !== undefined && x >= body[0] && x <= body[1]);\n\n\treturn index === -1 ? undefined : index;\n}\n\nexport function regionTravel(\n\t{ end, start }: RegionSpan,\n\t[low, high]: [number, number],\n): [number, number] {\n\treturn [low, Math.max(low, trimFloat(high - (end - start)))];\n}\n\nexport function moveRegionTo(mapping: ValueMapping, span: RegionSpan, start: number): RegionSpan {\n\tconst moved = clamp(mapping.snap(start), ...regionTravel(span, mapping.bounds));\n\n\treturn { end: trimFloat(moved + (span.end - span.start)), start: moved };\n}\n\nexport function startRegionDrag({ position, ...fixed }: RegionDragStart): RegionDragState {\n\treturn {\n\t\t...fixed,\n\t\tisEngaged: false,\n\t\tlastPosition: position,\n\t\tstartPosition: position,\n\t\ttravelPx: 0,\n\t};\n}\n\n// `span` stays `undefined` until the pointer has travelled the threshold, so a lift before then is a press\nexport function stepRegionDrag(\n\tmapping: ValueMapping,\n\tstate: RegionDragState,\n\tmove: { isFine: boolean; position: number },\n): RegionDragStep {\n\tconst isEngaged =\n\t\tstate.isEngaged || Math.abs(move.position - state.startPosition) >= state.thresholdPx;\n\tif (!isEngaged) return { span: undefined, state };\n\n\tconst pace = move.isFine ? fineFactor : 1;\n\tconst travelPx = state.travelPx + (move.position - state.lastPosition) * pace;\n\tconst at = mapping.proportionOf(state.from.start) + travelPx / state.widthPx;\n\n\treturn {\n\t\tspan: moveRegionTo(mapping, state.from, mapping.valueAt(clampProportion(at))),\n\t\tstate: { ...state, isEngaged: true, lastPosition: move.position, travelPx },\n\t};\n}\n"],"mappings":";;;;AAiCA,SAAgB,SACf,QACA,GACqB;CACrB,MAAM,QAAQ,OAAO,eAAe,SAAS,SAAS,UAAa,KAAK,KAAK,MAAM,KAAK,KAAK,EAAE;CAE/F,OAAO,UAAU,KAAK,SAAY;AACnC;AAEA,SAAgB,aACf,EAAE,KAAK,SACP,CAAC,KAAK,OACa;CACnB,OAAO,CAAC,KAAK,KAAK,IAAI,KAAK,UAAU,QAAQ,MAAM,MAAM,CAAC,CAAC;AAC5D;AAEA,SAAgB,aAAa,SAAuB,MAAkB,OAA2B;CAChG,MAAM,QAAQ,MAAM,QAAQ,KAAK,KAAK,GAAG,GAAG,aAAa,MAAM,QAAQ,MAAM,CAAC;CAE9E,OAAO;EAAE,KAAK,UAAU,SAAS,KAAK,MAAM,KAAK,MAAM;EAAG,OAAO;CAAM;AACxE;AAEA,SAAgB,gBAAgB,EAAE,UAAU,GAAG,SAA2C;CACzF,OAAO;EACN,GAAG;EACH,WAAW;EACX,cAAc;EACd,eAAe;EACf,UAAU;CACX;AACD;AAGA,SAAgB,eACf,SACA,OACA,MACiB;CAGjB,IAAI,EADH,MAAM,aAAa,KAAK,IAAI,KAAK,WAAW,MAAM,aAAa,KAAK,MAAM,cAC3D,OAAO;EAAE,MAAM;EAAW;CAAM;CAEhD,MAAM,OAAO,KAAK,SAAS,aAAa;CACxC,MAAM,WAAW,MAAM,YAAY,KAAK,WAAW,MAAM,gBAAgB;CACzE,MAAM,KAAK,QAAQ,aAAa,MAAM,KAAK,KAAK,IAAI,WAAW,MAAM;CAErE,OAAO;EACN,MAAM,aAAa,SAAS,MAAM,MAAM,QAAQ,QAAQ,gBAAgB,EAAE,CAAC,CAAC;EAC5E,OAAO;GAAE,GAAG;GAAO,WAAW;GAAM,cAAc,KAAK;GAAU;EAAS;CAC3E;AACD"}
@@ -1,3 +1,5 @@
1
+ import { formatClock } from "./clock.js";
2
+
1
3
  //#region src/lib/spoken-duration.ts
2
4
  const formatters = /* @__PURE__ */ new Map();
3
5
  function formatterFor(language, isZero) {
@@ -18,6 +20,7 @@ function formatterFor(language, isZero) {
18
20
  return formatter;
19
21
  }
20
22
  function spokenDuration(seconds, language = "") {
23
+ if (!("DurationFormat" in Intl)) return formatClock(seconds);
21
24
  const total = Math.trunc(seconds);
22
25
  return formatterFor(language, total === 0).format({
23
26
  hours: Math.trunc(total / 3600),
@@ -1 +1 @@
1
- {"version":3,"file":"spoken-duration.js","names":[],"sources":["../../../src/lib/spoken-duration.ts"],"sourcesContent":["const formatters = new Map<string, Intl.DurationFormat>();\n\nfunction formatterFor(language: string, isZero: boolean): Intl.DurationFormat {\n\tconst key = `${language}|${String(isZero)}`;\n\tconst cached = formatters.get(key);\n\tif (cached) return cached;\n\n\t// All-zero units format to an empty string unless the seconds are forced\n\tconst options = { secondsDisplay: isZero ? 'always' : 'auto', style: 'long' } as const;\n\tlet formatter: Intl.DurationFormat;\n\n\ttry {\n\t\tformatter = new Intl.DurationFormat(language || undefined, options);\n\t} catch {\n\t\tformatter = new Intl.DurationFormat(undefined, options);\n\t}\n\tformatters.set(key, formatter);\n\n\treturn formatter;\n}\n\nexport function spokenDuration(seconds: number, language = ''): string {\n\t// Truncated as the clock is, so every unit carries one sign; mixed signs throw\n\tconst total = Math.trunc(seconds);\n\n\treturn formatterFor(language, total === 0).format({\n\t\thours: Math.trunc(total / 3600),\n\t\tminutes: Math.trunc(total / 60) % 60,\n\t\tseconds: total % 60,\n\t});\n}\n"],"mappings":";AAAA,MAAM,6BAAa,IAAI,IAAiC;AAExD,SAAS,aAAa,UAAkB,QAAsC;CAC7E,MAAM,MAAM,GAAG,SAAS,GAAG,OAAO,MAAM;CACxC,MAAM,SAAS,WAAW,IAAI,GAAG;CACjC,IAAI,QAAQ,OAAO;CAGnB,MAAM,UAAU;EAAE,gBAAgB,SAAS,WAAW;EAAQ,OAAO;CAAO;CAC5E,IAAI;CAEJ,IAAI;EACH,YAAY,IAAI,KAAK,eAAe,YAAY,QAAW,OAAO;CACnE,QAAQ;EACP,YAAY,IAAI,KAAK,eAAe,QAAW,OAAO;CACvD;CACA,WAAW,IAAI,KAAK,SAAS;CAE7B,OAAO;AACR;AAEA,SAAgB,eAAe,SAAiB,WAAW,IAAY;CAEtE,MAAM,QAAQ,KAAK,MAAM,OAAO;CAEhC,OAAO,aAAa,UAAU,UAAU,CAAC,CAAC,CAAC,OAAO;EACjD,OAAO,KAAK,MAAM,QAAQ,IAAI;EAC9B,SAAS,KAAK,MAAM,QAAQ,EAAE,IAAI;EAClC,SAAS,QAAQ;CAClB,CAAC;AACF"}
1
+ {"version":3,"file":"spoken-duration.js","names":[],"sources":["../../../src/lib/spoken-duration.ts"],"sourcesContent":["import { formatClock } from '#lib/clock.ts';\n\nconst formatters = new Map<string, Intl.DurationFormat>();\n\nfunction formatterFor(language: string, isZero: boolean): Intl.DurationFormat {\n\tconst key = `${language}|${String(isZero)}`;\n\tconst cached = formatters.get(key);\n\tif (cached) return cached;\n\n\t// All-zero units format to an empty string unless the seconds are forced\n\tconst options = { secondsDisplay: isZero ? 'always' : 'auto', style: 'long' } as const;\n\tlet formatter: Intl.DurationFormat;\n\n\ttry {\n\t\tformatter = new Intl.DurationFormat(language || undefined, options);\n\t} catch {\n\t\tformatter = new Intl.DurationFormat(undefined, options);\n\t}\n\tformatters.set(key, formatter);\n\n\treturn formatter;\n}\n\nexport function spokenDuration(seconds: number, language = ''): string {\n\tif (!('DurationFormat' in Intl)) return formatClock(seconds);\n\n\t// Truncated as the clock is, so every unit carries one sign; mixed signs throw\n\tconst total = Math.trunc(seconds);\n\n\treturn formatterFor(language, total === 0).format({\n\t\thours: Math.trunc(total / 3600),\n\t\tminutes: Math.trunc(total / 60) % 60,\n\t\tseconds: total % 60,\n\t});\n}\n"],"mappings":";;;AAEA,MAAM,6BAAa,IAAI,IAAiC;AAExD,SAAS,aAAa,UAAkB,QAAsC;CAC7E,MAAM,MAAM,GAAG,SAAS,GAAG,OAAO,MAAM;CACxC,MAAM,SAAS,WAAW,IAAI,GAAG;CACjC,IAAI,QAAQ,OAAO;CAGnB,MAAM,UAAU;EAAE,gBAAgB,SAAS,WAAW;EAAQ,OAAO;CAAO;CAC5E,IAAI;CAEJ,IAAI;EACH,YAAY,IAAI,KAAK,eAAe,YAAY,QAAW,OAAO;CACnE,QAAQ;EACP,YAAY,IAAI,KAAK,eAAe,QAAW,OAAO;CACvD;CACA,WAAW,IAAI,KAAK,SAAS;CAE7B,OAAO;AACR;AAEA,SAAgB,eAAe,SAAiB,WAAW,IAAY;CACtE,IAAI,EAAE,oBAAoB,OAAO,OAAO,YAAY,OAAO;CAG3D,MAAM,QAAQ,KAAK,MAAM,OAAO;CAEhC,OAAO,aAAa,UAAU,UAAU,CAAC,CAAC,CAAC,OAAO;EACjD,OAAO,KAAK,MAAM,QAAQ,IAAI;EAC9B,SAAS,KAAK,MAAM,QAAQ,EAAE,IAAI;EAClC,SAAS,QAAQ;CAClB,CAAC;AACF"}
@@ -85,7 +85,8 @@
85
85
  font: inherit;
86
86
  color: var(--_sonic-ink);
87
87
  cursor: pointer;
88
- user-select: none;
88
+ -webkit-user-select: none;
89
+ user-select: none;
89
90
  background: var(--_sonic-well-floor);
90
91
  border: 0;
91
92
  border-radius: var(--_sonic-button-corner);
@@ -112,11 +113,13 @@
112
113
  );
113
114
 
114
115
  color: color-mix(in oklab, var(--_sonic-lit) 55%, var(--_sonic-ink));
116
+ }
115
117
 
116
118
  @media (forced-colors: active) {
119
+ :where([armed]) > .sonic-button:not(:disabled, [aria-disabled='true']) {
117
120
  --_sonic-keycap-glow: transparent;
118
- }
119
121
  }
122
+ }
120
123
 
121
124
  /* Before the latched rule too; a button with an LED blinks the LED and keeps its legend */
122
125
  :where([busy]) > .sonic-button:not(:disabled, [aria-disabled='true'], :has(.sonic-led)) {
@@ -153,21 +156,28 @@
153
156
  )
154
157
  )
155
158
  );
159
+ }
156
160
 
157
- & .sonic-button-cap > [data-sonic-when='released'] {
161
+ .sonic-button:is([aria-pressed='true'],[aria-expanded='true'],:where([momentary]:state(pressed)) > *) .sonic-button-cap > [data-sonic-when='released'] {
158
162
  display: none;
159
163
  }
160
164
 
161
165
  @media (forced-colors: active) {
166
+
167
+ .sonic-button:is(
168
+ [aria-pressed='true'],
169
+ [aria-expanded='true'],
170
+ :where([momentary]:state(pressed)) > *
171
+ ) {
162
172
  --_sonic-skirt: var(--_sonic-lit);
163
173
  --_sonic-face: var(--_sonic-lit);
164
174
  --_sonic-ink: HighlightText;
175
+ }
165
176
 
166
- &:is(:disabled, [aria-disabled='true']) {
177
+ .sonic-button:is([aria-pressed='true'],[aria-expanded='true'],:where([momentary]:state(pressed)) > *):is(:disabled,[aria-disabled='true']) {
167
178
  --_sonic-ink: ButtonFace;
168
179
  }
169
180
  }
170
- }
171
181
 
172
182
  .sonic-button:is(:active, [data-sonic-pressed], :where([momentary]:state(pressed)) > *):where(
173
183
  :not(:disabled, [aria-disabled='true'])
@@ -232,11 +242,14 @@
232
242
  --_sonic-blank-edge-off,
233
243
  var(--_sonic-blank-edge-lit, color-mix(in oklab, currentcolor 35%, transparent))
234
244
  );
245
+ }
235
246
 
236
247
  @media (forced-colors: active) {
248
+
249
+ .sonic-button:not(:has(> .sonic-button-cap > :not(.sonic-led))) {
237
250
  --_sonic-blank-edge: transparent;
238
- }
239
251
  }
252
+ }
240
253
 
241
254
  .sonic-button-cap {
242
255
  --_sonic-keycap-corner: max(0px, var(--_sonic-button-corner) - var(--_sonic-button-gap));
@@ -259,19 +272,20 @@
259
272
  inset: var(--_sonic-button-gap);
260
273
  gap: calc(var(--_sonic-button-size) * var(--_sonic-button-led-ratio) / 2);
261
274
  place-content: center;
275
+ }
262
276
 
263
- & > .sonic-led {
277
+ .sonic-button-cap > .sonic-led {
264
278
  --_sonic-led-size: var(
265
279
  --sonic-led-size,
266
280
  calc(var(--_sonic-button-size) * var(--_sonic-button-led-ratio))
267
281
  );
268
282
  }
269
283
 
270
- & > :not(.sonic-led) {
284
+ .sonic-button-cap > :not(.sonic-led) {
271
285
  filter: var(--_sonic-engraving) var(--_sonic-button-legend-glow,);
272
286
  }
273
287
 
274
- & > :not(svg, .sonic-led) {
288
+ .sonic-button-cap > :not(svg,.sonic-led) {
275
289
  padding-inline: calc(var(--_sonic-button-size) * var(--_sonic-button-padding-ratio));
276
290
  font-size: var(
277
291
  --_sonic-button-font-own,
@@ -284,18 +298,23 @@
284
298
  white-space: nowrap;
285
299
  }
286
300
 
287
- & > svg {
301
+ .sonic-button-cap > svg {
288
302
  inline-size: auto;
289
303
  block-size: calc(var(--_sonic-button-size) * var(--_sonic-button-icon-ratio));
290
304
  aspect-ratio: auto 1;
291
- mask-image: linear-gradient(
305
+ -webkit-mask-image: linear-gradient(
292
306
  var(--_sonic-light-fall),
293
307
  #000 15%,
294
308
  rgb(0 0 0 / calc(1 - 0.4 * var(--_sonic-relief)))
295
309
  );
296
- mask-repeat: no-repeat;
310
+ mask-image: linear-gradient(
311
+ var(--_sonic-light-fall),
312
+ #000 15%,
313
+ rgb(0 0 0 / calc(1 - 0.4 * var(--_sonic-relief)))
314
+ );
315
+ -webkit-mask-repeat: no-repeat;
316
+ mask-repeat: no-repeat;
297
317
  }
298
- }
299
318
 
300
319
  .sonic-button-cap:has(> :not(.sonic-led)) {
301
320
  position: relative;
@@ -329,16 +348,24 @@
329
348
  [busy] > .sonic-button:not(:disabled, [aria-disabled='true']) .sonic-led::after {
330
349
  animation: sonic-button-busy 500ms ease-in-out var(--sonic-button-busy-delay, 0s) infinite
331
350
  alternate;
351
+ }
332
352
 
333
353
  @media (prefers-reduced-motion: reduce) {
354
+
355
+ [busy]
356
+ > .sonic-button:not(:disabled, [aria-disabled='true'], :has(.sonic-led))
357
+ > .sonic-button-cap
358
+ > *,
359
+ [busy] > .sonic-button:not(:disabled, [aria-disabled='true']) .sonic-led::after {
334
360
  animation: none;
335
- }
336
361
  }
362
+ }
337
363
 
338
364
  @media (forced-colors: active) {
339
365
  .sonic-button-cap > :not(.sonic-led) {
340
366
  filter: none;
341
- mask: none;
367
+ -webkit-mask: none;
368
+ mask: none;
342
369
  }
343
370
  }
344
371
  }
@@ -8,6 +8,7 @@
8
8
  @import './meter.css';
9
9
  @import './number.css';
10
10
  @import './panel.css';
11
+ @import './region.css';
11
12
  @import './ring.css';
12
13
  @import './screen.css';
13
14
  @import './segmented.css';
@@ -83,7 +83,8 @@
83
83
  block-size: var(--_sonic-dial-size);
84
84
  touch-action: var(--sonic-touch-action, pan-x);
85
85
  cursor: ns-resize;
86
- user-select: none;
86
+ -webkit-user-select: none;
87
+ user-select: none;
87
88
  border-radius: 50%;
88
89
  }
89
90
 
@@ -130,7 +131,7 @@
130
131
  transparent 0 var(--_sonic-dial-notch-pitch)
131
132
  );
132
133
  border-radius: 50%;
133
- mask:
134
+ -webkit-mask:
134
135
  radial-gradient(
135
136
  closest-side,
136
137
  transparent calc(100% - var(--_sonic-dial-notch-band) * 0.8),
@@ -142,14 +143,28 @@
142
143
  #000 calc(var(--_sonic-dial-sweep) + 2 * var(--_sonic-dial-notch-width)),
143
144
  transparent 0
144
145
  );
145
- mask-composite: intersect;
146
+ mask:
147
+ radial-gradient(
148
+ closest-side,
149
+ transparent calc(100% - var(--_sonic-dial-notch-band) * 0.8),
150
+ #000 0 calc(100% - var(--_sonic-dial-notch-band) * 0.25),
151
+ transparent 0
152
+ ),
153
+ conic-gradient(
154
+ from calc(var(--_sonic-dial-start) - var(--_sonic-dial-notch-width)),
155
+ #000 calc(var(--_sonic-dial-sweep) + 2 * var(--_sonic-dial-notch-width)),
156
+ transparent 0
157
+ );
158
+ -webkit-mask-composite: source-in, xor;
159
+ mask-composite: intersect;
146
160
  }
147
161
 
148
162
  .sonic-dial-scale {
149
163
  font-size: calc(var(--_sonic-dial-size) * var(--_sonic-dial-scale-font-ratio));
150
- line-height: 1cap;
164
+ line-height: 0.7em;
165
+ }
151
166
 
152
- & > * {
167
+ .sonic-dial-scale > * {
153
168
  --_sonic-dial-scale-angle: calc(
154
169
  var(--_sonic-dial-start) + var(--_sonic-scale-at, 0) * var(--_sonic-dial-sweep)
155
170
  );
@@ -161,11 +176,11 @@
161
176
 
162
177
  inset: calc(50% - var(--_sonic-dial-scale-cos) * var(--_sonic-dial-scale-radius)) auto auto
163
178
  calc(50% + var(--_sonic-dial-scale-sin) * var(--_sonic-dial-scale-radius));
164
- translate: calc(-50% + var(--_sonic-dial-scale-sin) * (50% + 0.15cap))
165
- calc(-50% - var(--_sonic-dial-scale-cos) * (50% + 0.15cap));
179
+ translate: calc(-50% + var(--_sonic-dial-scale-sin) * (50% + 0.105em))
180
+ calc(-50% - var(--_sonic-dial-scale-cos) * (50% + 0.105em));
166
181
  }
167
182
 
168
- & > .sonic-scale-tick {
183
+ .sonic-dial-scale > .sonic-scale-tick {
169
184
  --_sonic-dial-tick-length: calc(var(--_sonic-dial-scale-band) * 0.4);
170
185
 
171
186
  inline-size: var(--_sonic-unit);
@@ -174,7 +189,6 @@
174
189
  translate: calc(-50% + var(--_sonic-dial-scale-sin) * var(--_sonic-dial-tick-length) / 2)
175
190
  calc(-50% - var(--_sonic-dial-scale-cos) * var(--_sonic-dial-tick-length) / 2);
176
191
  }
177
- }
178
192
 
179
193
  :is(.sonic-dial-ring, .sonic-dial-modulation) {
180
194
  --_sonic-arc-width: var(--_sonic-dial-ring-width);
@@ -182,7 +196,8 @@
182
196
  position: absolute;
183
197
  inset: calc(var(--_sonic-dial-scale-band) + var(--_sonic-dial-notch-band));
184
198
  border-radius: 50%;
185
- mask: var(--_sonic-arc-mask);
199
+ -webkit-mask: var(--_sonic-arc-mask);
200
+ mask: var(--_sonic-arc-mask);
186
201
  }
187
202
 
188
203
  .sonic-dial-ring {
@@ -253,17 +268,18 @@
253
268
 
254
269
  display: block;
255
270
  background: none;
256
- mask: none;
271
+ -webkit-mask: none;
272
+ mask: none;
273
+ }
257
274
 
258
- &::before,
259
- &::after {
275
+ :is(:state(modulated) > .sonic-dial > .sonic-dial-modulation)::before,:is(:state(modulated) > .sonic-dial > .sonic-dial-modulation)::after {
260
276
  position: absolute;
261
277
  inset: 0;
262
278
  content: '';
263
279
  border-radius: 50%;
264
280
  }
265
281
 
266
- &::before {
282
+ :is(:state(modulated) > .sonic-dial > .sonic-dial-modulation)::before {
267
283
  background:
268
284
  conic-gradient(
269
285
  from var(--_sonic-dial-start),
@@ -279,10 +295,11 @@
279
295
  calc(var(--_sonic-dial-modulation-to, 0) * var(--_sonic-dial-sweep)),
280
296
  transparent 0
281
297
  );
282
- mask: var(--_sonic-arc-mask);
298
+ -webkit-mask: var(--_sonic-arc-mask);
299
+ mask: var(--_sonic-arc-mask);
283
300
  }
284
301
 
285
- &::after {
302
+ :is(:state(modulated) > .sonic-dial > .sonic-dial-modulation)::after {
286
303
  background: linear-gradient(
287
304
  var(--_sonic-dial-modulation-tick),
288
305
  var(--_sonic-dial-modulation-tick)
@@ -290,7 +307,6 @@
290
307
  50% 0 / var(--_sonic-unit) var(--_sonic-dial-ring-width) no-repeat;
291
308
  rotate: calc(var(--_sonic-dial-start) + var(--_sonic-dial-modulation-value-angle));
292
309
  }
293
- }
294
310
 
295
311
  .sonic-dial-cap {
296
312
  position: absolute;
@@ -302,27 +318,27 @@
302
318
  var(--_sonic-drop-shade),
303
319
  var(--_sonic-rim),
304
320
  var(--_sonic-focus-shadow);
321
+ }
305
322
 
306
- &::before,
307
- &::after {
323
+ .sonic-dial-cap::before,.sonic-dial-cap::after {
308
324
  position: absolute;
309
325
  content: '';
310
326
  border-radius: 50%;
311
327
  }
312
328
 
313
- &::before {
329
+ .sonic-dial-cap::before {
314
330
  inset: 0;
315
331
  background: repeating-conic-gradient(
316
- rgb(from var(--_sonic-shade) r g b / calc(0.3 * var(--_sonic-relief))) 0,
317
- rgb(from var(--_sonic-shine) r g b / calc(0.12 * var(--_sonic-relief)))
332
+ color-mix(in srgb, var(--_sonic-shade) calc(30% * var(--_sonic-relief)), transparent) 0,
333
+ color-mix(in srgb, var(--_sonic-shine) calc(12% * var(--_sonic-relief)), transparent)
318
334
  calc(var(--_sonic-dial-ridge-angle) / 2),
319
- rgb(from var(--_sonic-shade) r g b / calc(0.3 * var(--_sonic-relief)))
335
+ color-mix(in srgb, var(--_sonic-shade) calc(30% * var(--_sonic-relief)), transparent)
320
336
  var(--_sonic-dial-ridge-angle)
321
337
  );
322
338
  rotate: var(--_sonic-dial-angle);
323
339
  }
324
340
 
325
- &::after {
341
+ .sonic-dial-cap::after {
326
342
  inset: 16%;
327
343
  background:
328
344
  conic-gradient(
@@ -330,20 +346,19 @@
330
346
  calc(
331
347
  var(--_sonic-dial-face-drift) * var(--_sonic-dial-angle) - var(--_sonic-light-tilt)
332
348
  ),
333
- rgb(from var(--_sonic-shine) r g b / calc(0.16 * var(--_sonic-relief))),
349
+ color-mix(in srgb, var(--_sonic-shine) calc(16% * var(--_sonic-relief)), transparent),
334
350
  transparent 12%,
335
- rgb(from var(--_sonic-shade) r g b / calc(0.1 * var(--_sonic-relief))) 25%,
351
+ color-mix(in srgb, var(--_sonic-shade) calc(10% * var(--_sonic-relief)), transparent) 25%,
336
352
  transparent 38%,
337
- rgb(from var(--_sonic-shine) r g b / calc(0.12 * var(--_sonic-relief))) 50%,
353
+ color-mix(in srgb, var(--_sonic-shine) calc(12% * var(--_sonic-relief)), transparent) 50%,
338
354
  transparent 62%,
339
- rgb(from var(--_sonic-shade) r g b / calc(0.1 * var(--_sonic-relief))) 75%,
355
+ color-mix(in srgb, var(--_sonic-shade) calc(10% * var(--_sonic-relief)), transparent) 75%,
340
356
  transparent 88%,
341
- rgb(from var(--_sonic-shine) r g b / calc(0.16 * var(--_sonic-relief)))
357
+ color-mix(in srgb, var(--_sonic-shine) calc(16% * var(--_sonic-relief)), transparent)
342
358
  ),
343
359
  var(--_sonic-face);
344
360
  box-shadow: var(--_sonic-face-shadow);
345
361
  }
346
- }
347
362
 
348
363
  .sonic-dial-indicator {
349
364
  position: absolute;
@@ -351,8 +366,9 @@
351
366
  pointer-events: none;
352
367
  border-radius: 50%;
353
368
  rotate: calc(var(--_sonic-dial-start) + var(--_sonic-dial-angle));
369
+ }
354
370
 
355
- &::before {
371
+ .sonic-dial-indicator::before {
356
372
  --_sonic-dial-indicator-width: calc(
357
373
  var(--_sonic-dial-size) * var(--_sonic-dial-indicator-ratio)
358
374
  );
@@ -369,7 +385,7 @@
369
385
  background: var(--_sonic-ink);
370
386
  border-radius: calc(var(--_sonic-dial-indicator-width) / 2);
371
387
  box-shadow: inset 0 0 calc(0.75 * var(--_sonic-unit))
372
- rgb(from var(--_sonic-shade) r g b / calc(0.55 * var(--_sonic-relief)));
388
+ color-mix(in srgb, var(--_sonic-shade) calc(55% * var(--_sonic-relief)), transparent);
373
389
  clip-path: shape(
374
390
  from 0 100%,
375
391
  line to calc(50% - var(--_sonic-dial-indicator-tip)) var(--_sonic-dial-indicator-tip),
@@ -380,7 +396,6 @@
380
396
  );
381
397
  translate: -50% 0;
382
398
  }
383
- }
384
399
 
385
400
  :disabled > .sonic-dial > .sonic-dial-indicator::before {
386
401
  background: var(--_sonic-ink-disabled);
@@ -40,7 +40,8 @@
40
40
  inline-size: calc(var(--_sonic-envelope-size) * var(--_sonic-envelope-aspect-ratio));
41
41
  block-size: var(--_sonic-envelope-size);
42
42
  container-type: size;
43
- user-select: none;
43
+ -webkit-user-select: none;
44
+ user-select: none;
44
45
  isolation: isolate;
45
46
  }
46
47
 
@@ -55,23 +56,23 @@
55
56
  clip-path: inset(calc(-1 * var(--_sonic-envelope-inset)));
56
57
  }
57
58
 
58
- .sonic-envelope-bracket {
59
+ .sonic-envelope-reticle {
59
60
  --_sonic-point-size: calc(
60
61
  2 * var(--_sonic-envelope-size) * var(--_sonic-envelope-handle-ratio)
61
62
  );
62
- --_sonic-bracket-size: var(--_sonic-point-size);
63
+ --_sonic-reticle-size: var(--_sonic-point-size);
63
64
 
64
65
  position: absolute;
65
66
  inset: 0;
66
67
  overflow: clip;
67
68
  pointer-events: none;
69
+ }
68
70
 
69
- &[data-sonic-part='curve'] {
71
+ .sonic-envelope-reticle[data-sonic-part='curve'] {
70
72
  --_sonic-point-size: calc(
71
73
  2 * var(--_sonic-envelope-size) * var(--_sonic-envelope-curve-ratio)
72
74
  );
73
75
  }
74
- }
75
76
 
76
77
  .sonic-envelope-fill {
77
78
  fill: color-mix(in oklab, var(--_sonic-lit) 18%, transparent);
@@ -93,7 +94,7 @@
93
94
  }
94
95
 
95
96
  :is(.sonic-envelope-handle, .sonic-envelope-curve),
96
- .sonic-envelope-bracket::before {
97
+ .sonic-envelope-reticle::before {
97
98
  position: absolute;
98
99
  inset: auto auto
99
100
  calc(