@ponchia/ui 0.6.7 → 0.6.8

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 (90) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +3 -3
  3. package/annotations/index.d.ts.map +1 -1
  4. package/annotations/index.js +21 -3
  5. package/behaviors/carousel.d.ts.map +1 -1
  6. package/behaviors/carousel.js +91 -35
  7. package/behaviors/combobox.d.ts.map +1 -1
  8. package/behaviors/combobox.js +117 -43
  9. package/behaviors/command.d.ts.map +1 -1
  10. package/behaviors/command.js +74 -14
  11. package/behaviors/connectors.d.ts.map +1 -1
  12. package/behaviors/connectors.js +92 -9
  13. package/behaviors/crosshair.d.ts.map +1 -1
  14. package/behaviors/crosshair.js +47 -1
  15. package/behaviors/dialog.d.ts.map +1 -1
  16. package/behaviors/dialog.js +24 -9
  17. package/behaviors/disclosure.d.ts.map +1 -1
  18. package/behaviors/disclosure.js +33 -3
  19. package/behaviors/dismissible.d.ts.map +1 -1
  20. package/behaviors/dismissible.js +3 -2
  21. package/behaviors/forms.d.ts.map +1 -1
  22. package/behaviors/forms.js +67 -0
  23. package/behaviors/glyph.d.ts.map +1 -1
  24. package/behaviors/glyph.js +17 -2
  25. package/behaviors/inert.js +3 -2
  26. package/behaviors/internal.d.ts.map +1 -1
  27. package/behaviors/internal.js +2 -1
  28. package/behaviors/legend.d.ts +0 -5
  29. package/behaviors/legend.d.ts.map +1 -1
  30. package/behaviors/legend.js +45 -13
  31. package/behaviors/menu.d.ts.map +1 -1
  32. package/behaviors/menu.js +13 -8
  33. package/behaviors/modal.d.ts.map +1 -1
  34. package/behaviors/modal.js +77 -19
  35. package/behaviors/popover.d.ts +4 -3
  36. package/behaviors/popover.d.ts.map +1 -1
  37. package/behaviors/popover.js +89 -9
  38. package/behaviors/sources.d.ts.map +1 -1
  39. package/behaviors/sources.js +14 -2
  40. package/behaviors/splitter.d.ts.map +1 -1
  41. package/behaviors/splitter.js +149 -110
  42. package/behaviors/spotlight.d.ts.map +1 -1
  43. package/behaviors/spotlight.js +28 -2
  44. package/behaviors/table.d.ts.map +1 -1
  45. package/behaviors/table.js +103 -11
  46. package/behaviors/tabs.d.ts.map +1 -1
  47. package/behaviors/tabs.js +82 -18
  48. package/behaviors/theme.d.ts.map +1 -1
  49. package/behaviors/theme.js +25 -5
  50. package/classes/index.d.ts +15 -2
  51. package/classes/index.js +0 -1
  52. package/connectors/index.d.ts +39 -6
  53. package/connectors/index.d.ts.map +1 -1
  54. package/connectors/index.js +67 -9
  55. package/css/annotations.css +12 -0
  56. package/css/crosshair.css +27 -2
  57. package/css/feedback.css +2 -30
  58. package/css/navigation.css +12 -0
  59. package/css/tokens.css +16 -0
  60. package/dist/bronto.css +1 -1
  61. package/dist/css/analytical.css +1 -1
  62. package/dist/css/annotations.css +1 -1
  63. package/dist/css/crosshair.css +1 -1
  64. package/dist/css/feedback.css +1 -1
  65. package/dist/css/navigation.css +1 -1
  66. package/dist/css/report-kit.css +1 -1
  67. package/dist/css/tokens.css +1 -1
  68. package/docs/adr/0001-color-system.md +3 -2
  69. package/docs/annotations.md +12 -1
  70. package/docs/architecture.md +46 -13
  71. package/docs/command.md +4 -1
  72. package/docs/connectors.md +16 -0
  73. package/docs/crosshair.md +1 -1
  74. package/docs/dots.md +4 -1
  75. package/docs/glyphs.md +11 -0
  76. package/docs/migrations/0.2-to-0.3.md +1 -1
  77. package/docs/package-contract.md +5 -5
  78. package/docs/reporting.md +23 -12
  79. package/docs/stability.md +18 -2
  80. package/docs/theming.md +2 -2
  81. package/docs/usage.md +16 -2
  82. package/docs/vega.md +4 -4
  83. package/llms.txt +10 -5
  84. package/package.json +20 -4
  85. package/svelte/index.d.ts +71 -45
  86. package/svelte/index.d.ts.map +1 -1
  87. package/svelte/index.js +29 -2
  88. package/vue/index.d.ts +42 -5
  89. package/vue/index.d.ts.map +1 -1
  90. package/vue/index.js +32 -1
