@silverpoint/core 0.3.0 → 0.7.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 (47) hide show
  1. package/README.md +18 -0
  2. package/dist/catalog.d.ts +97 -0
  3. package/dist/catalog.js +8004 -0
  4. package/dist/catalog.js.map +1 -0
  5. package/dist/{chunk-I6BAZ7MJ.js → chunk-46QELMCG.js} +41 -236
  6. package/dist/chunk-46QELMCG.js.map +1 -0
  7. package/dist/chunk-5MP3BLZK.js +48 -0
  8. package/dist/chunk-5MP3BLZK.js.map +1 -0
  9. package/dist/chunk-6YFO7OP3.js +84 -0
  10. package/dist/chunk-6YFO7OP3.js.map +1 -0
  11. package/dist/chunk-ANJSW5JX.js +93 -0
  12. package/dist/chunk-ANJSW5JX.js.map +1 -0
  13. package/dist/chunk-E5CC2NNQ.js +172 -0
  14. package/dist/chunk-E5CC2NNQ.js.map +1 -0
  15. package/dist/chunk-G7N4X5XD.js +12 -0
  16. package/dist/chunk-G7N4X5XD.js.map +1 -0
  17. package/dist/chunk-KFBSQH5G.js +34 -0
  18. package/dist/chunk-KFBSQH5G.js.map +1 -0
  19. package/dist/chunk-NI7B6UEO.js +24 -0
  20. package/dist/chunk-NI7B6UEO.js.map +1 -0
  21. package/dist/chunk-WYFRHI4S.js +179 -0
  22. package/dist/chunk-WYFRHI4S.js.map +1 -0
  23. package/dist/contrast-siWF_R5o.d.ts +30 -0
  24. package/dist/dashboard-demos.d.ts +2 -2
  25. package/dist/index.d.ts +16 -60
  26. package/dist/index.js +34 -179
  27. package/dist/index.js.map +1 -1
  28. package/dist/model-DVVt59AB.d.ts +53 -0
  29. package/dist/palettes.d.ts +29 -0
  30. package/dist/palettes.js +79 -0
  31. package/dist/palettes.js.map +1 -0
  32. package/dist/{props-3G1XYd-r.d.ts → props-BZ9CcrGv.d.ts} +394 -2
  33. package/dist/timeline.d.ts +90 -0
  34. package/dist/timeline.js +125 -0
  35. package/dist/timeline.js.map +1 -0
  36. package/dist/{types-CbVMKvyQ.d.ts → types-BT3Xm7fP.d.ts} +3 -1
  37. package/dist/types-D5ys35fF.d.ts +576 -0
  38. package/dist/types-DHBJ0PzV.d.ts +57 -0
  39. package/dist/ui-demos.d.ts +14 -3
  40. package/dist/ui-demos.js +163 -1
  41. package/dist/ui-demos.js.map +1 -1
  42. package/dist/ui.d.ts +346 -325
  43. package/dist/ui.js +933 -118
  44. package/dist/ui.js.map +1 -1
  45. package/dist/view-DteS9glE.d.ts +82 -0
  46. package/package.json +19 -1
  47. package/dist/chunk-I6BAZ7MJ.js.map +0 -1
package/dist/ui.js CHANGED
@@ -1,4 +1,13 @@
1
- import { round2, roundPathData, arcPath, circlePath, diagnose, resolveSeed, DEFAULTS } from './chunk-I6BAZ7MJ.js';
1
+ import { DEFAULTS } from './chunk-KFBSQH5G.js';
2
+ export { uiSortRows } from './chunk-NI7B6UEO.js';
3
+ import { arcPath, circlePath, resolveSeed } from './chunk-46QELMCG.js';
4
+ import { paletteStyle, resolvePalette } from './chunk-ANJSW5JX.js';
5
+ import { round2, roundPathData } from './chunk-5MP3BLZK.js';
6
+ import { uiRequireName } from './chunk-G7N4X5XD.js';
7
+ export { uiRequireName } from './chunk-G7N4X5XD.js';
8
+ import { safeHref, el, root, frame, tone, glyph, slot, baseOf, text, headingTag, nativeInput, sr, UI_GLYPHS, cssLength, iconClose } from './chunk-6YFO7OP3.js';
9
+ export { UI_GLYPHS } from './chunk-6YFO7OP3.js';
10
+ import { diagnose } from './chunk-WYFRHI4S.js';
2
11
 
3
12
  // src/ui/catalog.ts
