@silverpoint/core 0.6.0 → 0.8.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 (46) hide show
  1. package/README.md +14 -0
  2. package/dist/catalog.d.ts +2 -2
  3. package/dist/catalog.js +4 -2
  4. package/dist/catalog.js.map +1 -1
  5. package/dist/{chunk-OLYKR5PS.js → chunk-46QELMCG.js} +8 -213
  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-PD4MD7JU.js → chunk-E5CC2NNQ.js} +5 -3
  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-NI7B6UEO.js +24 -0
  18. package/dist/chunk-NI7B6UEO.js.map +1 -0
  19. package/dist/chunk-WYFRHI4S.js +179 -0
  20. package/dist/chunk-WYFRHI4S.js.map +1 -0
  21. package/dist/contrast-siWF_R5o.d.ts +30 -0
  22. package/dist/dashboard-demos.d.ts +2 -2
  23. package/dist/index.d.ts +13 -10
  24. package/dist/index.js +20 -9
  25. package/dist/index.js.map +1 -1
  26. package/dist/{model-CLhtoG75.d.ts → model-DVVt59AB.d.ts} +1 -1
  27. package/dist/palettes.d.ts +29 -0
  28. package/dist/palettes.js +79 -0
  29. package/dist/palettes.js.map +1 -0
  30. package/dist/{props-Dq8FC8GG.d.ts → props-BZ9CcrGv.d.ts} +53 -1
  31. package/dist/timeline.d.ts +90 -0
  32. package/dist/timeline.js +125 -0
  33. package/dist/timeline.js.map +1 -0
  34. package/dist/{types-B81k-s9_.d.ts → types-BT3Xm7fP.d.ts} +3 -1
  35. package/dist/types-D5ys35fF.d.ts +576 -0
  36. package/dist/types-DHBJ0PzV.d.ts +57 -0
  37. package/dist/ui-demos.d.ts +14 -3
  38. package/dist/ui-demos.js +163 -1
  39. package/dist/ui-demos.js.map +1 -1
  40. package/dist/ui.d.ts +346 -325
  41. package/dist/ui.js +932 -118
  42. package/dist/ui.js.map +1 -1
  43. package/dist/view-DteS9glE.d.ts +82 -0
  44. package/package.json +13 -1
  45. package/dist/chunk-OLYKR5PS.js.map +0 -1
  46. package/dist/chunk-PD4MD7JU.js.map +0 -1
package/dist/ui.js CHANGED
@@ -1,5 +1,13 @@
1
1
  import { DEFAULTS } from './chunk-KFBSQH5G.js';
2
- import { round2, roundPathData, arcPath, circlePath, diagnose, resolveSeed } from './chunk-OLYKR5PS.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';
3
11
 
4
12
  // src/ui/catalog.ts