package/behaviors/tabs.js CHANGED
@@ -1,4 +1,12 @@
1
- import { hasDom, resolveHost, noop, bindOnce, nextFieldUid, collectHosts } from './internal.js';
1
+ import {
2
+ hasDom,
3
+ resolveHost,
4
+ noop,
5
+ bindOnce,
6
+ nextFieldUid,
7
+ collectHosts,
8
+ closestSafe,
9
+ } from './internal.js';
2
10
 
3
11
  /**
4
12
  * Wire `[data-bronto-tabs]` groups for full keyboard a11y. The framework
@@ -24,6 +32,23 @@ export function initTabs({ root } = {}) {
24
32
  if (!host) return noop;
25
33
  const cleanups = [];
26
34
  const groups = collectHosts(host, '[data-bronto-tabs]');
35
+ const snapshotAttrs = (el, names) => {
36
+ const out = {};
37
+ for (const name of names) {
38
+ out[name] = {
39
+ had: el.hasAttribute(name),
40
+ value: el.getAttribute(name),
41
+ };
42
+ }
43
+ return out;
44
+ };
45
+ const restoreAttrs = (el, attrs) => {
46
+ for (const [name, attr] of Object.entries(attrs)) {
47
+ if (attr.had) el.setAttribute(name, attr.value);
48
+ else el.removeAttribute(name);
49
+ }
50
+ };
51
+
27
52
  for (const group of groups) {
28
53
  // Own group only — a tab/panel inside a nested [data-bronto-tabs]
29
54
  // belongs to that inner group, not this one.
@@ -31,20 +56,43 @@ export function initTabs({ root } = {}) {
31
56
  const tabs = [...group.querySelectorAll('.ui-tab')].filter(owned);
32
57
  const panels = [...group.querySelectorAll('.ui-tabs__panel')].filter(owned);
33
58
  if (!tabs.length) continue;
34
- const list = group.querySelector('.ui-tabs__list');
35
- if (list) list.setAttribute('role', 'tablist');
36
-
37
- // APG: bind each tab to its panel (aria-controls) and back
38
- // (aria-labelledby), minting stable ids only where absent.
39
- for (const t of tabs) {
40
- const p = panels.find((x) => x.dataset.panel === t.dataset.tab);
41
- if (!p) continue;
42
- const n = nextFieldUid();
43
- if (!t.id) t.id = `bronto-tab-${n}`;
44
- if (!p.id) p.id = `bronto-tabpanel-${n}`;
45
- t.setAttribute('aria-controls', p.id);
46
- p.setAttribute('aria-labelledby', t.id);
47
- }
59
+ const list = [...group.querySelectorAll('.ui-tabs__list')].find(owned);
60
+ const rememberState = () => ({
61
+ list: list ? snapshotAttrs(list, ['role']) : null,
62
+ tabs: new Map(
63
+ tabs.map((tab) => [
64
+ tab,
65
+ {
66
+ active: tab.classList.contains('is-active'),
67
+ attrs: snapshotAttrs(tab, ['id', 'role', 'aria-selected', 'aria-controls', 'tabindex']),
68
+ },
69
+ ]),
70
+ ),
71
+ panels: new Map(
72
+ panels.map((panel) => [
73
+ panel,
74
+ {
75
+ hidden: panel.hidden,
76
+ attrs: snapshotAttrs(panel, ['id', 'role', 'aria-labelledby', 'tabindex']),
77
+ },
78
+ ]),
79
+ ),
80
+ });
81
+ const restoreState = (state) => {
82
+ if (list && state.list) restoreAttrs(list, state.list);
83
+ for (const tab of tabs) {
84
+ const tabState = state.tabs.get(tab);
85
+ if (!tabState) continue;
86
+ tab.classList.toggle('is-active', tabState.active);
87
+ restoreAttrs(tab, tabState.attrs);
88
+ }
89
+ for (const panel of panels) {
90
+ const panelState = state.panels.get(panel);
91
+ if (!panelState) continue;
92
+ panel.hidden = panelState.hidden;
93
+ restoreAttrs(panel, panelState.attrs);
94
+ }
95
+ };
48
96
 
49
97
  const select = (tab) => {
50
98
  for (const t of tabs) {
@@ -70,14 +118,15 @@ export function initTabs({ root } = {}) {
70
118
  const onClick = (e) => {
71
119
  // `tabs` is filtered to this group, so membership (not mere DOM
72
120
  // containment) is what isolates nested [data-bronto-tabs] groups.
73
- const tab = e.target.closest('.ui-tab');
121
+ const tab = closestSafe(e.target, '.ui-tab');
74
122
  if (tab && tabs.includes(tab)) {
123
+ e.preventDefault();
75
124
  select(tab);
76
125
  tab.focus();
77
126
  }
78
127
  };
79
128
  const onKey = (e) => {
80
- const i = tabs.indexOf(e.target.closest('.ui-tab'));
129
+ const i = tabs.indexOf(closestSafe(e.target, '.ui-tab'));
81
130
  if (i < 0) return;
82
131
  let n = i;
83
132
  if (e.key === 'ArrowRight' || e.key === 'ArrowDown') n = (i + 1) % tabs.length;
@@ -90,14 +139,29 @@ export function initTabs({ root } = {}) {
90
139
  select(tabs[n]);
91
140
  tabs[n].focus();
92
141
  };
93
- select(tabs.find((t) => t.classList.contains('is-active')) || tabs[0]);
94
142
  cleanups.push(
95
143
  bindOnce(group, 'tabs', () => {
144
+ const state = rememberState();
145
+ if (list) list.setAttribute('role', 'tablist');
146
+
147
+ // APG: bind each tab to its panel (aria-controls) and back
148
+ // (aria-labelledby), minting stable ids only where absent.
149
+ for (const t of tabs) {
150
+ const p = panels.find((x) => x.dataset.panel === t.dataset.tab);
151
+ if (!p) continue;
152
+ const n = nextFieldUid();
153
+ if (!t.id) t.id = `bronto-tab-${n}`;
154
+ if (!p.id) p.id = `bronto-tabpanel-${n}`;
155
+ t.setAttribute('aria-controls', p.id);
156
+ p.setAttribute('aria-labelledby', t.id);
157
+ }
158
+ select(tabs.find((t) => t.classList.contains('is-active')) || tabs[0]);
96
159
  group.addEventListener('click', onClick);
97
160
  group.addEventListener('keydown', onKey);
98
161
  return () => {
99
162
  group.removeEventListener('click', onClick);
100
163
  group.removeEventListener('keydown', onKey);
164
+ restoreState(state);
101
165
  };
102
166
  }),
103
167
  );
@@ -1 +1 @@
1
- {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["theme.js"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH;;;;;;;GAOG;AACH,wDAHW,cAAc,GACZ,IAAI,CAahB;AAED;;;;;;;;;;;;;;GAcG;AACH,uDAHW,gBAAgB,GAAG,OAAO,eAAe,EAAE,YAAY,GACrD,OAAO,eAAe,EAAE,OAAO,CAmD3C;;;;;;;;;;6BA5FY,gBAAgB,GAAG;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE;;;;;WAIpC,OAAO,GAAG,MAAM"}
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["theme.js"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH;;;;;;;GAOG;AACH,wDAHW,cAAc,GACZ,IAAI,CAahB;AAED;;;;;;;;;;;;;;GAcG;AACH,uDAHW,gBAAgB,GAAG,OAAO,eAAe,EAAE,YAAY,GACrD,OAAO,eAAe,EAAE,OAAO,CAuE3C;;;;;;;;;;6BAhHY,gBAAgB,GAAG;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE;;;;;WAIpC,OAAO,GAAG,MAAM"}
@@ -1,4 +1,4 @@
1
- import { hasDom, resolveHost, noop, bindOnce, collectHosts } from './internal.js';
1
+ import { hasDom, resolveHost, noop, bindOnce, collectHosts, closestSafe } from './internal.js';
2
2
 
3
3
  const THEMES = ['light', 'dark'];
4
4
 
@@ -53,7 +53,18 @@ export function initThemeToggle({ storageKey = 'bronto-theme', root } = {}) {
53
53
  if (!hasDom()) return noop;
54
54
  const host = resolveHost(root);
55
55
  if (!host) return noop;
56
- const docEl = document.documentElement;
56
+ const doc = host.nodeType === 9 ? host : host.ownerDocument || document;
57
+ const docEl = doc.documentElement;
58
+ const toggleStates = new Map();
59
+
60
+ const rememberToggle = (el) => {
61
+ if (!toggleStates.has(el)) {
62
+ toggleStates.set(el, {
63
+ had: el.hasAttribute('aria-pressed'),
64
+ value: el.getAttribute('aria-pressed'),
65
+ });
66
+ }
67
+ };
57
68
 
58
69
  const prefersDark = () =>
59
70
  typeof matchMedia === 'function' && matchMedia('(prefers-color-scheme: dark)').matches;
@@ -67,6 +78,7 @@ export function initThemeToggle({ storageKey = 'bronto-theme', root } = {}) {
67
78
  const reflect = () => {
68
79
  const c = current();
69
80
  collectHosts(host, '[data-bronto-theme-toggle]').forEach((el) => {
81
+ rememberToggle(el);
70
82
  const forced = el.getAttribute('data-bronto-theme-toggle');
71
83
  // A forced control is "pressed" when its theme is the active one;
72
84
  // a plain toggle reflects whether dark is active.
@@ -76,8 +88,9 @@ export function initThemeToggle({ storageKey = 'bronto-theme', root } = {}) {
76
88
  };
77
89
 
78
90
  const onClick = (e) => {
79
- const trigger = e.target.closest('[data-bronto-theme-toggle]');
91
+ const trigger = closestSafe(e.target, '[data-bronto-theme-toggle]');
80
92
  if (!trigger || !host.contains(trigger)) return;
93
+ e.preventDefault();
81
94
  const forced = trigger.getAttribute('data-bronto-theme-toggle');
82
95
  const next = THEMES.includes(forced) ? forced : current() === 'dark' ? 'light' : 'dark';
83
96
  docEl.setAttribute('data-theme', next);
@@ -92,10 +105,17 @@ export function initThemeToggle({ storageKey = 'bronto-theme', root } = {}) {
92
105
  );
93
106
  };
94
107
 
95
- applyStoredTheme({ storageKey });
108
+ applyStoredTheme({ storageKey, root: docEl });
96
109
  reflect();
97
110
  return bindOnce(host, 'themeToggle', () => {
98
111
  host.addEventListener('click', onClick);
99
- return () => host.removeEventListener('click', onClick);
112
+ return () => {
113
+ host.removeEventListener('click', onClick);
114
+ for (const [el, state] of toggleStates) {
115
+ if (state.had) el.setAttribute('aria-pressed', state.value);
116
+ else el.removeAttribute('aria-pressed');
117
+ }
118
+ toggleStates.clear();
119
+ };
100
120
  });
101
121
  }
@@ -959,9 +959,22 @@ export interface ValueAttrs {
959
959
  'aria-valuemax': number;
960
960
  style: { '--value': number };
961
961
  }
962
+ export interface MeterValueAttrs extends ValueAttrs {
963
+ role: 'meter';
964
+ }
965
+ export interface ProgressValueAttrs extends ValueAttrs {
966
+ role: 'progressbar';
967
+ }
968
+ export interface IndeterminateProgressAttrs {
969
+ role: 'progressbar';
970
+ 'aria-busy': 'true';
971
+ }
962
972
  export interface Attrs {
963
- meter(value: number, opts?: ValueRangeOpts): ValueAttrs;
964
- progress(value: number, opts?: ValueRangeOpts): ValueAttrs;
973
+ meter(value: number, opts?: ValueRangeOpts): MeterValueAttrs;
974
+ progress(value: number, opts?: ValueRangeOpts): ProgressValueAttrs;
975
+ progress(value?: undefined, opts?: ValueRangeOpts): IndeterminateProgressAttrs;
976
+ dotbar(value: number, opts?: ValueRangeOpts): ProgressValueAttrs;
977
+ dotbar(value?: undefined, opts?: ValueRangeOpts): IndeterminateProgressAttrs;
965
978
  }
966
979
  /** Set the painted value AND its ARIA together so they cannot drift. */