4
13
  var BATCH = {
@@ -14,14 +23,17 @@ var BATCH = {
14
23
  Slider: "B2",
15
24
  Rate: "B2"
16
25
  };
17
- var row = (name, group, states) => Object.freeze({
26
+ var row = (name, group, states, family2 = "ui", batch = BATCH[name] ?? "B3") => Object.freeze({
18
27
  name,
19
- batch: BATCH[name] ?? "B3",
28
+ batch,
20
29
  slug: name.replace(/[A-Z]/g, (m, i) => (i ? "-" : "") + m.toLowerCase()),
21
30
  component: `Sp${name}`,
22
31
  group,
32
+ family: family2,
23
33
  states: Object.freeze([...states])
24
34
  });
35
+ var composed = (name, group, batch, states) => row(name, group, states, "composed", batch);
36
+ var overlay = (name, states) => row(name, "overlays", states, "overlay", "B");
25
37
  var UI_COMPONENTS = /* @__PURE__ */ Object.freeze([
26
38
  row("Button", "actions", ["default", "primary", "danger", "disabled"]),
27
39
  row("Input", "data-entry", ["empty", "filled", "invalid", "disabled"]),
@@ -39,13 +51,34 @@ var UI_COMPONENTS = /* @__PURE__ */ Object.freeze([
39
51
  row("Divider", "data-display", ["plain", "text"]),
40
52
  row("Progress", "feedback", ["line", "circle", "indeterminate"]),
41
53
  row("Alert", "feedback", ["info", "success", "warning", "error"]),
42
- row("Skeleton", "feedback", ["paragraph", "avatar"])
54
+ row("Skeleton", "feedback", ["paragraph", "avatar"]),
55
+ composed("Surface", "building-blocks", "A1", ["frame-card", "tone-2"]),
56
+ composed("Glyph", "building-blocks", "A1", ["all"]),
57
+ composed("Table", "data-display", "A1", ["plain", "sorted-striped", "empty"]),
58
+ composed("Pagination", "navigation", "A2", ["middle", "total"]),
59
+ composed("Breadcrumb", "navigation", "A1", ["three"]),
60
+ composed("Avatar", "data-display", "A1", ["initials", "square", "group"]),
61
+ composed("Statistic", "data-display", "A1", ["plain", "trend-down"]),
62
+ composed("Descriptions", "data-display", "A1", ["bordered", "plain"]),
63
+ composed("TimelineList", "data-display", "A1", ["mixed"]),
64
+ composed("Collapse", "disclosure", "A2", ["one-open", "accordion-disabled"]),
65
+ composed("Empty", "data-display", "A1", ["plain"]),
66
+ composed("Result", "data-display", "A1", ["success", "error"]),
67
+ composed("Textarea", "data-entry", "A2", ["count", "invalid"]),
68
+ composed("InputNumber", "data-entry", "A2", ["value", "at-min"]),
69
+ overlay("Modal", ["open"]),
70
+ overlay("Drawer", ["end-open"]),
71
+ overlay("Popover", ["open"]),
72
+ overlay("Tooltip", ["open"]),
73
+ overlay("Select", ["placeholder", "open-selected"]),
74
+ overlay("DropdownMenu", ["open"]),
75
+ overlay("Toast", ["info", "error-action"])
43
76
  ]);
44
77
 
45
78
  // src/ui/value.ts
46
79
  var DEFAULT_RANGE = { min: 0, max: 100, step: 1 };
47
- function warn(component, property, message) {
48
- if (process.env.NODE_ENV !== "production") diagnose("SP017", component, { property, message });
80
+ function warn(component, property, message2) {
81
+ if (process.env.NODE_ENV !== "production") diagnose("SP017", component, { property, message: message2 });
49
82
  }
50
83
  var tidy = (x) => Number.parseFloat(x.toPrecision(12));
51
84
  function uiRange(range, component = "UiValue") {
@@ -56,14 +89,14 @@ function uiRange(range, component = "UiValue") {
56
89
  return DEFAULT_RANGE;
57
90
  }
58
91
  function uiValue(value, range, component = "UiValue") {
59
- const valid = uiRange(range, component);
60
- const { min, max, step } = valid;
92
+ const valid2 = uiRange(range, component);
93
+ const { min, max, step } = valid2;
61
94
  let next = Number.isFinite(value) ? Math.min(Math.max(value, min), max) : min;
62
95
  if (step !== void 0) {
63
96
  const steps = Math.min(Math.round((next - min) / step), Math.floor(tidy((max - min) / step)));
64
97
  next = tidy(min + steps * step);
65
98
  }
66
- const corrected = valid !== range || next !== value;
99
+ const corrected = valid2 !== range || next !== value;
67
100
  if (next !== value) warn(component, "value", `${value} is drawn as ${next}.`);
68
101
  return { value: next, fraction: round2((next - min) / (max - min)), corrected };
69
102
  }
@@ -111,13 +144,13 @@ function uiSelectedKey(items, value, fallback) {
111
144
  function uiSteps(items, current, component = "SpSteps") {
112
145
  const steps = uiItems(items, component);
113
146
  if (steps.length === 0) return [];
114
- const last = steps.length - 1;
115
- const at = Number.isFinite(current) ? Math.min(Math.max(Math.round(current), 0), last) : 0;
147
+ const last2 = steps.length - 1;
148
+ const at = Number.isFinite(current) ? Math.min(Math.max(Math.round(current), 0), last2) : 0;
116
149
  if (at !== current && process.env.NODE_ENV !== "production") {
117
150
  diagnose("SP017", component, { property: "current", message: `${current} is drawn as ${at}.` });
118
151
  }
119
152
  const statuses = steps.map((item, i) => item.status ?? (i < at ? "finish" : i === at ? "process" : "wait"));
120
- return steps.map((item, i) => ({ key: item.key, status: statuses[i], connector: i < last ? statuses[i + 1] : null, current: i === at }));
153
+ return steps.map((item, i) => ({ key: item.key, status: statuses[i], connector: i < last2 ? statuses[i + 1] : null, current: i === at }));
121
154
  }
122
155
 
123
156
  // src/ui/keyboard.ts
@@ -126,12 +159,12 @@ function direction(key, orientation, dir) {
126
159
  const forward = key === "ArrowRight" ? 1 : key === "ArrowLeft" ? -1 : 0;
127
160
  return dir === "rtl" ? -forward : forward;
128
161
  }
129
- function uiRovingKey(state, key, orientation, dir) {
130
- const { index, count, disabled } = state;
131
- const enabled = (i2) => disabled[i2] !== true;
162
+ function uiRovingKey(state2, key, orientation, dir) {
163
+ const { index, count, disabled } = state2;
164
+ const enabled2 = (i2) => disabled[i2] !== true;
132
165
  if (key === "Home" || key === "End") {
133
166
  const step2 = key === "Home" ? 1 : -1;
134
- for (let n = 0, i2 = key === "Home" ? 0 : count - 1; n < count; n++, i2 += step2) if (enabled(i2)) return i2;
167
+ for (let n = 0, i2 = key === "Home" ? 0 : count - 1; n < count; n++, i2 += step2) if (enabled2(i2)) return i2;
135
168
  return index;
136
169
  }
137
170
  const step = direction(key, orientation, dir);
@@ -139,7 +172,7 @@ function uiRovingKey(state, key, orientation, dir) {
139
172
  let i = index < 0 || index >= count ? step > 0 ? -1 : count : index;
140
173
  for (let n = 0; n < count; n++) {
141
174
  i = ((i + step) % count + count) % count;
142
- if (enabled(i)) return i;
175
+ if (enabled2(i)) return i;
143
176
  }
144
177
  return index;
145
178
  }
@@ -197,13 +230,6 @@ function uiFrameOutline(kind) {
197
230
  };
198
231
  }
199
232
 
200
- // src/ui/names.ts
201
- function uiRequireName(component, text2, label) {
202
- if (process.env.NODE_ENV === "production") return;
203
- if (text2?.trim() || label?.trim()) return;
204
- diagnose("SP018", component, { property: "label" });
205
- }
206
-
207
233
  // src/ui/tokens.ts
208
234
  var UI_TOKEN_DEFAULTS = /* @__PURE__ */ Object.freeze({
209
235
  frame: "css",
@@ -211,11 +237,14 @@ var UI_TOKEN_DEFAULTS = /* @__PURE__ */ Object.freeze({
211
237
  controlHeight: Object.freeze({ sm: 24, md: 32, lg: 40 }),
212
238
  radius: 2,
213
239
  focusWidth: 2,
214
- tone: Object.freeze({ selected: 3, primary: 2, danger: 4, disabled: 1, alertError: 1 })
240
+ tone: Object.freeze({ selected: 3, primary: 2, danger: 4, disabled: 1, alertError: 1 }),
241
+ backdrop: 2,
242
+ rowTone: 1
215
243
  });
216
244
  var MIN_HEIGHT = 24;
217
245
  var MIN_FOCUS = 2;
218
246
  var within = (value, lo, hi, fallback) => Number.isFinite(value) ? Math.min(Math.max(value, lo), hi) : fallback;
247
+ var level = (value, fallback) => value === void 0 ? fallback : Math.round(within(value, 1, 4, fallback));
219
248
  function resolveUiTokens(ground) {
220
249
  const ui = ground.ui ?? UI_TOKEN_DEFAULTS;
221
250
  const inked = ui.frame === "inked" && ground.tonalMechanism === "hatch";
@@ -230,7 +259,9 @@ function resolveUiTokens(ground) {
230
259
  },
231
260
  radius: within(ui.radius, 0, Infinity, 2),
232
261
  focusWidth: within(ui.focusWidth, MIN_FOCUS, Infinity, 2),
233
- tone: ui.tone
262
+ tone: ui.tone,
263
+ backdrop: level(ui.backdrop, 2),
264
+ rowTone: level(ui.rowTone, 1)
234
265
  };
235
266
  }
236
267
 
@@ -246,10 +277,10 @@ function family(gap, angle) {
246
277
  const height = Math.abs(cos) < EPSILON ? lines * gap : lines * gap / Math.abs(cos);
247
278
  const offset = (x, y) => -sin * x + cos * y;
248
279
  const corners = [offset(0, 0), offset(width, 0), offset(0, height), offset(width, height)];
249
- const first = Math.ceil(Math.min(...corners) / gap - 0.5);
250
- const last = Math.floor(Math.max(...corners) / gap - 0.5);
280
+ const first2 = Math.ceil(Math.min(...corners) / gap - 0.5);
281
+ const last2 = Math.floor(Math.max(...corners) / gap - 0.5);
251
282
  const strokes = [];
252
- for (let k = first; k <= last; k++) {
283
+ for (let k = first2; k <= last2; k++) {
253
284
  const c = (k + 0.5) * gap;
254
285
  const at = (t) => ({ x: -sin * c + cos * t, y: cos * c + sin * t, t });
255
286
  const hits = [
@@ -259,11 +290,11 @@ function family(gap, angle) {
259
290
  const from = hits[0];
260
291
  const to = hits.at(-1);
261
292
  if (!from || !to || to.t - from.t < EPSILON * 1e3) continue;
262
- const clamp = (v, hi) => round2(Math.min(Math.max(v, 0), hi));
293
+ const clamp2 = (v, hi) => round2(Math.min(Math.max(v, 0), hi));
263
294
  const w = round2(width);
264
295
  const h = round2(height);
265
296
  strokes.push({
266
- d: `M${clamp(from.x, w)},${clamp(from.y, h)}L${clamp(to.x, w)},${clamp(to.y, h)}`,
297
+ d: `M${clamp2(from.x, w)},${clamp2(from.y, h)}L${clamp2(to.x, w)},${clamp2(to.y, h)}`,
267
298
  role: "ornament",
268
299
  part: "ink"
269
300
  });
@@ -277,75 +308,264 @@ function uiToneTile(spec) {
277
308
  return layers;
278
309
  }
279
310
 
280
- // src/ui/view-kit.ts
281
- function clean(attrs) {
282
- const out = {};
283
- for (const [name, value] of Object.entries(attrs)) if (value !== void 0 && value !== false) out[name] = value;
284
- return out;
285
- }
286
- var el = (tag, attrs, children = [], bind) => ({
287
- tag,
288
- attrs: clean(attrs),
289
- children,
290
- ...bind ? { bind } : {}
291
- });
292
- var text = (value) => ({ text: value });
293
- var slot = (name) => ({ slot: name });
294
- function root(slug, props, r, attrs = {}) {
295
- return {
296
- class: ["sp-ui", `sp-${slug}`, `sp-ground-${r.ground}`, props.className].filter(Boolean).join(" "),
297
- "data-ground": r.ground,
298
- "data-substrate": r.substrate,
299
- "data-mode": r.mode,
300
- "data-size": r.size,
301
- "data-frame": String(r.frame),
302
- ...attrs
311
+ // src/ui/pages.ts
312
+ function warn2(component, property, message2) {
313
+ if (process.env.NODE_ENV !== "production") diagnose("SP017", component, { property, message: message2 });
314
+ }
315
+ function uiPageWindow(count, current, siblings) {
316
+ const first2 = Math.max(2, current - siblings);
317
+ const last2 = Math.min(count - 1, current + siblings);
318
+ const pages = [1];
319
+ if (first2 > 2) pages.push("start-gap");
320
+ for (let page2 = first2; page2 <= last2; page2++) pages.push(page2);
321
+ if (last2 < count - 1) pages.push("end-gap");
322
+ if (count > 1) pages.push(count);
323
+ return pages;
324
+ }
325
+ function uiPagination(total, pageSize, current, component = "SpPagination") {
326
+ let corrected = false;
327
+ if (!(Number.isInteger(total) && total >= 0)) {
328
+ warn2(component, "total", `${total} is drawn as 0.`);
329
+ [total, corrected] = [0, true];
330
+ }
331
+ if (!(Number.isInteger(pageSize) && pageSize >= 1)) {
332
+ warn2(component, "pageSize", `${pageSize} is drawn as 10.`);
333
+ [pageSize, corrected] = [10, true];
334
+ }
335
+ const count = Math.max(1, Math.ceil(total / pageSize));
336
+ const page2 = Number.isFinite(current) ? Math.min(Math.max(Math.round(current), 1), count) : 1;
337
+ if (page2 !== current) {
338
+ warn2(component, "current", `${current} is drawn as ${page2}.`);
339
+ corrected = true;
340
+ }
341
+ return { count, current: page2, start: total ? (page2 - 1) * pageSize + 1 : 0, end: Math.min(total, page2 * pageSize), corrected };
342
+ }
343
+
344
+ // src/ui/number.ts
345
+ function warn3(component, property, message2) {
346
+ if (process.env.NODE_ENV !== "production") diagnose("SP017", component, { property, message: message2 });
347
+ }
348
+ var tidy2 = (x) => Number.parseFloat(x.toPrecision(12));
349
+ function valid(range, component) {
350
+ const drop = (property, value, as) => {
351
+ warn3(component, property, `${value} is ${as}.`);
352
+ return void 0;
303
353
  };
354
+ let min = range.min !== void 0 && !Number.isFinite(range.min) ? drop("min", range.min, "ignored") : range.min;
355
+ let max = range.max !== void 0 && !Number.isFinite(range.max) ? drop("max", range.max, "ignored") : range.max;
356
+ if (min !== void 0 && max !== void 0 && min > max) min = max = drop("min", `The bounds ${min}..${max}`, "ignored");
357
+ const { step: s, precision: p } = range;
358
+ const step = s !== void 0 && !(Number.isFinite(s) && s > 0) ? drop("step", s, "drawn as 1") ?? 1 : s;
359
+ const precision = p !== void 0 && !(Number.isInteger(p) && p >= 0 && p <= 15) ? drop("precision", p, "ignored") : p;
360
+ return { min, max, step, precision };
304
361
  }
305
- var frame = (kind) => el("span", { part: "sp-frame", "data-kind": kind, "aria-hidden": "true" });
306
- var tone = (value, kind) => el("span", { part: "sp-tone", "data-tone": value, "data-kind": kind, "aria-hidden": "true" });
307
- var UI_GLYPHS = Object.freeze({
308
- tick: "M3.5,8.5L6.5,11.5L12.5,4.5",
309
- dash: "M4,8H12",
310
- cross: "M4.5,4.5L11.5,11.5M11.5,4.5L4.5,11.5",
311
- warning: "M8,2.5L14.5,13.5H1.5Z M8,6.5V9.5 M8,11.5V11.5",
312
- /** A radio's selection: filled by ui.css. */
313
- dot: "M11,8A3,3 0 1,1 5,8A3,3 0 1,1 11,8Z",
314
- /** A Rate mark (C-2). */
315
- lozenge: "M8,1.5L14.5,8L8,14.5L1.5,8Z",
316
- info: "M14.5,8A6.5,6.5 0 1,1 1.5,8A6.5,6.5 0 1,1 14.5,8Z M8,7.5V11.5 M8,4.75V4.75"
317
- });
318
- var glyph = (name) => el("svg", { part: "sp-mark", "data-glyph": name, viewBox: "0 0 16 16", "aria-hidden": "true" }, [el("path", { d: UI_GLYPHS[name] })]);
319
- var sr = (value) => el("span", { class: "sp-ui-sr" }, [text(value)]);
320
- var baseOf = (props) => props.id ?? props.name;
321
- function nativeInput(type, attrs) {
322
- return el("input", { type, class: "sp-ui-native", ...attrs }, [], "native");
362
+ function clamp(value, { min, max, precision }) {
363
+ let next = value;
364
+ if (min !== void 0) next = Math.max(min, next);
365
+ if (max !== void 0) next = Math.min(max, next);
366
+ if (precision !== void 0) next = Math.round(tidy2(next * 10 ** precision)) / 10 ** precision;
367
+ return tidy2(next);
368
+ }
369
+ function uiNumberValue(value, range, component = "SpInputNumber") {
370
+ const r = valid(range, component);
371
+ if (value === null) return { value: null, corrected: false, atMin: false, atMax: false, range: r };
372
+ const next = Number.isFinite(value) ? clamp(value, r) : clamp(r.min ?? 0, r);
373
+ if (next !== value) warn3(component, "value", `${value} is drawn as ${next}.`);
374
+ return { value: next, corrected: next !== value, atMin: r.min !== void 0 && next <= r.min, atMax: r.max !== void 0 && next >= r.max, range: r };
375
+ }
376
+ function uiNumberStep(value, direction2, range, component = "SpInputNumber") {
377
+ const r = valid(range, component);
378
+ const base = value !== null && Number.isFinite(value) ? value : r.min ?? 0;
379
+ return clamp(base + direction2 * (r.step ?? 1), r);
380
+ }
381
+
382
+ // src/ui/initials.ts
383
+ function uiInitials(name) {
384
+ const words = (name ?? "").replace(/-/g, " ").split(/\s+/).filter(Boolean);
385
+ return words.slice(0, 2).map((word) => [...word][0].toUpperCase()).join("") || "?";
386
+ }
387
+
388
+ // src/ui/listbox.ts
389
+ function seek(state2, from, step) {
390
+ for (let i = from; i >= 0 && i < state2.count; i += step) if (state2.disabled[i] !== true) return i;
391
+ return -1;
392
+ }
393
+ var first = (s) => seek(s, 0, 1);
394
+ var last = (s) => seek(s, s.count - 1, -1);
395
+ var enabled = (s, i) => i >= 0 && i < s.count && s.disabled[i] !== true;
396
+ function arrow(state2, step, wrap) {
397
+ const { active, count } = state2;
398
+ if (active < 0 || active >= count) return step > 0 ? first(state2) : last(state2);
399
+ const next = seek(state2, active + step, step);
400
+ if (next >= 0) return next;
401
+ return wrap ? step > 0 ? first(state2) : last(state2) : active;
402
+ }
403
+ function page(state2, step) {
404
+ const target = Math.min(Math.max((state2.active < 0 ? 0 : state2.active) + 10 * step, 0), state2.count - 1);
405
+ const found = seek(state2, target, step);
406
+ return found >= 0 ? found : seek(state2, target, step > 0 ? -1 : 1);
407
+ }
408
+ function uiListKey(state2, key, kind) {
409
+ const { active } = state2;
410
+ const keep = (i) => i < 0 ? active : i;
411
+ if (!state2.open) {
412
+ if (key === "Home") return { active: keep(first(state2)), intent: "open" };
413
+ if (key === "End") return { active: keep(last(state2)), intent: "open" };
414
+ if (kind === "menu" && key === "ArrowUp") return { active: keep(last(state2)), intent: "open" };
415
+ if (["ArrowDown", "ArrowUp", "Enter", " "].includes(key)) {
416
+ const start = kind === "listbox" && enabled(state2, active) ? active : first(state2);
417
+ return { active: keep(start), intent: "open" };
418
+ }
419
+ return { active };
420
+ }
421
+ switch (key) {
422
+ case "ArrowDown":
423
+ return { active: keep(arrow(state2, 1, kind === "menu")) };
424
+ case "ArrowUp":
425
+ return { active: keep(arrow(state2, -1, kind === "menu")) };
426
+ case "Home":
427
+ return { active: keep(first(state2)) };
428
+ case "End":
429
+ return { active: keep(last(state2)) };
430
+ case "PageDown":
431
+ case "PageUp":
432
+ return kind === "listbox" ? { active: keep(page(state2, key === "PageDown" ? 1 : -1)) } : { active };
433
+ case "Enter":
434
+ case " ":
435
+ return { active, intent: enabled(state2, active) ? "select" : "close" };
436
+ case "Tab":
437
+ return { active, intent: kind === "listbox" && enabled(state2, active) ? "select" : "close" };
438
+ case "Escape":
439
+ return { active, intent: "close" };
440
+ default:
441
+ return { active };
442
+ }
443
+ }
444
+ function uiTypeahead(labels, disabled, buffer, from) {
445
+ const typed = buffer.toLowerCase();
446
+ if (!typed) return -1;
447
+ const single = [...typed].every((c) => c === typed[0]);
448
+ const prefix = single ? typed[0] : typed;
449
+ const count = labels.length;
450
+ const start = single ? from + 1 : Math.max(from, 0);
451
+ for (let n = 0; n < count; n++) {
452
+ const i = ((start + n) % count + count) % count;
453
+ if (disabled[i] !== true && labels[i].trim().toLowerCase().startsWith(prefix)) return i;
454
+ }
455
+ return -1;
456
+ }
457
+
458
+ // src/ui/place.ts
459
+ var OPPOSITE = { top: "bottom", bottom: "top", start: "end", end: "start" };
460
+ var shift = (at, extent, size, padding) => Math.max(padding, Math.min(at, size - padding - extent));
461
+ function uiPlace(input) {
462
+ const { anchor: a, floating: f, viewport: v, align, offset, padding, dir } = input;
463
+ const vertical = (side2) => side2 === "top" || side2 === "bottom";
464
+ const isLeft = (side2) => side2 === "start" === (dir === "ltr");
465
+ const main = (side2) => {
466
+ if (side2 === "bottom") return a.y + a.height + offset;
467
+ if (side2 === "top") return a.y - offset - f.height;
468
+ return isLeft(side2) ? a.x - offset - f.width : a.x + a.width + offset;
469
+ };
470
+ const fits = (side2) => {
471
+ const at = main(side2);
472
+ const [extent, size] = vertical(side2) ? [f.height, v.height] : [f.width, v.width];
473
+ return at >= padding && at + extent <= size - padding;
474
+ };
475
+ const side = !fits(input.side) && fits(OPPOSITE[input.side]) ? OPPOSITE[input.side] : input.side;
476
+ if (vertical(side)) {
477
+ const fromLeft = align === "center" ? null : align === "start" === (dir === "ltr");
478
+ const x = fromLeft === null ? a.x + (a.width - f.width) / 2 : fromLeft ? a.x : a.x + a.width - f.width;
479
+ return { x: round2(shift(x, f.width, v.width, padding)), y: round2(main(side)), side, align };
480
+ }
481
+ const y = align === "center" ? a.y + (a.height - f.height) / 2 : align === "start" ? a.y : a.y + a.height - f.height;
482
+ return { x: round2(main(side)), y: round2(shift(y, f.height, v.height, padding)), side, align };
483
+ }
484
+
485
+ // src/ui/anchor.ts
486
+ function uiAnchor(anchor, floating, options) {
487
+ const view = floating.ownerDocument?.defaultView;
488
+ if (!view) return () => {
489
+ };
490
+ const place = () => {
491
+ const { width, height } = floating.getBoundingClientRect();
492
+ const at = uiPlace({
493
+ anchor: anchor.getBoundingClientRect(),
494
+ floating: { width, height },
495
+ viewport: { width: view.innerWidth, height: view.innerHeight },
496
+ side: options.side,
497
+ align: options.align,
498
+ offset: options.offset ?? 6,
499
+ padding: options.padding ?? 8,
500
+ dir: anchor.closest("[dir]")?.getAttribute("dir") === "rtl" ? "rtl" : "ltr"
501
+ });
502
+ floating.style.setProperty("--sp-ui-x", `${at.x}px`);
503
+ floating.style.setProperty("--sp-ui-y", `${at.y}px`);
504
+ floating.setAttribute("data-side", at.side);
505
+ };
506
+ place();
507
+ view.addEventListener("scroll", place, { capture: true, passive: true });
508
+ view.addEventListener("resize", place);
509
+ const sized = view.ResizeObserver ? new view.ResizeObserver(place) : void 0;
510
+ sized?.observe(anchor);
511
+ sized?.observe(floating);
512
+ return () => {
513
+ view.removeEventListener("scroll", place, { capture: true });
514
+ view.removeEventListener("resize", place);
515
+ sized?.disconnect();
516
+ };
517
+ }
518
+
519
+ // src/ui/overlay.ts
520
+ function showing(element) {
521
+ try {
522
+ return element.matches(":popover-open");
523
+ } catch {
524
+ return false;
525
+ }
526
+ }
527
+ function uiOverlaySync(element, kind, open) {
528
+ if (kind === "popover") {
529
+ if (typeof element.showPopover !== "function") return;
530
+ if (open !== showing(element)) {
531
+ if (open) element.showPopover();
532
+ else element.hidePopover?.();
533
+ }
534
+ return;
535
+ }
536
+ if (open && !element.open) {
537
+ if (kind === "modal") element.showModal?.();
538
+ else element.show?.();
539
+ } else if (!open && element.open) element.close?.();
323
540
  }
324
541
 
325
542
  // src/ui/view.ts
326
543
  var FRAME_SLOTS = 6;
327
544
  function resolveUi(props, env) {
328
- const { provider = {}, cell = {} } = env;
329
- const ground = props.ground ?? cell.ground ?? provider.ground ?? DEFAULTS.ground;
545
+ const { provider = {}, cell: cell2 = {} } = env;
546
+ const ground = props.ground ?? cell2.ground ?? provider.ground ?? DEFAULTS.ground;
330
547
  return {
331
548
  ground: typeof ground === "string" ? ground : ground.name,
332
- substrate: props.substrate ?? cell.substrate ?? provider.substrate ?? DEFAULTS.substrate,
333
- mode: env.forcedPrecision ? "precision" : props.mode ?? cell.mode ?? provider.mode ?? DEFAULTS.mode,
549
+ substrate: props.substrate ?? cell2.substrate ?? provider.substrate ?? DEFAULTS.substrate,
550
+ mode: env.forcedPrecision ? "precision" : props.mode ?? cell2.mode ?? provider.mode ?? DEFAULTS.mode,
334
551
  size: props.size ?? "md",
335
- frame: uiFrameVariant(props.seed, props.id, FRAME_SLOTS)
552
+ frame: uiFrameVariant(props.seed, props.id, FRAME_SLOTS),
553
+ // Colours are not mode: a forced precision leaves them as resolved (REQ-468).
554
+ palette: paletteStyle(resolvePalette(provider.palette, cell2.palette, props.palette))
336
555
  };
337
556
  }
338
557
  function uiButtonView(props, r, content) {
339
558
  uiRequireName("SpButton", content.text, props.label);
340
559
  const variant = props.variant ?? "default";
341
- const link = props.href !== void 0;
560
+ const href = safeHref(props.href, "SpButton");
561
+ const link = href !== void 0;
342
562
  const toned = props.disabled ? "disabled" : variant === "default" ? void 0 : variant;
343
563
  return el(
344
564
  link ? "a" : "button",
345
565
  root("button", props, r, {
346
566
  id: props.id,
347
567
  type: link ? void 0 : props.type ?? "button",
348
- href: link && !props.disabled ? props.href : void 0,
568
+ href: link && !props.disabled ? href : void 0,
349
569
  disabled: !link && props.disabled === true,
350
570
  "aria-disabled": link && props.disabled ? "true" : void 0,
351
571
  "aria-label": props.label,
@@ -360,14 +580,14 @@ function uiButtonView(props, r, content) {
360
580
  ]
361
581
  );
362
582
  }
363
- function uiInputView(props, r, state) {
583
+ function uiInputView(props, r, state2) {
364
584
  const base = baseOf(props);
365
- const message = props.message ? props.message : void 0;
366
- const messageId = message && base ? `${base}--message` : void 0;
585
+ const message2 = props.message ? props.message : void 0;
586
+ const messageId = message2 && base ? `${base}--message` : void 0;
367
587
  return el("span", root("input", props, r, { "data-invalid": props.invalid ? "true" : void 0 }), [
368
588
  el("span", { class: "sp-ui-box" }, [
369
589
  frame("control"),
370
- ...state.prefix ? [el("span", { class: "sp-ui-affix", "data-slot": "prefix" }, [slot("prefix")])] : [],
590
+ ...state2.prefix ? [el("span", { class: "sp-ui-affix", "data-slot": "prefix" }, [slot("prefix")])] : [],
371
591
  el(
372
592
  "input",
373
593
  {
@@ -375,7 +595,7 @@ function uiInputView(props, r, state) {
375
595
  id: props.id,
376
596
  type: props.type ?? "text",
377
597
  name: props.name,
378
- value: state.value,
598
+ value: state2.value,
379
599
  placeholder: props.placeholder,
380
600
  disabled: props.disabled === true,
381
601
  readonly: props.readOnly === true,
@@ -386,10 +606,10 @@ function uiInputView(props, r, state) {
386
606
  [],
387
607
  "native"
388
608
  ),
389
- ...state.suffix ? [el("span", { class: "sp-ui-affix", "data-slot": "suffix" }, [slot("suffix")])] : [],
609
+ ...state2.suffix ? [el("span", { class: "sp-ui-affix", "data-slot": "suffix" }, [slot("suffix")])] : [],
390
610
  ...props.invalid ? [glyph("warning")] : []
391
611
  ]),
392
- ...message ? [el("span", { class: "sp-ui-message", id: messageId }, [text(message)])] : []
612
+ ...message2 ? [el("span", { class: "sp-ui-message", id: messageId }, [text(message2)])] : []
393
613
  ]);
394
614
  }
395
615
  function nativeCheckbox(props, checked, role) {
@@ -400,16 +620,16 @@ function nativeCheckbox(props, checked, role) {
400
620
  "native"
401
621
  );
402
622
  }
403
- function uiCheckboxView(props, r, state) {
623
+ function uiCheckboxView(props, r, state2) {
404
624
  return el("label", root("checkbox", props, r, { "data-indeterminate": props.indeterminate ? "true" : void 0 }), [
405
- nativeCheckbox(props, state.checked),
625
+ nativeCheckbox(props, state2.checked),
406
626
  el("span", { class: "sp-ui-box", "aria-hidden": "true" }, [frame("box"), tone("selected", "box"), glyph("tick"), glyph("dash")]),
407
627
  el("span", { class: "sp-ui-label" }, [text(props.label)])
408
628
  ]);
409
629
  }
410
- function uiSwitchView(props, r, state) {
630
+ function uiSwitchView(props, r, state2) {
411
631
  return el("label", root("switch", props, r), [
412
- nativeCheckbox(props, state.checked, "switch"),
632
+ nativeCheckbox(props, state2.checked, "switch"),
413
633
  el("span", { class: "sp-ui-track", "aria-hidden": "true" }, [frame("pill"), tone("selected", "pill"), el("span", { part: "sp-knob" })]),
414
634
  el("span", { class: "sp-ui-label" }, [text(props.label)])
415
635
  ]);
@@ -425,7 +645,7 @@ function uiCardView(props, r, slots) {
425
645
  frame("card"),
426
646
  ...header ? [
427
647
  el("div", { class: "sp-ui-card-header" }, [
428
- ...titled ? [el(`h${props.headingLevel ?? 3}`, { class: "sp-ui-card-title", id: titleId }, [text(props.title)])] : [],
648
+ ...titled ? [el(headingTag(props.headingLevel, "SpCard"), { class: "sp-ui-card-title", id: titleId }, [text(props.title)])] : [],
429
649
  ...slots.extra ? [el("div", { class: "sp-ui-card-extra" }, [slot("extra")])] : []
430
650
  ]),
431
651
  el("span", { part: "sp-rule", "aria-hidden": "true" })
@@ -453,11 +673,11 @@ function uiDividerView(props, r) {
453
673
 
454
674
  // src/ui/view-b2.ts
455
675
  var heighten = () => el("span", { part: "sp-heighten", "aria-hidden": "true" });
456
- var legend = (label, forSight) => el("legend", { class: forSight ? "sp-ui-legend" : "sp-ui-legend sp-ui-sr" }, [text(label)]);
676
+ var legend = (label2, forSight) => el("legend", { class: forSight ? "sp-ui-legend" : "sp-ui-legend sp-ui-sr" }, [text(label2)]);
457
677
  var groupName = (props) => props.name ?? (props.id ? `${props.id}--value` : void 0);
458
- function uiRadioGroupView(props, r, state) {
678
+ function uiRadioGroupView(props, r, state2) {
459
679
  const items = uiItems(props.items, "SpRadioGroup");
460
- const value = uiSelectedKey(items, state.value, "none");
680
+ const value = uiSelectedKey(items, state2.value, "none");
461
681
  return el("fieldset", root("radio-group", props, r, { id: props.id, "data-orientation": props.orientation ?? "vertical" }), [
462
682
  legend(props.label, true),
463
683
  ...items.map(
@@ -475,9 +695,9 @@ function uiRadioGroupView(props, r, state) {
475
695
  )
476
696
  ]);
477
697
  }
478
- function uiSegmentedView(props, r, state) {
698
+ function uiSegmentedView(props, r, state2) {
479
699
  const items = uiItems(props.items, "SpSegmented");
480
- const value = uiSelectedKey(items, state.value, "first");
700
+ const value = uiSelectedKey(items, state2.value, "first");
481
701
  const name = groupName(props);
482
702
  return el("fieldset", root("segmented", props, r, { id: props.id, "data-block": props.block ? "true" : void 0 }), [
483
703
  legend(props.label, false),
@@ -496,9 +716,9 @@ function uiSegmentedView(props, r, state) {
496
716
  ])
497
717
  ]);
498
718
  }
499
- function uiTabsView(props, r, state) {
719
+ function uiTabsView(props, r, state2) {
500
720
  const items = uiItems(props.items, "SpTabs");
501
- const value = uiSelectedKey(items, state.value, "first");
721
+ const value = uiSelectedKey(items, state2.value, "first");
502
722
  const vertical = props.orientation === "vertical";
503
723
  const id = props.id;
504
724
  return el("div", root("tabs", props, r, { id, "data-orientation": vertical ? "vertical" : "horizontal" }), [
@@ -542,11 +762,11 @@ function uiTabPanelView(props, tabs) {
542
762
  [slot("content")]
543
763
  );
544
764
  }
545
- function uiSliderView(props, r, state) {
546
- const valid = uiRange({ min: props.min ?? 0, max: props.max ?? 100, step: props.step ?? 1 }, "SpSlider");
547
- const { value, fraction } = uiValue(state.value, valid, "SpSlider");
548
- const span = valid.max - valid.min;
549
- const marks = (props.marks ?? []).filter((m) => Number.isFinite(m) && m >= valid.min && m <= valid.max);
765
+ function uiSliderView(props, r, state2) {
766
+ const valid2 = uiRange({ min: props.min ?? 0, max: props.max ?? 100, step: props.step ?? 1 }, "SpSlider");
767
+ const { value, fraction } = uiValue(state2.value, valid2, "SpSlider");
768
+ const span = valid2.max - valid2.min;
769
+ const marks = (props.marks ?? []).filter((m) => Number.isFinite(m) && m >= valid2.min && m <= valid2.max);
550
770
  return el("label", root("slider", props, r, { style: `--sp-ui-fraction: ${fraction}`, "data-disabled": props.disabled ? "true" : void 0 }), [
551
771
  el("span", { class: "sp-ui-label" }, [text(props.label)]),
552
772
  el("span", { class: "sp-ui-rail" }, [
@@ -560,9 +780,9 @@ function uiSliderView(props, r, state) {
560
780
  class: "sp-ui-range",
561
781
  id: props.id,
562
782
  name: props.name,
563
- min: String(valid.min),
564
- max: String(valid.max),
565
- step: String(valid.step ?? 1),
783
+ min: String(valid2.min),
784
+ max: String(valid2.max),
785
+ step: String(valid2.step ?? 1),
566
786
  value: String(value),
567
787
  disabled: props.disabled === true
568
788
  },
@@ -574,14 +794,14 @@ function uiSliderView(props, r, state) {
574
794
  el(
575
795
  "span",
576
796
  { class: "sp-ui-scale", "aria-hidden": "true" },
577
- marks.map((m) => el("span", { class: "sp-ui-scale-mark", style: `--sp-ui-at: ${round2((m - valid.min) / span)}` }, [text(String(m))]))
797
+ marks.map((m) => el("span", { class: "sp-ui-scale-mark", style: `--sp-ui-at: ${round2((m - valid2.min) / span)}` }, [text(String(m))]))
578
798
  )
579
799
  ] : []
580
800
  ]);
581
801
  }
582
- function uiRateView(props, r, state) {
802
+ function uiRateView(props, r, state2) {
583
803
  const count = uiRateCount(props.count);
584
- const { value } = uiValue(state.value, { min: 0, max: count, step: 1 }, "SpRate");
804
+ const { value } = uiValue(state2.value, { min: 0, max: count, step: 1 }, "SpRate");
585
805
  const name = groupName(props);
586
806
  const readOnly = props.readOnly === true;
587
807
  return el(
@@ -607,7 +827,7 @@ function uiRateView(props, r, state) {
607
827
  }
608
828
 
609
829
  // src/ui/view-b3.ts
610
- var closeButton = (label) => el("button", { type: "button", class: "sp-ui-close", "aria-label": label }, [glyph("cross")], "close");
830
+ var closeButton = (label2) => el("button", { type: "button", class: "sp-ui-close", "aria-label": label2 }, [glyph("cross")], "close");
611
831
  function uiStepsView(props, r) {
612
832
  const steps = uiSteps(props.items, props.current);
613
833
  const titles = new Map(props.items.map((item) => [item.key, item]));
@@ -630,13 +850,13 @@ function uiStepsView(props, r) {
630
850
  );
631
851
  }
632
852
  function uiTagView(props, r, content) {
633
- const level = String(props.tone ?? 1);
634
- const label = props.closeLabel ?? (content.text ? `Remove ${content.text}` : "Remove");
635
- return el("span", root("tag", props, r, { id: props.id, "data-tone": level }), [
853
+ const level2 = String(props.tone ?? 1);
854
+ const label2 = props.closeLabel ?? (content.text ? `Remove ${content.text}` : "Remove");
855
+ return el("span", root("tag", props, r, { id: props.id, "data-tone": level2 }), [
636
856
  frame("control"),
637
- tone(level, "control"),
857
+ tone(level2, "control"),
638
858
  el("span", { class: "sp-ui-label sp-ui-plate" }, [slot("content")]),
639
- ...props.closable ? [closeButton(label)] : []
859
+ ...props.closable ? [closeButton(label2)] : []
640
860
  ]);
641
861
  }
642
862
  function uiBadgeView(props, r, content) {
@@ -683,7 +903,7 @@ function uiProgressView(props, r) {
683
903
  );
684
904
  }
685
905
  var ALERT_GLYPHS = { info: "info", success: "tick", warning: "warning", error: "cross" };
686
- function uiAlertView(props, r, state) {
906
+ function uiAlertView(props, r, state2) {
687
907
  const kind = props.kind ?? "info";
688
908
  const error = kind === "error";
689
909
  return el("div", root("alert", props, r, { id: props.id, role: error || kind === "warning" ? "alert" : "status", "data-kind": kind }), [
@@ -694,7 +914,7 @@ function uiAlertView(props, r, state) {
694
914
  ...props.title ? [el("div", { class: "sp-ui-alert-title" }, [text(props.title)])] : [],
695
915
  el("div", { class: "sp-ui-alert-text" }, [slot("content")])
696
916
  ]),
697
- ...state.closable ? [closeButton(props.closeLabel ?? "Close")] : []
917
+ ...state2.closable ? [closeButton(props.closeLabel ?? "Close")] : []
698
918
  ]);
699
919
  }
700
920
  function uiSkeletonView(props, r) {
@@ -710,6 +930,601 @@ function uiSkeletonView(props, r) {
710
930
  ]);
711
931
  }
712
932
 
713
- export { UI_COMPONENTS, UI_FRAME_KINDS, UI_GLYPHS, UI_PROGRESS_VIEWBOX, UI_TOKEN_DEFAULTS, resolveUi, resolveUiTokens, uiAlertView, uiBadgeView, uiButtonView, uiCardView, uiCheckboxView, uiDividerView, uiFrameOutline, uiFrameVariant, uiInputView, uiIsRovingKey, uiItems, uiProgressArc, uiProgressView, uiRadioGroupView, uiRange, uiRateCount, uiRateView, uiRequireName, uiRovingFocus, uiRovingKey, uiSegmentedView, uiSelectedKey, uiSkeletonView, uiSliderView, uiSteps, uiStepsView, uiSwitchView, uiTabPanelView, uiTabsView, uiTagView, uiToneTile, uiValue };
933
+ // src/ui/view-composed.ts
934
+ function warn4(component, property, message2) {
935
+ if (process.env.NODE_ENV !== "production") diagnose("SP017", component, { property, message: message2 });
936
+ }
937
+ var heighten2 = () => el("span", { part: "sp-heighten", "aria-hidden": "true" });
938
+ var heading = (level2, component, attrs, title) => el(headingTag(level2, component), attrs, [text(title)]);
939
+ var maybeText = (value) => value === "" ? [] : [text(value)];
940
+ function keyed(items, component) {
941
+ const seen = /* @__PURE__ */ new Set();
942
+ return items.filter((item) => {
943
+ if (item.key === void 0) return true;
944
+ if (!seen.has(item.key)) return seen.add(item.key), true;
945
+ if (process.env.NODE_ENV !== "production") diagnose("SP019", component, { property: "items", message: `The key "${item.key}" repeats; that item is skipped.` });
946
+ return false;
947
+ });
948
+ }
949
+ var IDENT = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
950
+ var UNSAFE_TAGS = /* @__PURE__ */ new Set(["script", "style", "iframe", "object", "embed", "template", "noscript"]);
951
+ function uiSurfaceView(props, r) {
952
+ let tag = props.as ?? "div";
953
+ if (!IDENT.test(tag) || UNSAFE_TAGS.has(tag)) {
954
+ warn4("SpSurface", "as", `${JSON.stringify(tag)} is drawn as "div".`);
955
+ tag = "div";
956
+ }
957
+ let name = props.name;
958
+ if (name !== void 0 && !IDENT.test(name)) {
959
+ warn4("SpSurface", "name", `${JSON.stringify(name)} adds no class.`);
960
+ name = void 0;
961
+ }
962
+ const attrs = root("surface", props, r, { id: props.id });
963
+ if (name) attrs.class = `${attrs.class} sp-${name}`;
964
+ return el(tag, attrs, [
965
+ ...props.frame ? [frame(props.frame)] : [],
966
+ ...props.tone !== void 0 ? [tone(String(props.tone), props.frame ?? "box")] : [],
967
+ slot("content")
968
+ ]);
969
+ }
970
+ function uiGlyphView(props, r) {
971
+ const known = Object.hasOwn(UI_GLYPHS, props.glyph);
972
+ if (!known) warn4("SpGlyph", "glyph", `${JSON.stringify(props.glyph)} is not a glyph; nothing is drawn.`);
973
+ return el(
974
+ "svg",
975
+ root("glyph", props, r, { id: props.id, part: "sp-mark", "data-glyph": props.glyph, viewBox: "0 0 16 16", "aria-hidden": "true" }),
976
+ known ? [el("path", { d: UI_GLYPHS[props.glyph] })] : []
977
+ );
978
+ }
979
+ var cell = (value) => value === null || value === void 0 ? "" : String(value);
980
+ function uiTableView(props, r) {
981
+ const columns = uiItems(props.columns, "SpTable");
982
+ const order = props.sortOrder ?? "asc";
983
+ const sorted = columns.find((column) => column.key === props.sortKey && column.sortable === true);
984
+ const rows = props.data;
985
+ const headers = columns.map((column) => {
986
+ const width = cssLength(column.width, "SpTable", "columns");
987
+ const title = column.title ?? column.key;
988
+ const active = column === sorted;
989
+ return el(
990
+ "th",
991
+ {
992
+ scope: "col",
993
+ "data-align": column.align,
994
+ "aria-sort": active ? order === "asc" ? "ascending" : "descending" : void 0,
995
+ style: width ? `width:${width}` : void 0
996
+ },
997
+ column.sortable ? [
998
+ el("button", { type: "button", class: "sp-ui-sort", "data-key": column.key }, [
999
+ el("span", { class: "sp-ui-label" }, [text(title)]),
1000
+ glyph(active ? order === "asc" ? "sortUp" : "sortDown" : "sort")
1001
+ ], "sort")
1002
+ ] : [text(title)]
1003
+ );
1004
+ });
1005
+ const body = rows.length === 0 ? [el("tr", {}, [el("td", { class: "sp-ui-table-empty", colspan: String(columns.length) }, [text(props.emptyText ?? "No data")])])] : rows.map(
1006
+ (row2) => el(
1007
+ "tr",
1008
+ { "data-key": props.rowKey !== void 0 ? cell(row2[props.rowKey]) : void 0 },
1009
+ // The text sits on a plate, so a toned row keeps the contrast of the substrate (Data Model §3.8).
1010
+ columns.map((column) => {
1011
+ const value = cell(row2[column.key]);
1012
+ return el("td", { "data-align": column.align }, value === "" ? [] : [el("span", { class: "sp-ui-plate" }, [text(value)])]);
1013
+ })
1014
+ )
1015
+ );
1016
+ return el(
1017
+ "div",
1018
+ root("table", props, r, {
1019
+ id: props.id,
1020
+ "data-striped": props.striped ? "true" : void 0,
1021
+ "data-hoverable": props.hoverable !== false ? "true" : void 0
1022
+ }),
1023
+ [
1024
+ ...props.bordered !== false ? [frame("card")] : [],
1025
+ el("div", { class: "sp-ui-table-scroll" }, [
1026
+ el("table", { class: "sp-ui-table" }, [
1027
+ ...props.caption ? [el("caption", {}, [text(props.caption)])] : [],
1028
+ el("thead", {}, [el("tr", {}, headers)]),
1029
+ el("tbody", {}, body)
1030
+ ])
1031
+ ])
1032
+ ]
1033
+ );
1034
+ }
1035
+ function uiPaginationView(props, r, state2) {
1036
+ const { count, current, start, end } = uiPagination(props.total, props.pageSize ?? 10, state2.current);
1037
+ const pageLabel = props.pageLabel ?? "Page";
1038
+ const off = props.disabled === true;
1039
+ const button = (page2, label2, disabled, content) => el("li", {}, [
1040
+ el("button", { type: "button", class: "sp-ui-page", "data-page": String(page2), "aria-label": label2, disabled: off || disabled }, [frame("control"), ...content], "page")
1041
+ ]);
1042
+ const pages = uiPageWindow(count, current, props.siblings ?? 1).map(
1043
+ (entry) => typeof entry === "string" ? el("li", { class: "sp-ui-page-gap", "aria-hidden": "true" }, [glyph("ellipsis")]) : entry === current ? el("li", {}, [
1044
+ el("span", { class: "sp-ui-page", "aria-current": "page" }, [
1045
+ frame("control"),
1046
+ heighten2(),
1047
+ el("span", { class: "sp-ui-label sp-ui-plate" }, [sr(`${pageLabel} `), text(String(entry))])
1048
+ ])
1049
+ ]) : button(entry, `${pageLabel} ${entry}`, false, [el("span", { class: "sp-ui-label" }, [text(String(entry))])])
1050
+ );
1051
+ const summary = (props.totalTemplate ?? "{start}\u2013{end} of {total}").split("{start}").join(String(start)).split("{end}").join(String(end)).split("{total}").join(String(Math.max(0, props.total || 0)));
1052
+ return el("nav", root("pagination", props, r, { id: props.id, "aria-label": props.label ?? "Pagination", "data-disabled": off ? "true" : void 0 }), [
1053
+ ...props.showTotal ? [el("span", { class: "sp-ui-page-total" }, [text(summary)])] : [],
1054
+ el("ul", { class: "sp-ui-pages" }, [
1055
+ button(current - 1, props.previousLabel ?? "Previous page", current <= 1, [glyph("chevronLeft")]),
1056
+ ...pages,
1057
+ button(current + 1, props.nextLabel ?? "Next page", current >= count, [glyph("chevronRight")])
1058
+ ])
1059
+ ]);
1060
+ }
1061
+ function uiBreadcrumbView(props, r) {
1062
+ const items = keyed(props.items, "SpBreadcrumb");
1063
+ return el("nav", root("breadcrumb", props, r, { id: props.id, "aria-label": props.label ?? "Breadcrumb" }), [
1064
+ el(
1065
+ "ol",
1066
+ { class: "sp-ui-crumbs" },
1067
+ items.map((item, i) => {
1068
+ const last2 = i === items.length - 1;
1069
+ const href = last2 ? void 0 : safeHref(item.href, "SpBreadcrumb", "items");
1070
+ const inner = last2 ? el("span", { "aria-current": "page" }, [text(item.label)]) : href !== void 0 ? el("a", { href }, [text(item.label)]) : el("span", {}, [text(item.label)]);
1071
+ return el("li", { class: "sp-ui-crumb" }, [inner]);
1072
+ })
1073
+ )
1074
+ ]);
1075
+ }
1076
+ function uiAvatarView(props, r) {
1077
+ const name = props.alt ?? props.name ?? props.initials;
1078
+ uiRequireName("SpAvatar", name, void 0);
1079
+ let size = props.pixelSize;
1080
+ if (size !== void 0 && !(Number.isFinite(size) && size > 0)) {
1081
+ warn4("SpAvatar", "pixelSize", `${size} is dropped.`);
1082
+ size = void 0;
1083
+ }
1084
+ const square = props.shape === "square";
1085
+ const image = props.src !== void 0;
1086
+ return el(
1087
+ "span",
1088
+ root("avatar", props, r, {
1089
+ id: props.id,
1090
+ role: image ? void 0 : "img",
1091
+ "aria-label": image ? void 0 : name,
1092
+ "data-shape": square ? "square" : "circle",
1093
+ style: size !== void 0 ? `--sp-ui-avatar-size:${Math.round(size * 100) / 100}px` : void 0
1094
+ }),
1095
+ [
1096
+ frame(square ? "box" : "round"),
1097
+ image ? el("img", { class: "sp-ui-avatar-image", src: props.src, alt: props.alt ?? props.name ?? "" }) : el("span", { class: "sp-ui-avatar-initials", "aria-hidden": "true" }, [text(props.initials ?? uiInitials(props.name))])
1098
+ ]
1099
+ );
1100
+ }
1101
+ function uiAvatarGroupView(props, r) {
1102
+ return el("div", root("avatar-group", props, r, { id: props.id, role: "group", "aria-label": props.label }), [slot("content")]);
1103
+ }
1104
+ function uiStatisticView(props, r) {
1105
+ const affix = (value) => el("span", { class: "sp-ui-statistic-affix" }, [text(value)]);
1106
+ const up = props.trend === "up";
1107
+ return el("div", root("statistic", props, r, { id: props.id, "data-trend": props.trend }), [
1108
+ el("span", { class: "sp-ui-statistic-title" }, [text(props.title)]),
1109
+ el("span", { class: "sp-ui-statistic-value" }, [
1110
+ ...props.prefix ? [affix(props.prefix)] : [],
1111
+ el("span", { class: "sp-ui-statistic-number" }, [text(String(props.value))]),
1112
+ ...props.suffix ? [affix(props.suffix)] : []
1113
+ ]),
1114
+ ...props.trend ? [
1115
+ el("span", { class: "sp-ui-statistic-trend", "data-trend": props.trend }, [
1116
+ glyph(up ? "arrowUp" : "arrowDown"),
1117
+ props.delta ? el("span", { class: "sp-ui-statistic-delta" }, [text(props.delta)]) : sr(up ? "Up" : "Down")
1118
+ ])
1119
+ ] : []
1120
+ ]);
1121
+ }
1122
+ function uiDescriptionsView(props, r) {
1123
+ let columns = props.columns ?? 3;
1124
+ const fixed = Number.isFinite(columns) ? Math.min(6, Math.max(1, Math.round(columns))) : 3;
1125
+ if (fixed !== columns) warn4("SpDescriptions", "columns", `${columns} is drawn as ${fixed}.`);
1126
+ columns = fixed;
1127
+ const titled = props.title !== void 0;
1128
+ const titleId = titled && props.id ? `${props.id}--title` : void 0;
1129
+ const bordered = props.bordered !== false;
1130
+ return el(
1131
+ "section",
1132
+ root("descriptions", props, r, {
1133
+ id: props.id,
1134
+ "aria-labelledby": titleId,
1135
+ "aria-label": titled && !titleId ? props.title : void 0,
1136
+ "data-bordered": bordered ? "true" : void 0,
1137
+ "data-columns": String(columns)
1138
+ }),
1139
+ [
1140
+ ...bordered ? [frame("card")] : [],
1141
+ ...titled ? [heading(props.headingLevel, "SpDescriptions", { class: "sp-ui-descriptions-title", id: titleId }, props.title)] : [],
1142
+ el(
1143
+ "dl",
1144
+ {},
1145
+ keyed(props.items, "SpDescriptions").map(
1146
+ (item) => el("div", { class: "sp-ui-description" }, [el("dt", {}, [text(item.label)]), el("dd", {}, maybeText(item.value))])
1147
+ )
1148
+ )
1149
+ ]
1150
+ );
1151
+ }
1152
+ function uiTimelineListView(props, r) {
1153
+ let current = false;
1154
+ const items = keyed(props.items, "SpTimelineList").map((item) => {
1155
+ let status = item.status ?? "done";
1156
+ if (status === "current") {
1157
+ if (current) {
1158
+ warn4("SpTimelineList", "items", `A second current item, "${item.title}", is drawn as done.`);
1159
+ status = "done";
1160
+ }
1161
+ current = true;
1162
+ }
1163
+ const mark = status === "done" ? [tone("selected", "round")] : status === "current" ? [heighten2()] : status === "error" ? [glyph("cross")] : [];
1164
+ return el("li", { class: "sp-ui-timeline-item", "data-status": status, "aria-current": status === "current" ? "step" : void 0 }, [
1165
+ el("span", { class: "sp-ui-timeline-mark", "aria-hidden": "true" }, [frame("round"), ...mark]),
1166
+ el("span", { class: "sp-ui-timeline-text" }, [
1167
+ ...item.time ? [el("span", { class: "sp-ui-timeline-time" }, [text(item.time)])] : [],
1168
+ el("span", { class: "sp-ui-timeline-title" }, [text(item.title)]),
1169
+ ...item.description ? [el("span", { class: "sp-ui-timeline-description" }, [text(item.description)])] : []
1170
+ ])
1171
+ ]);
1172
+ });
1173
+ return el("ol", root("timeline-list", props, r, { id: props.id, "aria-label": props.label }), items);
1174
+ }
1175
+ function uiCollapseView(props, r) {
1176
+ return el("div", root("collapse", props, r, { id: props.id, "data-accordion": props.accordion ? "true" : void 0 }), [
1177
+ ...props.bordered !== false ? [frame("card")] : [],
1178
+ slot("content")
1179
+ ]);
1180
+ }
1181
+ function uiCollapseGroup(props) {
1182
+ if (!props.accordion) return void 0;
1183
+ if (props.id) return `${props.id}--accordion`;
1184
+ warn4("SpCollapse", "id", "An accordion needs an id to name its items; they open independently.");
1185
+ return void 0;
1186
+ }
1187
+ function uiCollapseItemView(props, r, state2) {
1188
+ return el(
1189
+ "details",
1190
+ root("collapse-item", props, r, { id: props.id, name: state2.group, open: state2.open, "data-disabled": props.disabled ? "true" : void 0 }),
1191
+ [
1192
+ // A disabled item's summary leaves the Tab order: no `disabled` attribute exists for it (REQ-522).
1193
+ el("summary", { class: "sp-ui-collapse-summary", "aria-disabled": props.disabled ? "true" : void 0, tabindex: props.disabled ? "-1" : void 0 }, [
1194
+ el("span", { class: "sp-ui-label" }, [text(props.title)]),
1195
+ glyph("chevronDown")
1196
+ ], "summary"),
1197
+ el("div", { class: "sp-ui-collapse-body" }, [slot("content")])
1198
+ ]
1199
+ );
1200
+ }
1201
+ function uiEmptyView(props, r, slots) {
1202
+ return el("div", root("empty", props, r, { id: props.id }), [
1203
+ el("span", { class: "sp-ui-empty-tray", "aria-hidden": "true" }, [tone("1", "control"), frame("control")]),
1204
+ el("p", { class: "sp-ui-empty-description" }, [text(props.description ?? "No data")]),
1205
+ ...slots.extra ? [el("div", { class: "sp-ui-empty-extra" }, [slot("extra")])] : []
1206
+ ]);
1207
+ }
1208
+ var RESULT_GLYPHS = { success: "tick", info: "info", warning: "warning", error: "cross" };
1209
+ function uiResultView(props, r, slots) {
1210
+ const status = props.status ?? "info";
1211
+ const alert = status === "warning" || status === "error";
1212
+ return el("div", root("result", props, r, { id: props.id, role: alert ? "alert" : "status", "data-status": status }), [
1213
+ el("span", { class: "sp-ui-result-mark", "aria-hidden": "true" }, [frame("round"), glyph(RESULT_GLYPHS[status])]),
1214
+ heading(props.headingLevel, "SpResult", { class: "sp-ui-result-title" }, props.title),
1215
+ ...props.subtitle ? [el("p", { class: "sp-ui-result-subtitle" }, [text(props.subtitle)])] : [],
1216
+ ...slots.extra ? [el("div", { class: "sp-ui-result-extra" }, [slot("extra")])] : []
1217
+ ]);
1218
+ }
1219
+ function message(props) {
1220
+ const base = baseOf(props);
1221
+ const id = props.message && base ? `${base}--message` : void 0;
1222
+ return { id, node: props.message ? [el("span", { class: "sp-ui-message", id }, [text(props.message)])] : [] };
1223
+ }
1224
+ function uiTextareaView(props, r, state2) {
1225
+ const said = message(props);
1226
+ const max = props.maxLength !== void 0 && Number.isInteger(props.maxLength) && props.maxLength >= 0 ? props.maxLength : void 0;
1227
+ return el("span", root("input sp-textarea", props, r, { "data-invalid": props.invalid ? "true" : void 0 }), [
1228
+ el("span", { class: "sp-ui-box" }, [
1229
+ frame("control"),
1230
+ el(
1231
+ "textarea",
1232
+ {
1233
+ class: "sp-ui-control",
1234
+ id: props.id,
1235
+ name: props.name,
1236
+ rows: String(props.rows ?? 3),
1237
+ maxlength: max !== void 0 ? String(max) : void 0,
1238
+ placeholder: props.placeholder,
1239
+ disabled: props.disabled === true,
1240
+ readonly: props.readOnly === true,
1241
+ "aria-label": props.label,
1242
+ "aria-invalid": props.invalid ? "true" : void 0,
1243
+ "aria-describedby": said.id
1244
+ },
1245
+ maybeText(state2.value),
1246
+ "native"
1247
+ ),
1248
+ ...props.invalid ? [glyph("warning")] : []
1249
+ ]),
1250
+ ...props.showCount ? [el("span", { class: "sp-ui-count" }, [text(max !== void 0 ? `${state2.value.length} / ${max}` : String(state2.value.length))])] : [],
1251
+ ...said.node
1252
+ ]);
1253
+ }
1254
+ function uiInputNumberView(props, r, state2) {
1255
+ const { value, atMin, atMax, range } = uiNumberValue(state2.value, props, "SpInputNumber");
1256
+ const said = message(props);
1257
+ const locked = props.disabled === true || props.readOnly === true;
1258
+ const stepper = (direction2, label2, disabled, mark) => el("button", { type: "button", class: "sp-ui-stepper", "data-step": direction2, "aria-label": label2, tabindex: "-1", disabled: locked || disabled }, [glyph(mark)], "stepper");
1259
+ return el("span", root("input sp-input-number", props, r, { "data-invalid": props.invalid ? "true" : void 0 }), [
1260
+ el("span", { class: "sp-ui-box" }, [
1261
+ frame("control"),
1262
+ stepper("-1", props.decrementLabel ?? "Decrease", atMin, "dash"),
1263
+ el(
1264
+ "input",
1265
+ {
1266
+ class: "sp-ui-control",
1267
+ type: "number",
1268
+ inputmode: "decimal",
1269
+ id: props.id,
1270
+ name: props.name,
1271
+ min: range.min !== void 0 ? String(range.min) : void 0,
1272
+ max: range.max !== void 0 ? String(range.max) : void 0,
1273
+ step: String(range.step ?? 1),
1274
+ value: value === null ? "" : String(value),
1275
+ placeholder: props.placeholder,
1276
+ disabled: props.disabled === true,
1277
+ readonly: props.readOnly === true,
1278
+ "aria-label": props.label,
1279
+ "aria-invalid": props.invalid ? "true" : void 0,
1280
+ "aria-describedby": said.id
1281
+ },
1282
+ [],
1283
+ "native"
1284
+ ),
1285
+ stepper("1", props.incrementLabel ?? "Increase", atMax, "plus"),
1286
+ ...props.invalid ? [glyph("warning")] : []
1287
+ ]),
1288
+ ...said.node
1289
+ ]);
1290
+ }
1291
+
1292
+ // src/ui/view-overlay.ts
1293
+ var heighten3 = () => el("span", { part: "sp-heighten", "aria-hidden": "true" });
1294
+ var state = (open) => open ? "open" : "closed";
1295
+ var style = (property, value) => value !== void 0 ? `${property}:${value}` : void 0;
1296
+ var label = (value, plate) => el("span", { class: plate ? "sp-ui-label sp-ui-plate" : "sp-ui-label" }, [text(value)]);
1297
+ function uiTriggerView() {
1298
+ return el("span", { class: "sp-ui-trigger" }, [slot("trigger")], "trigger");
1299
+ }
1300
+ var CONTROLS = {
1301
+ dialog: ["dialog", "dialog"],
1302
+ popover: ["dialog", "popover"],
1303
+ menu: ["menu", "menu"]
1304
+ };
1305
+ function uiTriggerAria(kind, id, open) {
1306
+ if (kind === "tooltip") return id ? { "aria-describedby": `${id}--tooltip` } : {};
1307
+ const [popup, part] = CONTROLS[kind];
1308
+ return { "aria-haspopup": popup, "aria-expanded": String(open), ...id ? { "aria-controls": `${id}--${part}` } : {} };
1309
+ }
1310
+ function uiSelectItems(items) {
1311
+ return uiItems(
1312
+ items.map((item) => typeof item === "string" ? { key: item, label: item } : item),
1313
+ "SpSelect"
1314
+ );
1315
+ }
1316
+ var actionable = (entry) => entry.type === void 0 || entry.type === "item";
1317
+ function uiMenuItems(entries) {
1318
+ return uiItems(entries, "SpDropdownMenu").filter(actionable).map((entry) => ({ key: entry.key, label: entry.label, ...entry.disabled ? { disabled: true } : {} }));
1319
+ }
1320
+ function uiToastDuration(duration, toaster) {
1321
+ const ms = duration ?? toaster ?? 5e3;
1322
+ if (ms === Infinity || Number.isFinite(ms) && ms >= 1e3) return ms;
1323
+ if (process.env.NODE_ENV !== "production") diagnose("SP017", "SpToast", { property: "duration", message: `${ms} ms is drawn as 1000 ms.` });
1324
+ return 1e3;
1325
+ }
1326
+ function dialog(component, slug, props, r, open, footer, attrs) {
1327
+ uiRequireName(component, props.title, props.label);
1328
+ const { id, title } = props;
1329
+ const titleId = title !== void 0 && id ? `${id}--title` : void 0;
1330
+ const descriptionId = props.description && id ? `${id}--description` : void 0;
1331
+ const closable = props.closable !== false;
1332
+ const header = title !== void 0 || closable;
1333
+ return el(
1334
+ "dialog",
1335
+ root(slug, props, r, {
1336
+ id: id ? `${id}--dialog` : void 0,
1337
+ "aria-labelledby": titleId,
1338
+ "aria-label": titleId ? void 0 : title ?? props.label,
1339
+ "aria-describedby": descriptionId,
1340
+ "data-state": state(open),
1341
+ ...attrs
1342
+ }),
1343
+ [
1344
+ frame("card"),
1345
+ ...header ? [
1346
+ el("div", { class: "sp-ui-overlay-header" }, [
1347
+ ...title !== void 0 ? [el("h2", { class: "sp-ui-overlay-title", id: titleId }, [text(title)])] : [],
1348
+ ...closable ? [iconClose(props.closeLabel ?? "Close")] : []
1349
+ ]),
1350
+ el("span", { part: "sp-rule", "aria-hidden": "true" })
1351
+ ] : [],
1352
+ ...props.description ? [el("p", { class: "sp-ui-overlay-description", id: descriptionId }, [text(props.description)])] : [],
1353
+ el("div", { class: "sp-ui-overlay-body" }, [slot("content")]),
1354
+ ...footer ? [el("div", { class: "sp-ui-overlay-footer" }, [slot("footer")])] : []
1355
+ ],
1356
+ "dialog"
1357
+ );
1358
+ }
1359
+ function uiModalView(props, r, s, slots) {
1360
+ return dialog("SpModal", "modal", props, r, s.open, slots.footer === true, {
1361
+ style: style("--sp-ui-overlay-width", cssLength(props.width, "SpModal", "width"))
1362
+ });
1363
+ }
1364
+ function uiDrawerView(props, r, s, slots) {
1365
+ return dialog("SpDrawer", "drawer", props, r, s.open, slots.footer === true, {
1366
+ "data-side": props.side ?? "end",
1367
+ style: style("--sp-ui-drawer-extent", cssLength(props.extent, "SpDrawer", "extent"))
1368
+ });
1369
+ }
1370
+ function uiPopoverView(props, r, s) {
1371
+ uiRequireName("SpPopover", props.title, props.label);
1372
+ const { id, title } = props;
1373
+ const titleId = title !== void 0 && id ? `${id}--title` : void 0;
1374
+ const header = title !== void 0 || props.closable === true;
1375
+ return el(
1376
+ "div",
1377
+ root("popover", props, r, {
1378
+ id: id ? `${id}--popover` : void 0,
1379
+ popover: "auto",
1380
+ role: "dialog",
1381
+ "aria-labelledby": titleId,
1382
+ "aria-label": titleId ? void 0 : title ?? props.label,
1383
+ "data-state": state(s.open),
1384
+ style: style("--sp-ui-overlay-width", cssLength(props.width, "SpPopover", "width"))
1385
+ }),
1386
+ [
1387
+ frame("card"),
1388
+ ...header ? [
1389
+ el("div", { class: "sp-ui-overlay-header" }, [
1390
+ ...title !== void 0 ? [el("div", { class: "sp-ui-overlay-title", id: titleId }, [text(title)])] : [],
1391
+ ...props.closable ? [iconClose(props.closeLabel ?? "Close")] : []
1392
+ ])
1393
+ ] : [],
1394
+ el("div", { class: "sp-ui-overlay-body" }, [slot("content")])
1395
+ ],
1396
+ "popover"
1397
+ );
1398
+ }
1399
+ function uiTooltipView(props, r, s) {
1400
+ return el(
1401
+ "div",
1402
+ root("tooltip", props, r, { id: props.id ? `${props.id}--tooltip` : void 0, popover: "manual", role: "tooltip", "data-state": state(s.open) }),
1403
+ [frame("control"), el("span", { class: "sp-ui-tooltip-text" }, [text(props.text)])],
1404
+ "popover"
1405
+ );
1406
+ }
1407
+ function uiSelectView(props, r, s) {
1408
+ uiRequireName("SpSelect", void 0, props.label);
1409
+ const items = uiSelectItems(props.items);
1410
+ const value = uiSelectedKey(items, s.value, "none");
1411
+ const chosen = items.find((item) => item.key === value);
1412
+ const { id } = props;
1413
+ const optionId = (key) => id ? `${id}--option-${key}` : void 0;
1414
+ const active = s.open ? items.find((item) => item.key === s.active && item.disabled !== true) : void 0;
1415
+ const options = items.map((item) => {
1416
+ const isActive = item === active;
1417
+ const isChosen = item === chosen;
1418
+ return el(
1419
+ "div",
1420
+ {
1421
+ class: "sp-ui-option",
1422
+ role: "option",
1423
+ id: optionId(item.key),
1424
+ "data-key": item.key,
1425
+ "aria-selected": String(isChosen),
1426
+ "aria-disabled": item.disabled ? "true" : void 0
1427
+ },
1428
+ [...isActive ? [heighten3()] : [], label(item.label, isActive), ...isChosen ? [glyph("tick")] : []],
1429
+ "option"
1430
+ );
1431
+ });
1432
+ return el("span", root("select", props, r, { id, style: style("--sp-ui-select-width", cssLength(props.width, "SpSelect", "width")) }), [
1433
+ el(
1434
+ "button",
1435
+ {
1436
+ type: "button",
1437
+ class: "sp-ui-select-trigger",
1438
+ role: "combobox",
1439
+ "aria-haspopup": "listbox",
1440
+ "aria-expanded": String(s.open),
1441
+ "aria-controls": id ? `${id}--listbox` : void 0,
1442
+ "aria-activedescendant": active ? optionId(active.key) : void 0,
1443
+ "aria-label": props.label,
1444
+ "aria-required": props.required ? "true" : void 0,
1445
+ disabled: props.disabled === true
1446
+ },
1447
+ [
1448
+ frame("control"),
1449
+ el("span", { class: "sp-ui-select-value", "data-placeholder": chosen ? void 0 : "true" }, [text(chosen?.label ?? props.placeholder ?? "Select\u2026")]),
1450
+ glyph("chevronDown")
1451
+ ],
1452
+ "trigger"
1453
+ ),
1454
+ el("div", { class: "sp-ui-select-content", popover: "auto", "data-state": state(s.open) }, [
1455
+ frame("card"),
1456
+ el("div", { role: "listbox", id: id ? `${id}--listbox` : void 0, "aria-label": props.label }, options)
1457
+ ], "popover"),
1458
+ ...props.name ? [el("input", { type: "hidden", name: props.name, value: value ?? "" })] : []
1459
+ ]);
1460
+ }
1461
+ function uiDropdownMenuView(props, r, s, slots) {
1462
+ const { id } = props;
1463
+ const entries = uiItems(props.items, "SpDropdownMenu");
1464
+ const active = s.open ? entries.find((entry) => actionable(entry) && entry.key === s.active && entry.disabled !== true) : void 0;
1465
+ const children = entries.map((entry) => {
1466
+ if (entry.type === "separator") return el("div", { class: "sp-ui-menu-separator", role: "separator" });
1467
+ if (entry.type === "label") return el("div", { class: "sp-ui-menu-label", role: "presentation" }, [text(entry.label)]);
1468
+ const isActive = entry === active;
1469
+ return el(
1470
+ "div",
1471
+ {
1472
+ class: "sp-ui-menuitem",
1473
+ role: "menuitem",
1474
+ tabindex: "-1",
1475
+ "data-key": entry.key,
1476
+ "aria-disabled": entry.disabled ? "true" : void 0,
1477
+ "data-danger": entry.danger ? "true" : void 0
1478
+ },
1479
+ [
1480
+ ...isActive ? [heighten3()] : [],
1481
+ label(entry.label, isActive),
1482
+ ...entry.shortcut ? [el("span", { class: "sp-ui-menu-shortcut", "aria-hidden": "true" }, [text(entry.shortcut)])] : []
1483
+ ],
1484
+ "menuitem"
1485
+ );
1486
+ });
1487
+ const trigger = slots.trigger ? uiTriggerView() : el(
1488
+ "button",
1489
+ { type: "button", class: "sp-ui-menu-trigger", ...uiTriggerAria("menu", id, s.open) },
1490
+ [frame("control"), el("span", { class: "sp-ui-label" }, [text(props.label)]), glyph("chevronDown")],
1491
+ "trigger"
1492
+ );
1493
+ return el("span", root("dropdown-menu", props, r, { id }), [
1494
+ trigger,
1495
+ el(
1496
+ "div",
1497
+ { class: "sp-ui-menu", id: id ? `${id}--menu` : void 0, popover: "auto", role: "menu", "aria-label": props.label, "data-state": state(s.open) },
1498
+ [frame("card"), ...children],
1499
+ "popover"
1500
+ )
1501
+ ]);
1502
+ }
1503
+ var TOAST_GLYPHS = { info: "info", success: "tick", warning: "warning", error: "cross" };
1504
+ function uiToastView(props, r, s, slots) {
1505
+ const kind = props.kind ?? "info";
1506
+ const urgent = kind === "warning" || kind === "error";
1507
+ return el("li", root("toast", props, r, { id: props.id, role: "listitem", "data-kind": kind, "data-state": state(s.open) }), [
1508
+ frame("card"),
1509
+ el("div", { class: "sp-ui-toast-body", role: urgent ? "alert" : "status" }, [
1510
+ glyph(TOAST_GLYPHS[kind]),
1511
+ el("div", { class: "sp-ui-toast-text" }, [
1512
+ el("span", { class: "sp-ui-toast-title" }, [text(props.title)]),
1513
+ ...props.description ? [el("span", { class: "sp-ui-toast-description" }, [text(props.description)])] : []
1514
+ ])
1515
+ ]),
1516
+ ...slots.action ? [el("div", { class: "sp-ui-toast-action" }, [slot("action")])] : [],
1517
+ iconClose(props.closeLabel ?? "Dismiss")
1518
+ ]);
1519
+ }
1520
+ function uiToasterView(props, r) {
1521
+ return el(
1522
+ "section",
1523
+ root("toaster", props, r, { id: props.id, "aria-label": props.label ?? "Notifications", "data-position": props.position ?? "bottom-end" }),
1524
+ [el("ol", { class: "sp-ui-toaster-list", role: "list" }, [slot("content")])]
1525
+ );
1526
+ }
1527
+
1528
+ export { UI_COMPONENTS, UI_FRAME_KINDS, UI_PROGRESS_VIEWBOX, UI_TOKEN_DEFAULTS, resolveUi, resolveUiTokens, uiAlertView, uiAnchor, uiAvatarGroupView, uiAvatarView, uiBadgeView, uiBreadcrumbView, uiButtonView, uiCardView, uiCheckboxView, uiCollapseGroup, uiCollapseItemView, uiCollapseView, uiDescriptionsView, uiDividerView, uiDrawerView, uiDropdownMenuView, uiEmptyView, uiFrameOutline, uiFrameVariant, uiGlyphView, uiInitials, uiInputNumberView, uiInputView, uiIsRovingKey, uiItems, uiListKey, uiMenuItems, uiModalView, uiNumberStep, uiNumberValue, uiOverlaySync, uiPageWindow, uiPagination, uiPaginationView, uiPlace, uiPopoverView, uiProgressArc, uiProgressView, uiRadioGroupView, uiRange, uiRateCount, uiRateView, uiResultView, uiRovingFocus, uiRovingKey, uiSegmentedView, uiSelectItems, uiSelectView, uiSelectedKey, uiSkeletonView, uiSliderView, uiStatisticView, uiSteps, uiStepsView, uiSurfaceView, uiSwitchView, uiTabPanelView, uiTableView, uiTabsView, uiTagView, uiTextareaView, uiTimelineListView, uiToastDuration, uiToastView, uiToasterView, uiToneTile, uiTooltipView, uiTriggerAria, uiTriggerView, uiTypeahead, uiValue };
714
1529
  //# sourceMappingURL=ui.js.map
715
1530
  //# sourceMappingURL=ui.js.map