5
13
  var BATCH = {
@@ -15,14 +23,17 @@ var BATCH = {
15
23
  Slider: "B2",
16
24
  Rate: "B2"
17
25
  };
18
- var row = (name, group, states) => Object.freeze({
26
+ var row = (name, group, states, family2 = "ui", batch = BATCH[name] ?? "B3") => Object.freeze({
19
27
  name,
20
- batch: BATCH[name] ?? "B3",
28
+ batch,
21
29
  slug: name.replace(/[A-Z]/g, (m, i) => (i ? "-" : "") + m.toLowerCase()),
22
30
  component: `Sp${name}`,
23
31
  group,
32
+ family: family2,
24
33
  states: Object.freeze([...states])
25
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");
26
37
  var UI_COMPONENTS = /* @__PURE__ */ Object.freeze([
27
38
  row("Button", "actions", ["default", "primary", "danger", "disabled"]),
28
39
  row("Input", "data-entry", ["empty", "filled", "invalid", "disabled"]),
@@ -40,13 +51,34 @@ var UI_COMPONENTS = /* @__PURE__ */ Object.freeze([
40
51
  row("Divider", "data-display", ["plain", "text"]),
41
52
  row("Progress", "feedback", ["line", "circle", "indeterminate"]),
42
53
  row("Alert", "feedback", ["info", "success", "warning", "error"]),
43
- 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"])
44
76
  ]);
45
77
 
46
78
  // src/ui/value.ts
47
79
  var DEFAULT_RANGE = { min: 0, max: 100, step: 1 };
48
- function warn(component, property, message) {
49
- 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 });
50
82
  }
51
83
  var tidy = (x) => Number.parseFloat(x.toPrecision(12));
52
84
  function uiRange(range, component = "UiValue") {
@@ -57,14 +89,14 @@ function uiRange(range, component = "UiValue") {
57
89
  return DEFAULT_RANGE;
58
90
  }
59
91
  function uiValue(value, range, component = "UiValue") {
60
- const valid = uiRange(range, component);
61
- const { min, max, step } = valid;
92
+ const valid2 = uiRange(range, component);
93
+ const { min, max, step } = valid2;
62
94
  let next = Number.isFinite(value) ? Math.min(Math.max(value, min), max) : min;
63
95
  if (step !== void 0) {
64
96
  const steps = Math.min(Math.round((next - min) / step), Math.floor(tidy((max - min) / step)));
65
97
  next = tidy(min + steps * step);
66
98
  }
67
- const corrected = valid !== range || next !== value;
99
+ const corrected = valid2 !== range || next !== value;
68
100
  if (next !== value) warn(component, "value", `${value} is drawn as ${next}.`);
69
101
  return { value: next, fraction: round2((next - min) / (max - min)), corrected };
70
102
  }
@@ -112,13 +144,13 @@ function uiSelectedKey(items, value, fallback) {
112
144
  function uiSteps(items, current, component = "SpSteps") {
113
145
  const steps = uiItems(items, component);
114
146
  if (steps.length === 0) return [];
115
- const last = steps.length - 1;
116
- 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;
117
149
  if (at !== current && process.env.NODE_ENV !== "production") {
118
150
  diagnose("SP017", component, { property: "current", message: `${current} is drawn as ${at}.` });
119
151
  }
120
152
  const statuses = steps.map((item, i) => item.status ?? (i < at ? "finish" : i === at ? "process" : "wait"));
121
- 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 }));
122
154
  }
123
155
 
124
156
  // src/ui/keyboard.ts
@@ -127,12 +159,12 @@ function direction(key, orientation, dir) {
127
159
  const forward = key === "ArrowRight" ? 1 : key === "ArrowLeft" ? -1 : 0;
128
160
  return dir === "rtl" ? -forward : forward;
129
161
  }
130
- function uiRovingKey(state, key, orientation, dir) {
131
- const { index, count, disabled } = state;
132
- 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;
133
165
  if (key === "Home" || key === "End") {
134
166
  const step2 = key === "Home" ? 1 : -1;
135
- 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;
136
168
  return index;
137
169
  }
138
170
  const step = direction(key, orientation, dir);
@@ -140,7 +172,7 @@ function uiRovingKey(state, key, orientation, dir) {
140
172
  let i = index < 0 || index >= count ? step > 0 ? -1 : count : index;
141
173
  for (let n = 0; n < count; n++) {
142
174
  i = ((i + step) % count + count) % count;
143
- if (enabled(i)) return i;
175
+ if (enabled2(i)) return i;
144
176
  }
145
177
  return index;
146
178
  }
@@ -198,13 +230,6 @@ function uiFrameOutline(kind) {
198
230
  };
199
231
  }
200
232
 
201
- // src/ui/names.ts
202
- function uiRequireName(component, text2, label) {
203
- if (process.env.NODE_ENV === "production") return;
204
- if (text2?.trim() || label?.trim()) return;
205
- diagnose("SP018", component, { property: "label" });
206
- }
207
-
208
233
  // src/ui/tokens.ts
209
234
  var UI_TOKEN_DEFAULTS = /* @__PURE__ */ Object.freeze({
210
235
  frame: "css",
@@ -212,11 +237,14 @@ var UI_TOKEN_DEFAULTS = /* @__PURE__ */ Object.freeze({
212
237
  controlHeight: Object.freeze({ sm: 24, md: 32, lg: 40 }),
213
238
  radius: 2,
214
239
  focusWidth: 2,
215
- 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
216
243
  });
217
244
  var MIN_HEIGHT = 24;
218
245
  var MIN_FOCUS = 2;
219
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));
220
248
  function resolveUiTokens(ground) {
221
249
  const ui = ground.ui ?? UI_TOKEN_DEFAULTS;
222
250
  const inked = ui.frame === "inked" && ground.tonalMechanism === "hatch";
@@ -231,7 +259,9 @@ function resolveUiTokens(ground) {
231
259
  },
232
260
  radius: within(ui.radius, 0, Infinity, 2),
233
261
  focusWidth: within(ui.focusWidth, MIN_FOCUS, Infinity, 2),
234
- tone: ui.tone
262
+ tone: ui.tone,
263
+ backdrop: level(ui.backdrop, 2),
264
+ rowTone: level(ui.rowTone, 1)
235
265
  };
236
266
  }
237
267
 
@@ -247,10 +277,10 @@ function family(gap, angle) {
247
277
  const height = Math.abs(cos) < EPSILON ? lines * gap : lines * gap / Math.abs(cos);
248
278
  const offset = (x, y) => -sin * x + cos * y;
249
279
  const corners = [offset(0, 0), offset(width, 0), offset(0, height), offset(width, height)];
250
- const first = Math.ceil(Math.min(...corners) / gap - 0.5);
251
- 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);
252
282
  const strokes = [];
253
- for (let k = first; k <= last; k++) {
283
+ for (let k = first2; k <= last2; k++) {
254
284
  const c = (k + 0.5) * gap;
255
285
  const at = (t) => ({ x: -sin * c + cos * t, y: cos * c + sin * t, t });
256
286
  const hits = [
@@ -260,11 +290,11 @@ function family(gap, angle) {
260
290
  const from = hits[0];
261
291
  const to = hits.at(-1);
262
292
  if (!from || !to || to.t - from.t < EPSILON * 1e3) continue;
263
- 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));
264
294
  const w = round2(width);
265
295
  const h = round2(height);
266
296
  strokes.push({
267
- 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)}`,
268
298
  role: "ornament",
269
299
  part: "ink"
270
300
  });
@@ -278,75 +308,264 @@ function uiToneTile(spec) {
278
308
  return layers;
279
309
  }
280
310
 
281
- // src/ui/view-kit.ts
282
- function clean(attrs) {
283
- const out = {};
284
- for (const [name, value] of Object.entries(attrs)) if (value !== void 0 && value !== false) out[name] = value;
285
- return out;
286
- }
287
- var el = (tag, attrs, children = [], bind) => ({
288
- tag,
289
- attrs: clean(attrs),
290
- children,
291
- ...bind ? { bind } : {}
292
- });
293
- var text = (value) => ({ text: value });
294
- var slot = (name) => ({ slot: name });
295
- function root(slug, props, r, attrs = {}) {
296
- return {
297
- class: ["sp-ui", `sp-${slug}`, `sp-ground-${r.ground}`, props.className].filter(Boolean).join(" "),
298
- "data-ground": r.ground,
299
- "data-substrate": r.substrate,
300
- "data-mode": r.mode,
301
- "data-size": r.size,
302
- "data-frame": String(r.frame),
303
- ...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;
304
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 };
305
361
  }
306
- var frame = (kind) => el("span", { part: "sp-frame", "data-kind": kind, "aria-hidden": "true" });
307
- var tone = (value, kind) => el("span", { part: "sp-tone", "data-tone": value, "data-kind": kind, "aria-hidden": "true" });
308
- var UI_GLYPHS = Object.freeze({
309
- tick: "M3.5,8.5L6.5,11.5L12.5,4.5",
310
- dash: "M4,8H12",
311
- cross: "M4.5,4.5L11.5,11.5M11.5,4.5L4.5,11.5",
312
- warning: "M8,2.5L14.5,13.5H1.5Z M8,6.5V9.5 M8,11.5V11.5",
313
- /** A radio's selection: filled by ui.css. */
314
- dot: "M11,8A3,3 0 1,1 5,8A3,3 0 1,1 11,8Z",
315
- /** A Rate mark (C-2). */
316
- lozenge: "M8,1.5L14.5,8L8,14.5L1.5,8Z",
317
- 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"
318
- });
319
- 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] })]);
320
- var sr = (value) => el("span", { class: "sp-ui-sr" }, [text(value)]);
321
- var baseOf = (props) => props.id ?? props.name;
322
- function nativeInput(type, attrs) {
323
- 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?.();
324
540
  }
325
541
 
326
542
  // src/ui/view.ts
327
543
  var FRAME_SLOTS = 6;
328
544
  function resolveUi(props, env) {
329
- const { provider = {}, cell = {} } = env;
330
- 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;
331
547
  return {
332
548
  ground: typeof ground === "string" ? ground : ground.name,
333
- substrate: props.substrate ?? cell.substrate ?? provider.substrate ?? DEFAULTS.substrate,
334
- 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,
335
551
  size: props.size ?? "md",
336
- 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))
337
555
  };
338
556
  }
339
557
  function uiButtonView(props, r, content) {
340
558
  uiRequireName("SpButton", content.text, props.label);
341
559
  const variant = props.variant ?? "default";
342
- const link = props.href !== void 0;
560
+ const href = safeHref(props.href, "SpButton");
561
+ const link = href !== void 0;
343
562
  const toned = props.disabled ? "disabled" : variant === "default" ? void 0 : variant;
344
563
  return el(
345
564
  link ? "a" : "button",
346
565
  root("button", props, r, {
347
566
  id: props.id,
348
567
  type: link ? void 0 : props.type ?? "button",
349
- href: link && !props.disabled ? props.href : void 0,
568
+ href: link && !props.disabled ? href : void 0,
350
569
  disabled: !link && props.disabled === true,
351
570
  "aria-disabled": link && props.disabled ? "true" : void 0,
352
571
  "aria-label": props.label,
@@ -361,14 +580,14 @@ function uiButtonView(props, r, content) {
361
580
  ]
362
581
  );
363
582
  }
364
- function uiInputView(props, r, state) {
583
+ function uiInputView(props, r, state2) {
365
584
  const base = baseOf(props);
366
- const message = props.message ? props.message : void 0;
367
- 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;
368
587
  return el("span", root("input", props, r, { "data-invalid": props.invalid ? "true" : void 0 }), [
369
588
  el("span", { class: "sp-ui-box" }, [
370
589
  frame("control"),
371
- ...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")])] : [],
372
591
  el(
373
592
  "input",
374
593
  {
@@ -376,7 +595,7 @@ function uiInputView(props, r, state) {
376
595
  id: props.id,
377
596
  type: props.type ?? "text",
378
597
  name: props.name,
379
- value: state.value,
598
+ value: state2.value,
380
599
  placeholder: props.placeholder,
381
600
  disabled: props.disabled === true,
382
601
  readonly: props.readOnly === true,
@@ -387,10 +606,10 @@ function uiInputView(props, r, state) {
387
606
  [],
388
607
  "native"
389
608
  ),
390
- ...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")])] : [],
391
610
  ...props.invalid ? [glyph("warning")] : []
392
611
  ]),
393
- ...message ? [el("span", { class: "sp-ui-message", id: messageId }, [text(message)])] : []
612
+ ...message2 ? [el("span", { class: "sp-ui-message", id: messageId }, [text(message2)])] : []
394
613
  ]);
395
614
  }
396
615
  function nativeCheckbox(props, checked, role) {
@@ -401,16 +620,16 @@ function nativeCheckbox(props, checked, role) {
401
620
  "native"
402
621
  );
403
622
  }
404
- function uiCheckboxView(props, r, state) {
623
+ function uiCheckboxView(props, r, state2) {
405
624
  return el("label", root("checkbox", props, r, { "data-indeterminate": props.indeterminate ? "true" : void 0 }), [
406
- nativeCheckbox(props, state.checked),
625
+ nativeCheckbox(props, state2.checked),
407
626
  el("span", { class: "sp-ui-box", "aria-hidden": "true" }, [frame("box"), tone("selected", "box"), glyph("tick"), glyph("dash")]),
408
627
  el("span", { class: "sp-ui-label" }, [text(props.label)])
409
628
  ]);
410
629
  }
411
- function uiSwitchView(props, r, state) {
630
+ function uiSwitchView(props, r, state2) {
412
631
  return el("label", root("switch", props, r), [
413
- nativeCheckbox(props, state.checked, "switch"),
632
+ nativeCheckbox(props, state2.checked, "switch"),
414
633
  el("span", { class: "sp-ui-track", "aria-hidden": "true" }, [frame("pill"), tone("selected", "pill"), el("span", { part: "sp-knob" })]),
415
634
  el("span", { class: "sp-ui-label" }, [text(props.label)])
416
635
  ]);
@@ -426,7 +645,7 @@ function uiCardView(props, r, slots) {
426
645
  frame("card"),
427
646
  ...header ? [
428
647
  el("div", { class: "sp-ui-card-header" }, [
429
- ...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)])] : [],
430
649
  ...slots.extra ? [el("div", { class: "sp-ui-card-extra" }, [slot("extra")])] : []
431
650
  ]),
432
651
  el("span", { part: "sp-rule", "aria-hidden": "true" })
@@ -454,11 +673,11 @@ function uiDividerView(props, r) {
454
673
 
455
674
  // src/ui/view-b2.ts
456
675
  var heighten = () => el("span", { part: "sp-heighten", "aria-hidden": "true" });
457
- 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)]);
458
677
  var groupName = (props) => props.name ?? (props.id ? `${props.id}--value` : void 0);
459
- function uiRadioGroupView(props, r, state) {
678
+ function uiRadioGroupView(props, r, state2) {
460
679
  const items = uiItems(props.items, "SpRadioGroup");
461
- const value = uiSelectedKey(items, state.value, "none");
680
+ const value = uiSelectedKey(items, state2.value, "none");
462
681
  return el("fieldset", root("radio-group", props, r, { id: props.id, "data-orientation": props.orientation ?? "vertical" }), [
463
682
  legend(props.label, true),
464
683
  ...items.map(
@@ -476,9 +695,9 @@ function uiRadioGroupView(props, r, state) {
476
695
  )
477
696
  ]);
478
697
  }
479
- function uiSegmentedView(props, r, state) {
698
+ function uiSegmentedView(props, r, state2) {
480
699
  const items = uiItems(props.items, "SpSegmented");
481
- const value = uiSelectedKey(items, state.value, "first");
700
+ const value = uiSelectedKey(items, state2.value, "first");
482
701
  const name = groupName(props);
483
702
  return el("fieldset", root("segmented", props, r, { id: props.id, "data-block": props.block ? "true" : void 0 }), [
484
703
  legend(props.label, false),
@@ -497,9 +716,9 @@ function uiSegmentedView(props, r, state) {
497
716
  ])
498
717
  ]);
499
718
  }
500
- function uiTabsView(props, r, state) {
719
+ function uiTabsView(props, r, state2) {
501
720
  const items = uiItems(props.items, "SpTabs");
502
- const value = uiSelectedKey(items, state.value, "first");
721
+ const value = uiSelectedKey(items, state2.value, "first");
503
722
  const vertical = props.orientation === "vertical";
504
723
  const id = props.id;
505
724
  return el("div", root("tabs", props, r, { id, "data-orientation": vertical ? "vertical" : "horizontal" }), [
@@ -543,11 +762,11 @@ function uiTabPanelView(props, tabs) {
543
762
  [slot("content")]
544
763
  );
545
764
  }
546
- function uiSliderView(props, r, state) {
547
- const valid = uiRange({ min: props.min ?? 0, max: props.max ?? 100, step: props.step ?? 1 }, "SpSlider");
548
- const { value, fraction } = uiValue(state.value, valid, "SpSlider");
549
- const span = valid.max - valid.min;
550
- 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);
551
770
  return el("label", root("slider", props, r, { style: `--sp-ui-fraction: ${fraction}`, "data-disabled": props.disabled ? "true" : void 0 }), [
552
771
  el("span", { class: "sp-ui-label" }, [text(props.label)]),
553
772
  el("span", { class: "sp-ui-rail" }, [
@@ -561,9 +780,9 @@ function uiSliderView(props, r, state) {
561
780
  class: "sp-ui-range",
562
781
  id: props.id,
563
782
  name: props.name,
564
- min: String(valid.min),
565
- max: String(valid.max),
566
- step: String(valid.step ?? 1),
783
+ min: String(valid2.min),
784
+ max: String(valid2.max),
785
+ step: String(valid2.step ?? 1),
567
786
  value: String(value),
568
787
  disabled: props.disabled === true
569
788
  },
@@ -575,14 +794,14 @@ function uiSliderView(props, r, state) {
575
794
  el(
576
795
  "span",
577
796
  { class: "sp-ui-scale", "aria-hidden": "true" },
578
- 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))]))
579
798
  )
580
799
  ] : []
581
800
  ]);
582
801
  }
583
- function uiRateView(props, r, state) {
802
+ function uiRateView(props, r, state2) {
584
803
  const count = uiRateCount(props.count);
585
- 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");
586
805
  const name = groupName(props);
587
806
  const readOnly = props.readOnly === true;
588
807
  return el(
@@ -608,7 +827,7 @@ function uiRateView(props, r, state) {
608
827
  }
609
828
 
610
829
  // src/ui/view-b3.ts
611
- 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");
612
831
  function uiStepsView(props, r) {
613
832
  const steps = uiSteps(props.items, props.current);
614
833
  const titles = new Map(props.items.map((item) => [item.key, item]));
@@ -631,13 +850,13 @@ function uiStepsView(props, r) {
631
850
  );
632
851
  }
633
852
  function uiTagView(props, r, content) {
634
- const level = String(props.tone ?? 1);
635
- const label = props.closeLabel ?? (content.text ? `Remove ${content.text}` : "Remove");
636
- 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 }), [
637
856
  frame("control"),
638
- tone(level, "control"),
857
+ tone(level2, "control"),
639
858
  el("span", { class: "sp-ui-label sp-ui-plate" }, [slot("content")]),
640
- ...props.closable ? [closeButton(label)] : []
859
+ ...props.closable ? [closeButton(label2)] : []
641
860
  ]);
642
861
  }
643
862
  function uiBadgeView(props, r, content) {
@@ -684,7 +903,7 @@ function uiProgressView(props, r) {
684
903
  );
685
904
  }
686
905
  var ALERT_GLYPHS = { info: "info", success: "tick", warning: "warning", error: "cross" };
687
- function uiAlertView(props, r, state) {
906
+ function uiAlertView(props, r, state2) {
688
907
  const kind = props.kind ?? "info";
689
908
  const error = kind === "error";
690
909
  return el("div", root("alert", props, r, { id: props.id, role: error || kind === "warning" ? "alert" : "status", "data-kind": kind }), [
@@ -695,7 +914,7 @@ function uiAlertView(props, r, state) {
695
914
  ...props.title ? [el("div", { class: "sp-ui-alert-title" }, [text(props.title)])] : [],
696
915
  el("div", { class: "sp-ui-alert-text" }, [slot("content")])
697
916
  ]),
698
- ...state.closable ? [closeButton(props.closeLabel ?? "Close")] : []
917
+ ...state2.closable ? [closeButton(props.closeLabel ?? "Close")] : []
699
918
  ]);
700
919
  }
701
920
  function uiSkeletonView(props, r) {
@@ -711,6 +930,601 @@ function uiSkeletonView(props, r) {
711
930
  ]);
712
931
  }
713
932
 
714
- 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 };
715
1529
  //# sourceMappingURL=ui.js.map
716
1530
  //# sourceMappingURL=ui.js.map