967
980
  export declare const attrs: Attrs;
package/classes/index.js CHANGED
@@ -467,7 +467,6 @@ export const cls = Object.freeze({
467
467
  crosshairLineX: 'ui-crosshair__line--x',
468
468
  crosshairLineY: 'ui-crosshair__line--y',
469
469
  crosshairBadge: 'ui-crosshair__badge',
470
- readout: 'ui-readout',
471
470
  // selection-state vocabulary (cross-cutting — css/selection.css)
472
471
  sel: 'ui-sel',
473
472
  selOn: 'ui-sel--on',
@@ -1,9 +1,42 @@
1
- export function finite(name: any, value: any, fallback: any): any;
2
- export function dimension(name: any, value: any, fallback: any): any;
3
- export function roundNumber(value: any): number;
4
- export function fmt(value: any): string;
5
- export function point(x: any, y: any): string;
6
- export function clamp(value: any, min: any, max: any): any;
1
+ /**
2
+ * Resolve a numeric option with an optional fallback.
3
+ * @param {string} name
4
+ * @param {number | null | undefined} value
5
+ * @param {number | null | undefined} [fallback]
6
+ * @returns {number}
7
+ */
8
+ export function finite(name: string, value: number | null | undefined, fallback?: number | null | undefined): number;
9
+ /**
10
+ * Resolve a non-negative numeric option with an optional fallback.
11
+ * @param {string} name
12
+ * @param {number | null | undefined} value
13
+ * @param {number | null | undefined} [fallback]
14
+ * @returns {number}
15
+ */
16
+ export function dimension(name: string, value: number | null | undefined, fallback?: number | null | undefined): number;
17
+ /**
18
+ * @param {number} value
19
+ * @returns {number}
20
+ */
21
+ export function roundNumber(value: number): number;
22
+ /**
23
+ * @param {number} value
24
+ * @returns {string}
25
+ */
26
+ export function fmt(value: number): string;
27
+ /**
28
+ * @param {number} x
29
+ * @param {number} y
30
+ * @returns {string}
31
+ */
32
+ export function point(x: number, y: number): string;
33
+ /**
34
+ * @param {number} value
35
+ * @param {number} min
36
+ * @param {number} max
37
+ * @returns {number}
38
+ */
39
+ export function clamp(value: number, min: number, max: number): number;
7
40
  /**
8
41
  * A point on a rect's edge (or centre). `rect` is `{ x, y, width, height }`.
9
42
  * @param {Rect} rect
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":"AAkDA,kEAIC;AAED,qEAIC;AAKD,gDAGC;AAED,wCAEC;AAED,8CAEC;AAID,2DAGC;AAED;;;;;GAKG;AACH,kCAJW,IAAI,SACJ,IAAI,GACF,KAAK,CAoBjB;AAED;;;;;GAKG;AACH,mCAJW,KAAK,MACL,KAAK,GACH,MAAM,CAOlB;AAED;;;;;GAKG;AACH,mCAJW,KAAK,MACL,KAAK,GACH,MAAM,CAOlB;AAED;;;;;;GAMG;AACH,gCALW,KAAK,MACL,KAAK,SACL;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACd,MAAM,CAgBlB;AAED;;;;;;GAMG;AACH,gCALW,KAAK,MACL,KAAK,SACL;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACpB,MAAM,CAclB;AAED;;;;GAIG;AACH,qCAHW,oBAAoB,GAClB,MAAM,CAOlB;AAED;;;;;;;;GAQG;AACH,6BAPW,KAAK,SACL,MAAM,SACN,MAAM,WACN,MAAM,GAEJ,MAAM,CAalB;AAED;;;;;GAKG;AACH,2BAJW,KAAK,WACL,MAAM,GACJ,MAAM,CAUlB;AAED;;;;;;;;;GASG;AACH,+BANW,MAAM,OACN,MAAM,SACN,MAAM,UACN,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,oCAJW,IAAI,UACJ,IAAI,GACF;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,IAAI,CAAA;CAAE,CAWpC;AAED;;;;;;;;GAQG;AACH,sCALW,KAAK,MACL,KAAK,UACL,cAAc,GACZ,MAAM,CAQlB;AAED;;;;;;GAMG;AACH,oCAHW,mBAAmB,GACjB,kBAAkB,CAW9B;AAvSD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAMH,wBAAyB,IAAI,CAAC;oBAhCjB;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE;mBACxB;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;mBACvD,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ;6BAC9C,UAAU,GAAG,OAAO,GAAG,OAAO;;UAG7B,KAAK;QACL,KAAK;;;;;;;;;;;;cAML,IAAI;YACJ,IAAI;;;;;;;;;;;;;;OAQJ,MAAM;UACN,KAAK;QACL,KAAK;;;;WACL,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":"AAkDA;;;;;;GAMG;AACH,6BALW,MAAM,SACN,MAAM,GAAG,IAAI,GAAG,SAAS,aACzB,MAAM,GAAG,IAAI,GAAG,SAAS,GACvB,MAAM,CAMlB;AAED;;;;;;GAMG;AACH,gCALW,MAAM,SACN,MAAM,GAAG,IAAI,GAAG,SAAS,aACzB,MAAM,GAAG,IAAI,GAAG,SAAS,GACvB,MAAM,CAMlB;AAKD;;;GAGG;AACH,mCAHW,MAAM,GACJ,MAAM,CAKlB;AAED;;;GAGG;AACH,2BAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,yBAJW,MAAM,KACN,MAAM,GACJ,MAAM,CAIlB;AAID;;;;;GAKG;AACH,6BALW,MAAM,OACN,MAAM,OACN,MAAM,GACJ,MAAM,CAKlB;AAqBD;;;;;GAKG;AACH,kCAJW,IAAI,SACJ,IAAI,GACF,KAAK,CAoBjB;AAED;;;;;GAKG;AACH,mCAJW,KAAK,MACL,KAAK,GACH,MAAM,CAOlB;AAED;;;;;GAKG;AACH,mCAJW,KAAK,MACL,KAAK,GACH,MAAM,CAOlB;AAED;;;;;;GAMG;AACH,gCALW,KAAK,MACL,KAAK,SACL;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACd,MAAM,CAgBlB;AAED;;;;;;GAMG;AACH,gCALW,KAAK,MACL,KAAK,SACL;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACpB,MAAM,CAclB;AAED;;;;GAIG;AACH,qCAHW,oBAAoB,GAClB,MAAM,CAQlB;AAED;;;;;;;;GAQG;AACH,6BAPW,KAAK,SACL,MAAM,SACN,MAAM,WACN,MAAM,GAEJ,MAAM,CAalB;AAED;;;;;GAKG;AACH,2BAJW,KAAK,WACL,MAAM,GACJ,MAAM,CAUlB;AAED;;;;;;;;;GASG;AACH,+BANW,MAAM,OACN,MAAM,SACN,MAAM,UACN,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,oCAJW,IAAI,UACJ,IAAI,GACF;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,IAAI,CAAA;CAAE,CAWpC;AAED;;;;;;;;GAQG;AACH,sCALW,KAAK,MACL,KAAK,UACL,cAAc,GACZ,MAAM,CASlB;AAED;;;;;;GAMG;AACH,oCAHW,mBAAmB,GACjB,kBAAkB,CAe9B;AAjWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAMH,wBAAyB,IAAI,CAAC;oBAhCjB;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE;mBACxB;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;mBACvD,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ;6BAC9C,UAAU,GAAG,OAAO,GAAG,OAAO;;UAG7B,KAAK;QACL,KAAK;;;;;;;;;;;;cAML,IAAI;YACJ,IAAI;;;;;;;;;;;;;;OAQJ,MAAM;UACN,KAAK;QACL,KAAK;;;;WACL,MAAM"}
@@ -48,12 +48,26 @@
48
48
  // builders below.
49
49
  export const PRECISION = 1000;
50
50
 
51
+ /**
52
+ * Resolve a numeric option with an optional fallback.
53
+ * @param {string} name
54
+ * @param {number | null | undefined} value
55
+ * @param {number | null | undefined} [fallback]
56
+ * @returns {number}
57
+ */
51
58
  export function finite(name, value, fallback) {
52
59
  const v = value ?? fallback;
53
60
  if (!Number.isFinite(v)) throw new TypeError(`${name} must be a finite number`);
54
61
  return v;
55
62
  }
56
63
 
64
+ /**
65
+ * Resolve a non-negative numeric option with an optional fallback.
66
+ * @param {string} name
67
+ * @param {number | null | undefined} value
68
+ * @param {number | null | undefined} [fallback]
69
+ * @returns {number}
70
+ */
57
71
  export function dimension(name, value, fallback) {
58
72
  const v = finite(name, value, fallback);
59
73
  if (v < 0) throw new RangeError(`${name} must be greater than or equal to 0`);
@@ -63,26 +77,64 @@ export function dimension(name, value, fallback) {
63
77
  // Round to PRECISION, normalising -0 → 0, and return the NUMBER (the numeric
64
78
  // core `fmt` stringifies). Shared with the annotations layer for the rounded
65
79
  // coordinates it echoes back to the host. (code-quality audit Q5.)
80
+ /**
81
+ * @param {number} value
82
+ * @returns {number}
83
+ */
66
84
  export function roundNumber(value) {
67
85
  const rounded = Math.round((Object.is(value, -0) ? 0 : value) * PRECISION) / PRECISION;
68
86
  return Object.is(rounded, -0) ? 0 : rounded;
69
87
  }
70
88
 
89
+ /**
90
+ * @param {number} value
91
+ * @returns {string}
92
+ */
71
93
  export function fmt(value) {
72
94
  return String(roundNumber(value));
73
95
  }
74
96
 
97
+ /**
98
+ * @param {number} x
99
+ * @param {number} y
100
+ * @returns {string}
101
+ */
75
102
  export function point(x, y) {
76
103
  return `${fmt(x)},${fmt(y)}`;
77
104
  }
78
105
 
79
106
  // Guarded form (returns min when the range is inverted) — the reconciled body;
80
107
  // connectors only ever calls clamp(v, 0, 1) so this is output-identical here.
108
+ /**
109
+ * @param {number} value
110
+ * @param {number} min
111
+ * @param {number} max
112
+ * @returns {number}
113
+ */
81
114
  export function clamp(value, min, max) {
82
115
  if (max < min) return min;
83
116
  return Math.min(max, Math.max(min, value));
84
117
  }
85
118
 
119
+ function connectorShape(value) {
120
+ const shape = value ?? 'straight';
121
+ if (shape === 'straight' || shape === 'elbow' || shape === 'curve') return shape;
122
+ throw new TypeError('shape must be "straight", "elbow" or "curve"');
123
+ }
124
+
125
+ function sideValue(value) {
126
+ const side = value ?? 'center';
127
+ if (
128
+ side === 'top' ||
129
+ side === 'right' ||
130
+ side === 'bottom' ||
131
+ side === 'left' ||
132
+ side === 'center'
133
+ )
134
+ return side;
135
+ throw new TypeError('side must be "top", "right", "bottom", "left" or "center"');
136
+ }
137
+
86
138
  /**
87
139
  * A point on a rect's edge (or centre). `rect` is `{ x, y, width, height }`.
88
140
  * @param {Rect} rect
@@ -90,11 +142,11 @@ export function clamp(value, min, max) {
90
142
  * @returns {Point}
91
143
  */
92
144
  export function anchorPoint(rect, side = 'center') {
93
- const x = finite('rect.x', rect?.x, 0);
94
- const y = finite('rect.y', rect?.y, 0);
95
- const w = dimension('rect.width', rect?.width, 0);
96
- const h = dimension('rect.height', rect?.height, 0);
97
- switch (side) {
145
+ const x = finite('rect.x', rect?.x);
146
+ const y = finite('rect.y', rect?.y);
147
+ const w = dimension('rect.width', rect?.width);
148
+ const h = dimension('rect.height', rect?.height);
149
+ switch (sideValue(side)) {
98
150
  case 'top':
99
151
  return { x: x + w / 2, y };
100
152
  case 'bottom':
@@ -185,7 +237,8 @@ export function curvePath(from, to, opts = {}) {
185
237
  * @returns {string}
186
238
  */
187
239
  export function connectorPath(opts = {}) {
188
- const { from, to, shape = 'straight' } = opts;
240
+ const { from, to } = opts;
241
+ const shape = connectorShape(opts.shape);
189
242
  if (shape === 'elbow') return elbowPath(from, to, opts);
190
243
  if (shape === 'curve') return curvePath(from, to, opts);
191
244
  return straightPath(from, to);
@@ -270,7 +323,8 @@ export function autoSides(fromRect, toRect) {
270
323
  * @returns {number}
271
324
  */
272
325
  export function endTangentAngle(from, to, shape = 'straight') {
273
- if (shape === 'straight') return angleBetween(from, to);
326
+ const resolved = connectorShape(shape);
327
+ if (resolved === 'straight') return angleBetween(from, to);
274
328
  const dx = finite('to.x', to?.x) - finite('from.x', from?.x);
275
329
  const dy = finite('to.y', to?.y) - finite('from.y', from?.y);
276
330
  if (Math.abs(dx) >= Math.abs(dy)) return dx >= 0 ? 0 : Math.PI;
@@ -285,10 +339,14 @@ export function endTangentAngle(from, to, shape = 'straight') {
285
339
  * @returns {ConnectRectsResult}
286
340
  */
287
341
  export function connectRects(opts = {}) {
288
- const { fromRect, toRect, shape = 'straight', curvature, mid } = opts;
342
+ const { fromRect, toRect, curvature, mid } = opts;
343
+ const shape = connectorShape(opts.shape);
289
344
  // Honor each side override independently; auto-pick whichever is unset.
290
345
  const auto = autoSides(fromRect, toRect);
291
- const sides = { from: opts.fromSide || auto.from, to: opts.toSide || auto.to };
346
+ const sides = {
347
+ from: opts.fromSide == null ? auto.from : sideValue(opts.fromSide),
348
+ to: opts.toSide == null ? auto.to : sideValue(opts.toSide),
349
+ };
292
350
  const from = anchorPoint(fromRect, sides.from);
293
351
  const to = anchorPoint(toRect, sides.to);
294
352
  const d = connectorPath({ from, to, shape, curvature, mid });
@@ -278,8 +278,20 @@
278
278
  animation: none !important;
279
279
  opacity: 1;
280
280
  stroke-dashoffset: 0;
281
+ }
282
+
283
+ .ui-annotation__subject,
284
+ .ui-annotation__connector,
285
+ .ui-annotation__note-line,
286
+ .ui-annotation__badge {
281
287
  transform: none;
282
288
  }
289
+
290
+ .ui-annotation--draw .ui-annotation__connector,
291
+ .ui-annotation--draw .ui-annotation__note-line {
292
+ stroke-dasharray: none;
293
+ stroke-dashoffset: 0;
294
+ }
283
295
  }
284
296
 
285
297
  @media (forced-colors: active) {
package/css/crosshair.css CHANGED
@@ -13,6 +13,9 @@
13
13
  --crosshair-x: 0;
14
14
  --crosshair-y: 0;
15
15
  --crosshair-color: var(--accent);
16
+ --crosshair-readout-gap: 0.35rem;
17
+ --crosshair-readout-x: var(--crosshair-readout-gap);
18
+ --crosshair-readout-y: var(--crosshair-readout-gap);
16
19
 
17
20
  inset: 0;
18
21
  opacity: 0;
@@ -25,6 +28,22 @@
25
28
  opacity: 1;
26
29
  }
27
30
 
31
+ .ui-crosshair[data-readout-inline='before'] {
32
+ --crosshair-readout-x: calc(-100% - var(--crosshair-readout-gap));
33
+ }
34
+
35
+ .ui-crosshair[data-readout-block='above'] {
36
+ --crosshair-readout-y: calc(-100% - var(--crosshair-readout-gap));
37
+ }
38
+
39
+ .ui-crosshair:dir(rtl) {
40
+ --crosshair-readout-x: calc(100% + var(--crosshair-readout-gap));
41
+ }
42
+
43
+ .ui-crosshair:dir(rtl)[data-readout-inline='before'] {
44
+ --crosshair-readout-x: calc(-1 * var(--crosshair-readout-gap));
45
+ }
46
+
28
47
  @media (prefers-reduced-motion: reduce) {
29
48
  .ui-crosshair {
30
49
  transition: none;
@@ -76,8 +95,9 @@
76
95
  color: var(--text);
77
96
  }
78
97
 
79
- /* A pinned readout chip — host fills the content; it follows the crosshair. */
80
- .ui-readout {
98
+ /* A pinned readout chip — host fills the content; it follows the crosshair.
99
+ Scoped so report-kit/crosshair imports do not restyle standalone dot readouts. */
100
+ .ui-crosshair .ui-readout {
81
101
  background: var(--panel);
82
102
  border: 1px solid var(--line);
83
103
  border-radius: var(--radius-sm);
@@ -87,10 +107,15 @@
87
107
  font-size: var(--text-xs);
88
108
  inset-block-start: var(--crosshair-y);
89
109
  inset-inline-start: var(--crosshair-x);
110
+ max-inline-size: calc(100% - var(--crosshair-readout-gap) * 2);
111
+ overflow: hidden;
90
112
  padding-block: 0.2rem;
91
113
  padding-inline: 0.4rem;
92
114
  pointer-events: none;
93
115
  position: absolute;
116
+ text-overflow: ellipsis;
117
+ transform: translate(var(--crosshair-readout-x), var(--crosshair-readout-y));
118
+ white-space: nowrap;
94
119
  }
95
120
 
96
121
  @media (forced-colors: active) {
package/css/feedback.css CHANGED
@@ -335,9 +335,7 @@
335
335
  position: absolute;
336
336
  text-transform: uppercase;
337
337
  transform: translate(-50%, 4px);
338
- transition:
339
- opacity var(--duration-fast) var(--ease-standard),
340
- transform var(--duration-fast) var(--ease-standard);
338
+ transition: opacity var(--duration-fast) var(--ease-standard);
341
339
  white-space: nowrap;
342
340
  z-index: var(--z-popover);
343
341
  }
@@ -348,33 +346,6 @@
348
346
  transform: translate(-50%, 0);
349
347
  }
350
348
 
351
- /* Progressive enhancement: where CSS anchor positioning exists, lift
352
- the bubble out of the normal flow so it can't be clipped by an
353
- ancestor's overflow/scroll and auto-flips at the viewport edge.
354
- Unsupported browsers keep the absolutely-positioned fallback above
355
- (fine for short labels; use .ui-popover + initPopover for rich or
356
- edge-critical content). */
357
- @supports (anchor-name: --x) {
358
- .ui-tooltip {
359
- anchor-name: --ui-tooltip;
360
- }
361
-
362
- .ui-tooltip__bubble {
363
- inset: auto;
364
- margin-block-end: 0.5rem;
365
- position: fixed;
366
- position-anchor: --ui-tooltip;
367
- position-area: block-start center;
368
- position-try-fallbacks: flip-block;
369
- transform: translateY(4px);
370
- }
371
-
372
- .ui-tooltip:hover .ui-tooltip__bubble,
373
- .ui-tooltip:focus-within .ui-tooltip__bubble {
374
- transform: translateY(0);
375
- }
376
- }
377
-
378
349
  /* Popover surface — a top-layer panel positioned by initPopover (JS
379
350
  collision-aware, dependency-free). Uses the native [popover] top
380
351
  layer when available so it never clips; the class styles it either
@@ -389,6 +360,7 @@
389
360
  inline-size: max-content;
390
361
  margin: 0;
391
362
  max-inline-size: min(22rem, calc(100vw - 2rem));
363
+ overflow: auto;
392
364
  padding: var(--space-sm) var(--space-md);
393
365
  position: fixed;
394
366
  z-index: var(--z-popover);
@@ -70,6 +70,18 @@
70
70
  transform: translateX(0.6rem);
71
71
  }
72
72
 
73
+ @media (prefers-color-scheme: dark) {
74
+ :root:not([data-theme='light']) .ui-themetoggle__thumb {
75
+ background: var(--accent);
76
+ transform: translateX(0.6rem);
77
+ }
78
+
79
+ :root[dir='rtl']:not([data-theme='light']) .ui-themetoggle__thumb,
80
+ :root:not([data-theme='light']) [dir='rtl'] .ui-themetoggle__thumb {
81
+ transform: translateX(-0.6rem);
82
+ }
83
+ }
84
+
73
85
  [dir='rtl'][data-theme='dark'] .ui-themetoggle__thumb,
74
86
  [dir='rtl'] [data-theme='dark'] .ui-themetoggle__thumb {
75
87
  transform: translateX(-0.6rem);
package/css/tokens.css CHANGED
@@ -360,9 +360,25 @@
360
360
  --line: #d9d9d9;
361
361
  --line-strong: #b3b3b3;
362
362
  --success: #2f7d4f;
363
+ --success-soft: rgb(47, 125, 79, 0.12);
363
364
  --danger: #c01622;
365
+ --danger-soft: rgb(192, 22, 34, 0.1);
364
366
  --warning: #806414;
367
+ --warning-soft: rgb(128, 100, 20, 0.13);
365
368
  --info: #1f63c4;
369
+ --info-soft: rgb(31, 99, 196, 0.12);
366
370
  --accent: #d71921;
371
+ --bg-accent: color-mix(in srgb, var(--accent) 6%, transparent);
372
+ --accent-ramp-end: #ffffff;
373
+ --accent-strong: color-mix(in srgb, var(--accent) 83%, #000);
374
+ --accent-text: var(--accent-strong);
375
+ --accent-soft: color-mix(in srgb, var(--accent) 10%, transparent);
376
+ --button-text: #ffffff;
377
+ --on-accent: var(--button-text);
378
+ --field-dot: rgb(17, 17, 17, 0.16);
379
+ --field-dot-hot: rgb(17, 17, 17, 0.4);
380
+ --field-dot-accent: color-mix(in srgb, var(--accent) 78%, transparent);
381
+ --focus-ring: var(--accent);
382
+ --code-bg: rgb(17, 17, 17, 0.05);
367
383
  }
368
384
  }