@mirror-physics/fractal-ui 0.2.0-next.9 → 0.2.0-next.90

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.
package/dist/index.cjs CHANGED
@@ -28,47 +28,91 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
30
  // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
33
  Alert: () => Alert,
34
34
  AlertDescription: () => AlertDescription,
35
+ AlertGhost: () => AlertGhost,
35
36
  AlertTitle: () => AlertTitle,
37
+ Breadcrumbs: () => Breadcrumbs,
36
38
  Button: () => Button,
39
+ ButtonGhost: () => ButtonGhost,
37
40
  Card: () => Card,
38
41
  CardContent: () => CardContent,
39
42
  CardDescription: () => CardDescription,
40
43
  CardFooter: () => CardFooter,
44
+ CardGhost: () => CardGhost,
41
45
  CardHeader: () => CardHeader,
42
46
  CardTitle: () => CardTitle,
47
+ Checkbox: () => Checkbox,
48
+ CheckboxGhost: () => CheckboxGhost,
43
49
  Chip: () => Chip,
50
+ ChipGhost: () => ChipGhost,
51
+ CodeBlock: () => CodeBlock,
52
+ ColumnEditor: () => ColumnEditor,
53
+ ContentSwitcher: () => ContentSwitcher,
54
+ ContentSwitcherGhost: () => ContentSwitcherGhost,
55
+ ConversationGhost: () => ConversationGhost,
44
56
  DataTable: () => DataTable,
57
+ DataTableGhost: () => DataTableGhost,
45
58
  Disclosure: () => Disclosure,
59
+ DisclosureGhost: () => DisclosureGhost,
46
60
  Dropdown: () => Dropdown,
61
+ DropdownGhost: () => DropdownGhost,
47
62
  FeatureCard: () => FeatureCard,
48
63
  FileDropzone: () => FileDropzone,
64
+ FileDropzoneGhost: () => FileDropzoneGhost,
49
65
  FolderCard: () => FolderCard,
66
+ Ghost: () => Ghost,
67
+ GhostLine: () => GhostLine,
50
68
  Input: () => Input,
69
+ InputGhost: () => InputGhost,
51
70
  Label: () => Label,
71
+ LabelGhost: () => LabelGhost,
72
+ LatticeLoader: () => LatticeLoader,
52
73
  Link: () => Link,
74
+ LinkGhost: () => LinkGhost,
53
75
  Modal: () => Modal,
76
+ ModalGhost: () => ModalGhost,
77
+ NumberInput: () => NumberInput,
78
+ Pagination: () => Pagination,
79
+ PaginationGhost: () => PaginationGhost,
80
+ PasswordInput: () => PasswordInput,
54
81
  PromptCard: () => PromptCard,
82
+ PromptCardGhost: () => PromptCardGhost,
55
83
  PromptGrid: () => PromptGrid,
84
+ Radio: () => Radio,
56
85
  SidePanel: () => SidePanel,
86
+ SidePanelGhost: () => SidePanelGhost,
57
87
  Sidebar: () => Sidebar,
88
+ SidebarAccountMenu: () => SidebarAccountMenu,
58
89
  SidebarBrand: () => SidebarBrand,
59
90
  SidebarFooter: () => SidebarFooter,
91
+ SidebarGhost: () => SidebarGhost,
60
92
  SidebarHeader: () => SidebarHeader,
61
93
  SidebarNav: () => SidebarNav,
62
94
  SidebarNavItem: () => SidebarNavItem,
63
95
  SidebarSection: () => SidebarSection,
96
+ Slider: () => Slider,
64
97
  SortableTable: () => SortableTable,
98
+ SortableTableGhost: () => SortableTableGhost,
99
+ TableSearch: () => TableSearch,
65
100
  Tabs: () => Tabs,
101
+ TabsGhost: () => TabsGhost,
102
+ TextButton: () => TextButton,
103
+ TextButtonGhost: () => TextButtonGhost,
66
104
  Textarea: () => Textarea,
105
+ TextareaGhost: () => TextareaGhost,
106
+ Toast: () => Toast,
107
+ ToastViewport: () => ToastViewport,
67
108
  Toggle: () => Toggle,
109
+ ToggleGhost: () => ToggleGhost,
110
+ Tooltip: () => Tooltip,
111
+ UILoader: () => UILoader,
68
112
  cn: () => cn,
69
113
  useEscapeDismiss: () => useEscapeDismiss
70
114
  });
71
- module.exports = __toCommonJS(index_exports);
115
+ module.exports = __toCommonJS(src_exports);
72
116
 
73
117
  // src/components/Button/Button.tsx
74
118
  var React = __toESM(require("react"), 1);
@@ -119,51 +163,34 @@ var colorPalette = {
119
163
  700: "var(--green-700)"
120
164
  }
121
165
  };
122
- var sizeConfig = {
123
- xs: { height: "28px", padding: "4px 8px", iconOnly: "28px", fontSize: "0.75rem" },
124
- sm: { height: "32px", padding: "6px 12px", iconOnly: "32px", fontSize: "0.75rem" },
125
- md: { height: "40px", padding: "8px 16px", iconOnly: "40px", fontSize: "0.875rem" },
126
- lg: { height: "44px", padding: "10px 20px", iconOnly: "44px", fontSize: "1rem" },
127
- xl: { height: "56px", padding: "14px 28px", iconOnly: "56px", fontSize: "1rem" }
128
- };
129
- function getButtonStyles(buttonType, isHovered, isActive, buttonSize, disabled, color, hasIconOnly) {
130
- const palette = colorPalette[color];
131
- const size = sizeConfig[buttonSize];
166
+ function getButtonStyles(buttonType, isHovered, isActive, disabled, color, hasIconOnly) {
167
+ const palette = color ? colorPalette[color] : void 0;
132
168
  const base = {
133
- height: size.height,
134
- padding: hasIconOnly ? "0" : size.padding,
135
- borderRadius: hasIconOnly ? "var(--radius-lg, 8px)" : "var(--radius-full, 9999px)",
169
+ borderRadius: "var(--radius-sm, 4px)",
136
170
  cursor: disabled ? "not-allowed" : "pointer",
137
- fontSize: size.fontSize,
138
171
  transition: "background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease"
139
172
  };
140
173
  if (hasIconOnly) {
141
- base.width = size.iconOnly;
142
174
  base.justifyContent = "center";
143
175
  }
144
176
  if (disabled) {
145
177
  if (buttonType === "ghost") {
146
- return { ...base, backgroundColor: "transparent", border: "none", color: "var(--fg-disabled)" };
178
+ return { ...base, backgroundColor: "transparent", border: "1px solid transparent", color: "var(--fg-disabled)" };
147
179
  }
148
180
  return { ...base, backgroundColor: "transparent", border: "1px solid var(--fg-disabled)", color: "var(--fg-disabled)" };
149
181
  }
150
182
  switch (buttonType) {
151
- // Primary with text: dark pill. Icon-only: color palette (Synapse style).
183
+ // Text and icon-only primary actions inherit the same scoped theme.
152
184
  case "primary":
153
- if (hasIconOnly) {
154
- return {
155
- ...base,
156
- backgroundColor: isActive ? palette[700] : isHovered ? palette[500] : palette[600],
157
- border: "none",
158
- color: "var(--fg-inverse)"
159
- };
160
- }
161
185
  return {
162
186
  ...base,
163
- backgroundColor: "var(--fg-primary)",
164
- border: "1px solid var(--fg-primary)",
165
- color: "var(--fg-inverse)",
166
- ...isHovered || isActive ? { opacity: 0.85 } : {}
187
+ border: "1px solid transparent",
188
+ ...palette ? {
189
+ "--button-primary-bg": palette[600],
190
+ "--button-primary-hover": palette[500],
191
+ "--button-primary-active": palette[700],
192
+ "--button-primary-fg": "var(--grey-0)"
193
+ } : {}
167
194
  };
168
195
  // Secondary: neutral. Rest=canvas, hover=surface-1 (darken), pressed=surface-2. Subtle border, dark text.
169
196
  case "secondary":
@@ -178,7 +205,7 @@ function getButtonStyles(buttonType, isHovered, isActive, buttonSize, disabled,
178
205
  return {
179
206
  ...base,
180
207
  backgroundColor: isHovered || isActive ? "var(--bg-surface-1)" : "transparent",
181
- border: "none",
208
+ border: "1px solid transparent",
182
209
  color: "var(--fg-primary)"
183
210
  };
184
211
  // Danger: low-intensity destructive action. Text stays steady; hover deepens the light red surface.
@@ -186,7 +213,7 @@ function getButtonStyles(buttonType, isHovered, isActive, buttonSize, disabled,
186
213
  return {
187
214
  ...base,
188
215
  backgroundColor: isHovered || isActive ? "var(--danger-button-surface-hover)" : "var(--danger-button-surface)",
189
- border: "none",
216
+ border: "1px solid transparent",
190
217
  color: "var(--danger-button-fg)"
191
218
  };
192
219
  default:
@@ -194,10 +221,10 @@ function getButtonStyles(buttonType, isHovered, isActive, buttonSize, disabled,
194
221
  }
195
222
  }
196
223
  var Button = React.forwardRef(
197
- ({ className, buttonType = "primary", buttonSize = "md", color = "blue", icon, hasIconOnly, iconPosition = "left", style, disabled, children, ...props }, ref) => {
224
+ ({ className, buttonType = "primary", buttonSize = "lg", controlSize, color, icon, shortcut, hasIconOnly, iconPosition = "left", style, disabled, children, ...props }, ref) => {
198
225
  const [isHovered, setIsHovered] = React.useState(false);
199
226
  const [isActive, setIsActive] = React.useState(false);
200
- const typeStyles = getButtonStyles(buttonType, isHovered, isActive, buttonSize, !!disabled, color, hasIconOnly);
227
+ const typeStyles = getButtonStyles(buttonType, isHovered, isActive, !!disabled, color, hasIconOnly);
201
228
  const hasIconAndText = icon && children && !hasIconOnly;
202
229
  const finalStyles = hasIconAndText ? { ...typeStyles, gap: "var(--space-2, 8px)", ...style } : { ...typeStyles, ...style };
203
230
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -205,7 +232,9 @@ var Button = React.forwardRef(
205
232
  {
206
233
  ref,
207
234
  type: "button",
208
- className: cn("fractal-button", `fractal-button--${buttonType}`, className),
235
+ "data-control-size": controlSize ?? buttonSize,
236
+ "data-icon-only": hasIconOnly || void 0,
237
+ className: cn("fractal-control", "fractal-button", `fractal-button--${buttonType}`, className),
209
238
  style: finalStyles,
210
239
  disabled,
211
240
  onMouseEnter: (e) => {
@@ -229,7 +258,8 @@ var Button = React.forwardRef(
229
258
  children: [
230
259
  iconPosition !== "right" && icon,
231
260
  !hasIconOnly && children,
232
- iconPosition === "right" && icon
261
+ iconPosition === "right" && icon,
262
+ !hasIconOnly && shortcut != null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "fractal-button__shortcut", "aria-hidden": "true", children: shortcut })
233
263
  ]
234
264
  }
235
265
  );
@@ -237,44 +267,409 @@ var Button = React.forwardRef(
237
267
  );
238
268
  Button.displayName = "Button";
239
269
 
240
- // src/components/Card/Card.tsx
241
- var React2 = __toESM(require("react"), 1);
270
+ // src/components/CodeBlock/CodeBlock.tsx
271
+ var import_react = require("react");
272
+ var import_react_codemirror = __toESM(require("@uiw/react-codemirror"), 1);
273
+ var import_language = require("@codemirror/language");
274
+ var import_lang_json = require("@codemirror/lang-json");
275
+ var import_view = require("@codemirror/view");
276
+ var import_highlight = require("@lezer/highlight");
242
277
  var import_fractal_icons = require("@mirror-physics/fractal-icons");
243
278
  var import_jsx_runtime2 = require("react/jsx-runtime");
279
+ var codeTheme = import_view.EditorView.theme({
280
+ "&": {
281
+ backgroundColor: "transparent",
282
+ color: "var(--fg-primary)",
283
+ fontSize: "0.75rem"
284
+ },
285
+ "&.cm-focused": { outline: "none" },
286
+ ".cm-scroller": {
287
+ fontFamily: "var(--font-mono)",
288
+ lineHeight: "1.55",
289
+ overscrollBehavior: "none"
290
+ },
291
+ ".cm-content": { padding: "10px 0" },
292
+ ".cm-line": {
293
+ padding: "0 12px 0 calc(12px + var(--fractal-code-wrap-indent, 0ch))",
294
+ textIndent: "calc(0px - var(--fractal-code-wrap-indent, 0ch))"
295
+ },
296
+ ".cm-cursor, .cm-dropCursor": { borderLeftColor: "var(--fg-primary)" },
297
+ ".cm-gutters": {
298
+ backgroundColor: "var(--bg-canvas)",
299
+ borderRight: "1px solid var(--border-subtle)",
300
+ color: "var(--fg-tertiary)"
301
+ },
302
+ ".cm-lineNumbers .cm-gutterElement": { padding: "0 10px 0 4px" },
303
+ ".cm-foldGutter": { width: "14px" },
304
+ ".cm-foldGutter .cm-gutterElement": {
305
+ position: "relative",
306
+ display: "block",
307
+ width: "14px",
308
+ padding: "0"
309
+ },
310
+ ".cm-foldGutter .cm-gutterElement > span": {
311
+ position: "absolute",
312
+ top: "2px",
313
+ left: "0",
314
+ display: "inline-flex",
315
+ width: "14px",
316
+ height: "14px",
317
+ alignItems: "center",
318
+ justifyContent: "center",
319
+ padding: "0",
320
+ color: "var(--fg-tertiary)",
321
+ lineHeight: "14px",
322
+ transition: "color 120ms ease"
323
+ },
324
+ '.cm-foldGutter .cm-gutterElement > span[title="Fold line"]': { transform: "translateY(-3px)" },
325
+ ".cm-foldGutter .cm-gutterElement > span:hover": { color: "var(--fg-secondary)" },
326
+ ".fractal-code-block__escape": { color: "var(--fg-secondary)" },
327
+ ".cm-activeLine, .cm-activeLineGutter": { backgroundColor: "var(--bg-surface-1)" },
328
+ ".cm-selectionBackground, &.cm-focused .cm-selectionBackground, ::selection": {
329
+ backgroundColor: "var(--accent-surface-stroke) !important"
330
+ },
331
+ ".cm-searchMatch": {
332
+ backgroundColor: "var(--warning-surface)",
333
+ outline: "1px solid var(--warning-surface-stroke)"
334
+ },
335
+ ".cm-searchMatch.cm-searchMatch-selected": { backgroundColor: "var(--warning-subtle)" },
336
+ ".cm-panels": { backgroundColor: "var(--bg-surface-1)", color: "var(--fg-primary)" },
337
+ ".cm-tooltip": {
338
+ borderColor: "var(--border-subtle)",
339
+ backgroundColor: "var(--bg-canvas)",
340
+ color: "var(--fg-primary)"
341
+ }
342
+ });
343
+ var fractalHighlightStyle = import_language.HighlightStyle.define([
344
+ { tag: import_highlight.tags.comment, color: "var(--fg-tertiary)", fontStyle: "italic" },
345
+ { tag: [import_highlight.tags.keyword, import_highlight.tags.controlKeyword, import_highlight.tags.operatorKeyword], color: "var(--purple-default)" },
346
+ { tag: [import_highlight.tags.string, import_highlight.tags.special(import_highlight.tags.string)], color: "var(--purple-default)" },
347
+ { tag: [import_highlight.tags.number, import_highlight.tags.bool, import_highlight.tags.null], color: "var(--orange-default)" },
348
+ { tag: [import_highlight.tags.function(import_highlight.tags.variableName), import_highlight.tags.labelName], color: "var(--accent-default)" },
349
+ { tag: [import_highlight.tags.typeName, import_highlight.tags.className, import_highlight.tags.namespace], color: "var(--pink-default)" },
350
+ { tag: [import_highlight.tags.propertyName, import_highlight.tags.attributeName], color: "var(--success-default)" },
351
+ { tag: [import_highlight.tags.operator, import_highlight.tags.punctuation], color: "var(--fg-secondary)" },
352
+ { tag: import_highlight.tags.bracket, color: "var(--accent-default)" },
353
+ { tag: [import_highlight.tags.heading, import_highlight.tags.strong], color: "var(--fg-primary)", fontWeight: "600" },
354
+ { tag: import_highlight.tags.emphasis, fontStyle: "italic" },
355
+ { tag: import_highlight.tags.link, color: "var(--accent-default)", textDecoration: "underline" },
356
+ { tag: import_highlight.tags.invalid, color: "var(--error-default)", textDecoration: "underline wavy" }
357
+ ]);
358
+ var jsonSupport = (0, import_lang_json.json)();
359
+ var JSON_LANGUAGE_NAMES = /* @__PURE__ */ new Set(["json", "jsonl", "ndjson", "geojson"]);
360
+ var JSON_LINES_LANGUAGE_NAMES = /* @__PURE__ */ new Set(["jsonl", "ndjson"]);
361
+ var JSON_LINES_PRETTY_PRINT_THRESHOLD = 160;
362
+ var JSON_PROPERTY_PREFIX = /^(\s*"(?:\\.|[^"\\])*"\s*:\s*)/;
363
+ function countVisualColumns(value) {
364
+ let columns = 0;
365
+ for (const character of value) columns += character === " " ? 2 : 1;
366
+ return columns;
367
+ }
368
+ function getWrappedLineIndent(line) {
369
+ const leadingWhitespace = line.match(/^\s*/)?.[0] ?? "";
370
+ if (JSON_PROPERTY_PREFIX.test(line)) return Math.min(countVisualColumns(leadingWhitespace), 24);
371
+ return Math.min(countVisualColumns(leadingWhitespace) + 2, 12);
372
+ }
373
+ function buildWrappedLineIndentation(view) {
374
+ const decorations = [];
375
+ const decoratedLines = /* @__PURE__ */ new Set();
376
+ for (const visibleRange of view.visibleRanges) {
377
+ let position = visibleRange.from;
378
+ while (position <= visibleRange.to) {
379
+ const line = view.state.doc.lineAt(position);
380
+ if (!decoratedLines.has(line.from)) {
381
+ const indent = getWrappedLineIndent(line.text);
382
+ decorations.push(import_view.Decoration.line({
383
+ attributes: { style: `--fractal-code-wrap-indent: ${indent}ch` }
384
+ }).range(line.from));
385
+ decoratedLines.add(line.from);
386
+ }
387
+ if (line.to >= visibleRange.to) break;
388
+ position = line.to + 1;
389
+ }
390
+ }
391
+ return import_view.Decoration.set(decorations, true);
392
+ }
393
+ var wrappedLineIndentation = import_view.ViewPlugin.fromClass(class {
394
+ constructor(view) {
395
+ this.decorations = buildWrappedLineIndentation(view);
396
+ }
397
+ update(update) {
398
+ this.decorations = buildWrappedLineIndentation(update.view);
399
+ }
400
+ }, {
401
+ decorations: (plugin) => plugin.decorations
402
+ });
403
+ var jsonEscapeMatcher = new import_view.MatchDecorator({
404
+ regexp: /\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4})/g,
405
+ decoration: import_view.Decoration.mark({ class: "fractal-code-block__escape" })
406
+ });
407
+ var jsonEscapeHighlighting = import_view.ViewPlugin.fromClass(class {
408
+ constructor(view) {
409
+ this.decorations = jsonEscapeMatcher.createDeco(view);
410
+ }
411
+ update(update) {
412
+ this.decorations = jsonEscapeMatcher.updateDeco(update, this.decorations);
413
+ }
414
+ }, {
415
+ decorations: (plugin) => plugin.decorations
416
+ });
417
+ function formatJsonLinesForDisplay(value) {
418
+ return value.split(/\r?\n/).map((line) => {
419
+ const firstContentIndex = line.search(/\S/);
420
+ if (firstContentIndex < 0 || line.length - firstContentIndex <= JSON_LINES_PRETTY_PRINT_THRESHOLD) return line;
421
+ const indentation = line.slice(0, firstContentIndex);
422
+ try {
423
+ const parsed = JSON.parse(line.slice(firstContentIndex));
424
+ if (parsed === null || typeof parsed !== "object") return line;
425
+ return JSON.stringify(parsed, null, 2).split("\n").map((formattedLine) => `${indentation}${formattedLine}`).join("\n");
426
+ } catch {
427
+ return line;
428
+ }
429
+ }).join("\n");
430
+ }
431
+ function findJsonContainerEnd(document2, start) {
432
+ const openingCharacter = document2[start];
433
+ if (openingCharacter !== "{" && openingCharacter !== "[") return null;
434
+ const closingCharacters = [];
435
+ let isInsideString = false;
436
+ let isEscaped = false;
437
+ for (let index = start; index < document2.length; index += 1) {
438
+ const character = document2[index];
439
+ if (isInsideString) {
440
+ if (isEscaped) {
441
+ isEscaped = false;
442
+ } else if (character === "\\") {
443
+ isEscaped = true;
444
+ } else if (character === '"') {
445
+ isInsideString = false;
446
+ }
447
+ continue;
448
+ }
449
+ if (character === '"') {
450
+ isInsideString = true;
451
+ } else if (character === "{") {
452
+ closingCharacters.push("}");
453
+ } else if (character === "[") {
454
+ closingCharacters.push("]");
455
+ } else if (character === "}" || character === "]") {
456
+ if (closingCharacters.pop() !== character) return null;
457
+ if (closingCharacters.length === 0) return index;
458
+ }
459
+ }
460
+ return null;
461
+ }
462
+ var jsonLinesFoldSupport = import_language.foldService.of((state, lineStart, lineEnd) => {
463
+ const line = state.doc.sliceString(lineStart, lineEnd);
464
+ const firstContentIndex = line.search(/\S/);
465
+ if (firstContentIndex < 0) return null;
466
+ const containerStart = lineStart + firstContentIndex;
467
+ const openingCharacter = line[firstContentIndex];
468
+ if (openingCharacter !== "{" && openingCharacter !== "[") return null;
469
+ const document2 = state.doc.toString();
470
+ const containerEnd = findJsonContainerEnd(document2, containerStart);
471
+ if (containerEnd === null || containerEnd - containerStart < 2) return null;
472
+ try {
473
+ const parsed = JSON.parse(document2.slice(containerStart, containerEnd + 1));
474
+ if (parsed === null || typeof parsed !== "object") return null;
475
+ } catch {
476
+ return null;
477
+ }
478
+ return {
479
+ from: containerStart + 1,
480
+ to: containerEnd
481
+ };
482
+ });
483
+ function resolveLanguage(language, fileName) {
484
+ const normalized = language?.trim().toLowerCase();
485
+ const extension = fileName?.split(/[?#]/, 1)[0].split(".").pop()?.toLowerCase();
486
+ if (normalized && JSON_LANGUAGE_NAMES.has(normalized) || extension && JSON_LANGUAGE_NAMES.has(extension)) {
487
+ const isJsonLines = Boolean(
488
+ normalized && JSON_LINES_LANGUAGE_NAMES.has(normalized) || extension && JSON_LINES_LANGUAGE_NAMES.has(extension)
489
+ );
490
+ return { label: isJsonLines ? "JSONL" : "JSON", support: jsonSupport, isJsonLines };
491
+ }
492
+ if (normalized && !["text", "txt", "plaintext", "plain"].includes(normalized)) {
493
+ return { label: language?.trim(), support: null, isJsonLines: false };
494
+ }
495
+ return { support: null, isJsonLines: false };
496
+ }
497
+ function CodeBlock({
498
+ value,
499
+ language,
500
+ fileName,
501
+ label,
502
+ editable = false,
503
+ onChange,
504
+ lineNumbers = true,
505
+ wrap = true,
506
+ copyable = true,
507
+ ariaLabel,
508
+ height,
509
+ minHeight,
510
+ maxHeight = "28rem",
511
+ placeholder,
512
+ autoFocus,
513
+ className,
514
+ ...props
515
+ }) {
516
+ const resolvedLanguage = (0, import_react.useMemo)(() => resolveLanguage(language, fileName), [fileName, language]);
517
+ const displayValue = (0, import_react.useMemo)(
518
+ () => !editable && resolvedLanguage.isJsonLines ? formatJsonLinesForDisplay(value) : value,
519
+ [editable, resolvedLanguage.isJsonLines, value]
520
+ );
521
+ const [copyStatus, setCopyStatus] = (0, import_react.useState)("idle");
522
+ const copyResetTimer = (0, import_react.useRef)(null);
523
+ (0, import_react.useEffect)(() => () => {
524
+ if (copyResetTimer.current) clearTimeout(copyResetTimer.current);
525
+ }, []);
526
+ const extensions = (0, import_react.useMemo)(() => [
527
+ codeTheme,
528
+ (0, import_language.syntaxHighlighting)(fractalHighlightStyle),
529
+ ...resolvedLanguage.support ? [resolvedLanguage.support] : [],
530
+ ...resolvedLanguage.support ? [jsonEscapeHighlighting] : [],
531
+ ...resolvedLanguage.isJsonLines ? [jsonLinesFoldSupport] : [],
532
+ ...lineNumbers ? [(0, import_language.foldGutter)(), (0, import_view.lineNumbers)()] : [],
533
+ ...wrap ? [import_view.EditorView.lineWrapping, wrappedLineIndentation] : []
534
+ ], [lineNumbers, resolvedLanguage.isJsonLines, resolvedLanguage.support, wrap]);
535
+ const languageLabel = resolvedLanguage.label;
536
+ const toolbarLabel = label ?? fileName ?? languageLabel;
537
+ const surfaceLabel = ariaLabel ?? (fileName ? `${fileName} ${editable ? "editor" : "viewer"}` : `Code ${editable ? "editor" : "viewer"}`);
538
+ const copyCode = async () => {
539
+ try {
540
+ await navigator.clipboard.writeText(value);
541
+ setCopyStatus("copied");
542
+ } catch {
543
+ setCopyStatus("error");
544
+ }
545
+ if (copyResetTimer.current) clearTimeout(copyResetTimer.current);
546
+ copyResetTimer.current = setTimeout(() => setCopyStatus("idle"), 1800);
547
+ };
548
+ const showToolbar = toolbarLabel !== void 0 || copyable || editable;
549
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
550
+ "div",
551
+ {
552
+ className: cn(
553
+ "fractal-code-block",
554
+ editable && "fractal-code-block--editable",
555
+ !lineNumbers && "fractal-code-block--no-line-numbers",
556
+ className
557
+ ),
558
+ ...props,
559
+ children: [
560
+ showToolbar && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "fractal-code-block__toolbar", children: [
561
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "fractal-code-block__identity", children: [
562
+ toolbarLabel !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fractal-code-block__label", title: typeof toolbarLabel === "string" ? toolbarLabel : void 0, children: toolbarLabel }),
563
+ fileName && languageLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fractal-code-block__language", children: languageLabel }),
564
+ editable && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fractal-code-block__mode", children: "Editing" })
565
+ ] }),
566
+ copyable && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
567
+ "button",
568
+ {
569
+ type: "button",
570
+ className: "fractal-code-block__copy",
571
+ "aria-label": copyStatus === "copied" ? "Code copied" : "Copy code",
572
+ onClick: () => void copyCode(),
573
+ children: [
574
+ copyStatus === "copied" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_fractal_icons.Check, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_fractal_icons.Copy, { size: 14 }),
575
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: copyStatus === "copied" ? "Copied" : copyStatus === "error" ? "Copy failed" : "Copy" })
576
+ ]
577
+ }
578
+ )
579
+ ] }),
580
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "fractal-code-block__surface", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
581
+ import_react_codemirror.default,
582
+ {
583
+ "aria-label": surfaceLabel,
584
+ value: displayValue,
585
+ editable,
586
+ readOnly: !editable,
587
+ onChange,
588
+ extensions,
589
+ theme: "none",
590
+ basicSetup: {
591
+ lineNumbers: false,
592
+ foldGutter: false,
593
+ drawSelection: editable,
594
+ highlightActiveLine: editable,
595
+ highlightActiveLineGutter: editable,
596
+ highlightSelectionMatches: editable,
597
+ autocompletion: editable,
598
+ history: editable
599
+ },
600
+ indentWithTab: editable,
601
+ height,
602
+ minHeight,
603
+ maxHeight,
604
+ placeholder,
605
+ autoFocus
606
+ }
607
+ ) }),
608
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fractal-code-block__status", "aria-live": "polite", children: copyStatus === "copied" ? "Code copied to clipboard." : copyStatus === "error" ? "Code could not be copied." : "" })
609
+ ]
610
+ }
611
+ );
612
+ }
613
+
614
+ // src/components/Breadcrumbs/Breadcrumbs.tsx
615
+ var import_fractal_icons2 = require("@mirror-physics/fractal-icons");
616
+ var import_jsx_runtime3 = require("react/jsx-runtime");
617
+ function Breadcrumbs({
618
+ items,
619
+ ariaLabel = "Breadcrumbs",
620
+ separator = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_fractal_icons2.ChevronRight, { "aria-hidden": "true", size: 14 }),
621
+ className,
622
+ ...props
623
+ }) {
624
+ if (items.length === 0) return null;
625
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("nav", { "aria-label": ariaLabel, className: cn("fractal-breadcrumbs", className), ...props, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ol", { className: "fractal-breadcrumbs__list", children: items.map((item, index) => {
626
+ const isCurrent = index === items.length - 1;
627
+ const content = isCurrent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "aria-current": "page", className: "fractal-breadcrumbs__current", children: item.label }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("a", { className: "fractal-breadcrumbs__link", href: item.href ?? "#", onClick: item.onClick, children: item.label });
628
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "fractal-breadcrumbs__item", children: [
629
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "aria-hidden": "true", className: "fractal-breadcrumbs__separator", children: separator }),
630
+ content
631
+ ] }, index);
632
+ }) }) });
633
+ }
634
+
635
+ // src/components/Card/Card.tsx
636
+ var React2 = __toESM(require("react"), 1);
637
+ var import_fractal_icons3 = require("@mirror-physics/fractal-icons");
638
+ var import_jsx_runtime4 = require("react/jsx-runtime");
244
639
  var Card = React2.forwardRef(
245
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, className: cn("fractal-card", className), ...props })
640
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, "data-fractal-border-surface": "surface-1", className: cn("fractal-card", className), ...props })
246
641
  );
247
642
  Card.displayName = "Card";
248
643
  var CardHeader = React2.forwardRef(
249
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, className: cn("fractal-card-header", className), ...props })
644
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, className: cn("fractal-card-header", className), ...props })
250
645
  );
251
646
  CardHeader.displayName = "CardHeader";
252
647
  var CardTitle = React2.forwardRef(
253
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { ref, className: cn("fractal-card-title", className), ...props })
648
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { ref, className: cn("fractal-card-title", className), ...props })
254
649
  );
255
650
  CardTitle.displayName = "CardTitle";
256
651
  var CardDescription = React2.forwardRef(
257
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { ref, className: cn("fractal-card-description", className), ...props })
652
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { ref, className: cn("fractal-card-description", className), ...props })
258
653
  );
259
654
  CardDescription.displayName = "CardDescription";
260
655
  var CardContent = React2.forwardRef(
261
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, className: cn("fractal-card-content", className), ...props })
656
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, className: cn("fractal-card-content", className), ...props })
262
657
  );
263
658
  CardContent.displayName = "CardContent";
264
659
  var CardFooter = React2.forwardRef(
265
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, className: cn("fractal-card-footer", className), ...props })
660
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, className: cn("fractal-card-footer", className), ...props })
266
661
  );
267
662
  CardFooter.displayName = "CardFooter";
268
663
  var FeatureCard = React2.forwardRef(
269
- ({ icon, title, description, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ref, className: cn("fractal-feature-card", className), ...props, children: [
270
- icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "fractal-feature-card__icon", children: icon }),
271
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "fractal-feature-card__title", children: title }),
272
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "fractal-feature-card__description", children: description })
664
+ ({ icon, title, description, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { ref, className: cn("fractal-feature-card", className), ...props, children: [
665
+ icon && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "fractal-feature-card__icon", children: icon }),
666
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "fractal-feature-card__title", children: title }),
667
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "fractal-feature-card__description", children: description })
273
668
  ] })
274
669
  );
275
670
  FeatureCard.displayName = "FeatureCard";
276
671
  var FolderCard = React2.forwardRef(
277
- ({ name, sessionCount, selected, className, onClick, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
672
+ ({ name, sessionCount, selected, className, onClick, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
278
673
  "div",
279
674
  {
280
675
  ref,
@@ -290,11 +685,11 @@ var FolderCard = React2.forwardRef(
290
685
  className: cn("fractal-folder-card", selected && "fractal-folder-card--selected", className),
291
686
  ...props,
292
687
  children: [
293
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "fractal-folder-card__header", children: [
294
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_fractal_icons.Folder, { size: 16, className: "fractal-folder-card__icon", "aria-hidden": true }),
295
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fractal-folder-card__name", children: name })
688
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "fractal-folder-card__header", children: [
689
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_fractal_icons3.Folder, { size: 16, className: "fractal-folder-card__icon", "aria-hidden": true }),
690
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "fractal-folder-card__name", children: name })
296
691
  ] }),
297
- sessionCount !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "fractal-folder-card__footer", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "fractal-folder-card__sessions", children: [
692
+ sessionCount !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "fractal-folder-card__footer", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "fractal-folder-card__sessions", children: [
298
693
  sessionCount,
299
694
  " ",
300
695
  sessionCount === 1 ? "session" : "sessions"
@@ -307,7 +702,7 @@ FolderCard.displayName = "FolderCard";
307
702
 
308
703
  // src/components/PromptCard/PromptCard.tsx
309
704
  var React3 = __toESM(require("react"), 1);
310
- var import_jsx_runtime3 = require("react/jsx-runtime");
705
+ var import_jsx_runtime5 = require("react/jsx-runtime");
311
706
  var titleToPrompt = (title) => typeof title === "string" || typeof title === "number" ? String(title) : "";
312
707
  var PromptCard = React3.forwardRef(
313
708
  ({
@@ -328,7 +723,7 @@ var PromptCard = React3.forwardRef(
328
723
  onPromptSelect?.(prompt ?? titleToPrompt(title));
329
724
  }
330
725
  };
331
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
726
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
332
727
  "button",
333
728
  {
334
729
  ref,
@@ -343,8 +738,8 @@ var PromptCard = React3.forwardRef(
343
738
  onClick: handleClick,
344
739
  ...props,
345
740
  children: [
346
- icon && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fractal-prompt-card__icon", "aria-hidden": "true", children: icon }),
347
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fractal-prompt-card__title", children: title })
741
+ icon && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "fractal-prompt-card__icon", "aria-hidden": "true", children: icon }),
742
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "fractal-prompt-card__title", children: title })
348
743
  ]
349
744
  }
350
745
  );
@@ -352,7 +747,7 @@ var PromptCard = React3.forwardRef(
352
747
  );
353
748
  PromptCard.displayName = "PromptCard";
354
749
  var PromptGrid = React3.forwardRef(
355
- ({ compact, padded, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
750
+ ({ compact, padded, className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
356
751
  "div",
357
752
  {
358
753
  ref,
@@ -370,28 +765,364 @@ PromptGrid.displayName = "PromptGrid";
370
765
 
371
766
  // src/components/Input/Input.tsx
372
767
  var React4 = __toESM(require("react"), 1);
373
- var import_jsx_runtime4 = require("react/jsx-runtime");
768
+
769
+ // src/utils/FloatingFieldBorder.tsx
770
+ var import_jsx_runtime6 = require("react/jsx-runtime");
771
+ function FloatingFieldBorder({ label }) {
772
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("fieldset", { className: "fractal-floating-field__border", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("legend", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: label }) }) });
773
+ }
774
+
775
+ // src/components/Input/Input.tsx
776
+ var import_jsx_runtime7 = require("react/jsx-runtime");
374
777
  var Input = React4.forwardRef(
375
- ({ className, type, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
376
- "input",
778
+ ({ className, type, controlSize = "lg", fieldLabel, id, placeholder, ...props }, ref) => {
779
+ const generatedId = React4.useId();
780
+ const fieldId = id ?? generatedId;
781
+ const floatingLabel = type === "search" ? void 0 : fieldLabel;
782
+ const input = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
783
+ "input",
784
+ {
785
+ type,
786
+ ref,
787
+ id: floatingLabel ? fieldId : id,
788
+ placeholder: floatingLabel ? placeholder || " " : placeholder,
789
+ "data-control-size": controlSize,
790
+ className: cn("fractal-control", "fractal-input", className),
791
+ ...props,
792
+ "aria-label": props["aria-label"] ?? (type === "search" && !props["aria-labelledby"] ? fieldLabel ?? "Search" : void 0)
793
+ }
794
+ );
795
+ if (!floatingLabel) return input;
796
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
797
+ "div",
798
+ {
799
+ className: "fractal-floating-field",
800
+ "data-control-size": controlSize,
801
+ "data-always-float": ["date", "datetime-local", "time", "month", "week", "color", "file", "range"].includes(type ?? "") || void 0,
802
+ children: [
803
+ input,
804
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(FloatingFieldBorder, { label: fieldLabel ?? "" }),
805
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "fractal-floating-field__label", htmlFor: fieldId, children: fieldLabel })
806
+ ]
807
+ }
808
+ );
809
+ }
810
+ );
811
+ Input.displayName = "Input";
812
+
813
+ // src/components/TableSearch/TableSearch.tsx
814
+ var React5 = __toESM(require("react"), 1);
815
+ var import_fractal_icons4 = require("@mirror-physics/fractal-icons");
816
+ var import_jsx_runtime8 = require("react/jsx-runtime");
817
+ var TableSearch = React5.forwardRef(
818
+ ({
819
+ searchType = "text",
820
+ controlSize = "lg",
821
+ className,
822
+ inputClassName,
823
+ placeholder,
824
+ title,
825
+ ...props
826
+ }, ref) => {
827
+ const isGlob = searchType === "glob";
828
+ const SearchIcon = isGlob ? import_fractal_icons4.MagnifyingGlassLines : import_fractal_icons4.MagnifyingGlass;
829
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
830
+ "div",
831
+ {
832
+ className: cn("fractal-table-search", className),
833
+ "data-search-type": searchType,
834
+ "data-control-size": controlSize,
835
+ children: [
836
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SearchIcon, { className: "fractal-table-search__icon", size: 17, "aria-hidden": "true" }),
837
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
838
+ Input,
839
+ {
840
+ ...props,
841
+ controlSize,
842
+ ref,
843
+ type: "search",
844
+ className: cn("fractal-table-search__input", inputClassName),
845
+ placeholder: placeholder ?? (isGlob ? "Glob search \u2014 use *, ?, or [a-z]" : "Search"),
846
+ title: title ?? (isGlob ? "Glob pattern: *, **, ?, [abc], [a-z], and [!abc]" : void 0)
847
+ }
848
+ )
849
+ ]
850
+ }
851
+ );
852
+ }
853
+ );
854
+ TableSearch.displayName = "TableSearch";
855
+
856
+ // src/components/NumberInput/NumberInput.tsx
857
+ var React6 = __toESM(require("react"), 1);
858
+ var import_fractal_icons5 = require("@mirror-physics/fractal-icons");
859
+ var import_jsx_runtime9 = require("react/jsx-runtime");
860
+ function NumberInput({
861
+ unit,
862
+ controlSize = "lg",
863
+ className,
864
+ value,
865
+ defaultValue,
866
+ disabled,
867
+ readOnly,
868
+ min,
869
+ max,
870
+ ...props
871
+ }) {
872
+ const inputRef = React6.useRef(null);
873
+ const numericValue = Number(value ?? defaultValue);
874
+ const characterCount = Math.max(String(value ?? defaultValue ?? "").length, 1);
875
+ const controlLabel = typeof props["aria-label"] === "string" ? props["aria-label"] : props.fieldLabel ?? "value";
876
+ const decrementDisabled = disabled || readOnly || Number.isFinite(numericValue) && min !== void 0 && numericValue <= Number(min);
877
+ const incrementDisabled = disabled || readOnly || Number.isFinite(numericValue) && max !== void 0 && numericValue >= Number(max);
878
+ const changeByStep = (direction) => {
879
+ const input = inputRef.current;
880
+ if (!input) return;
881
+ if (direction === -1) input.stepDown();
882
+ else input.stepUp();
883
+ input.dispatchEvent(new Event("input", { bubbles: true }));
884
+ input.focus();
885
+ };
886
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { "data-control-size": controlSize, className: cn("fractal-number-input", unit && "fractal-number-input--with-unit", className), children: [
887
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
888
+ Input,
889
+ {
890
+ ref: inputRef,
891
+ controlSize,
892
+ type: "number",
893
+ value,
894
+ defaultValue,
895
+ disabled,
896
+ readOnly,
897
+ min,
898
+ max,
899
+ ...props
900
+ }
901
+ ),
902
+ unit && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
903
+ "span",
904
+ {
905
+ className: "fractal-number-input-unit",
906
+ style: { left: `calc(var(--control-padding-inline) + ${characterCount}ch)` },
907
+ "aria-hidden": "true",
908
+ children: unit
909
+ }
910
+ ),
911
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "fractal-number-input-controls", children: [
912
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
913
+ "button",
914
+ {
915
+ type: "button",
916
+ className: "fractal-number-input-step",
917
+ "aria-label": `Decrease ${controlLabel}`,
918
+ disabled: decrementDisabled,
919
+ onClick: () => changeByStep(-1),
920
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_fractal_icons5.Minus, { "aria-hidden": "true", size: 16 })
921
+ }
922
+ ),
923
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
924
+ "button",
925
+ {
926
+ type: "button",
927
+ className: "fractal-number-input-step",
928
+ "aria-label": `Increase ${controlLabel}`,
929
+ disabled: incrementDisabled,
930
+ onClick: () => changeByStep(1),
931
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_fractal_icons5.Plus, { "aria-hidden": "true", size: 16 })
932
+ }
933
+ )
934
+ ] })
935
+ ] });
936
+ }
937
+
938
+ // src/components/Slider/Slider.tsx
939
+ var import_jsx_runtime10 = require("react/jsx-runtime");
940
+ function Slider(props) {
941
+ const {
942
+ min = 0,
943
+ max = 100,
944
+ step = 1,
945
+ disabled = false,
946
+ className,
947
+ showValues = true,
948
+ formatValue = (value) => value,
949
+ knobs = 1
950
+ } = props;
951
+ const span = Math.max(max - min, 1);
952
+ const rawValues = knobs === 2 ? props.value : [props.value];
953
+ const firstValue = clamp(rawValues[0], min, max);
954
+ const secondValue = knobs === 2 ? clamp(rawValues[1], firstValue, max) : firstValue;
955
+ const selectionStart = knobs === 2 ? firstValue : min;
956
+ const selectionEnd = secondValue;
957
+ const selectionStartPercent = (selectionStart - min) / span * 100;
958
+ const selectionEndPercent = (selectionEnd - min) / span * 100;
959
+ const changeSingleValue = (value) => {
960
+ ;
961
+ props.onValueChange(clamp(value, min, max));
962
+ };
963
+ const changeRangeValue = (nextLower, nextUpper) => {
964
+ ;
965
+ props.onValueChange([
966
+ clamp(nextLower, min, nextUpper),
967
+ clamp(nextUpper, nextLower, max)
968
+ ]);
969
+ };
970
+ const getKeyboardValue = (event, value) => {
971
+ const pageStep = step * 10;
972
+ const adjustments = {
973
+ ArrowDown: -step,
974
+ ArrowLeft: -step,
975
+ ArrowRight: step,
976
+ ArrowUp: step,
977
+ PageDown: -pageStep,
978
+ PageUp: pageStep
979
+ };
980
+ if (event.key === "Home") return min;
981
+ if (event.key === "End") return max;
982
+ if (!(event.key in adjustments)) return null;
983
+ return value + adjustments[event.key];
984
+ };
985
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
986
+ "div",
377
987
  {
378
- type,
379
- ref,
380
- className: cn("fractal-input", className),
381
- ...props
988
+ className: cn("fractal-slider", disabled && "fractal-slider--disabled", className),
989
+ "data-disabled": disabled || void 0,
990
+ "data-knobs": knobs,
991
+ children: [
992
+ showValues && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "fractal-slider-values", "aria-hidden": "true", children: [
993
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("output", { children: formatValue(firstValue) }),
994
+ knobs === 2 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("output", { children: formatValue(secondValue) })
995
+ ] }),
996
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
997
+ "div",
998
+ {
999
+ className: "fractal-slider-control",
1000
+ style: {
1001
+ "--fractal-slider-selection-start": `${selectionStartPercent}%`,
1002
+ "--fractal-slider-selection-end": `${selectionEndPercent}%`
1003
+ },
1004
+ children: [
1005
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fractal-slider-track", "aria-hidden": "true" }),
1006
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fractal-slider-selection", "aria-hidden": "true" }),
1007
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1008
+ "input",
1009
+ {
1010
+ "aria-label": knobs === 2 ? props.lowerAriaLabel ?? "Lower value" : props.ariaLabel ?? "Slider value",
1011
+ disabled,
1012
+ max,
1013
+ min,
1014
+ onChange: (event) => {
1015
+ const nextValue = event.currentTarget.valueAsNumber;
1016
+ if (knobs === 2) changeRangeValue(Math.min(nextValue, secondValue), secondValue);
1017
+ else changeSingleValue(nextValue);
1018
+ },
1019
+ onKeyDown: (event) => {
1020
+ const nextValue = getKeyboardValue(event, firstValue);
1021
+ if (nextValue === null) return;
1022
+ event.preventDefault();
1023
+ if (knobs === 2) changeRangeValue(Math.min(nextValue, secondValue), secondValue);
1024
+ else changeSingleValue(nextValue);
1025
+ },
1026
+ step,
1027
+ type: "range",
1028
+ value: firstValue
1029
+ }
1030
+ ),
1031
+ knobs === 2 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1032
+ "input",
1033
+ {
1034
+ "aria-label": props.upperAriaLabel ?? "Upper value",
1035
+ disabled,
1036
+ max,
1037
+ min,
1038
+ onChange: (event) => {
1039
+ const nextValue = event.currentTarget.valueAsNumber;
1040
+ changeRangeValue(firstValue, Math.max(nextValue, firstValue));
1041
+ },
1042
+ onKeyDown: (event) => {
1043
+ const nextValue = getKeyboardValue(event, secondValue);
1044
+ if (nextValue === null) return;
1045
+ event.preventDefault();
1046
+ changeRangeValue(firstValue, Math.max(nextValue, firstValue));
1047
+ },
1048
+ step,
1049
+ type: "range",
1050
+ value: secondValue
1051
+ }
1052
+ )
1053
+ ]
1054
+ }
1055
+ )
1056
+ ]
382
1057
  }
383
- )
1058
+ );
1059
+ }
1060
+ function clamp(value, minimum, maximum) {
1061
+ return Math.min(maximum, Math.max(minimum, value));
1062
+ }
1063
+
1064
+ // src/components/PasswordInput/PasswordInput.tsx
1065
+ var React7 = __toESM(require("react"), 1);
1066
+ var import_fractal_icons6 = require("@mirror-physics/fractal-icons");
1067
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1068
+ var PasswordInput = React7.forwardRef(
1069
+ ({
1070
+ className,
1071
+ controlSize = "lg",
1072
+ disabled,
1073
+ showPasswordLabel = "Show password",
1074
+ hidePasswordLabel = "Hide password",
1075
+ ...props
1076
+ }, ref) => {
1077
+ const [passwordVisible, setPasswordVisible] = React7.useState(false);
1078
+ const toggleLabel = passwordVisible ? hidePasswordLabel : showPasswordLabel;
1079
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1080
+ "div",
1081
+ {
1082
+ className: cn("fractal-password-input", className),
1083
+ "data-control-size": controlSize,
1084
+ "data-password-visible": passwordVisible ? "" : void 0,
1085
+ children: [
1086
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1087
+ Input,
1088
+ {
1089
+ ...props,
1090
+ controlSize,
1091
+ ref,
1092
+ className: "fractal-password-input-field",
1093
+ disabled,
1094
+ type: passwordVisible ? "text" : "password"
1095
+ }
1096
+ ),
1097
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1098
+ "button",
1099
+ {
1100
+ type: "button",
1101
+ className: "fractal-password-input-toggle",
1102
+ "aria-label": toggleLabel,
1103
+ "aria-pressed": passwordVisible,
1104
+ disabled,
1105
+ title: toggleLabel,
1106
+ onClick: () => setPasswordVisible((visible) => !visible),
1107
+ children: passwordVisible ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_fractal_icons6.EyeSlash, { "aria-hidden": "true", size: 18 }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_fractal_icons6.Eye, { "aria-hidden": "true", size: 18 })
1108
+ }
1109
+ )
1110
+ ]
1111
+ }
1112
+ );
1113
+ }
384
1114
  );
385
- Input.displayName = "Input";
1115
+ PasswordInput.displayName = "PasswordInput";
386
1116
 
387
1117
  // src/components/Label/Label.tsx
388
- var React5 = __toESM(require("react"), 1);
389
- var import_jsx_runtime5 = require("react/jsx-runtime");
390
- var Label = React5.forwardRef(
391
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1118
+ var React8 = __toESM(require("react"), 1);
1119
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1120
+ var Label = React8.forwardRef(
1121
+ ({ className, controlSize = "lg", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
392
1122
  "label",
393
1123
  {
394
1124
  ref,
1125
+ "data-control-size": controlSize,
395
1126
  className: cn("fractal-label", className),
396
1127
  ...props
397
1128
  }
@@ -400,44 +1131,147 @@ var Label = React5.forwardRef(
400
1131
  Label.displayName = "Label";
401
1132
 
402
1133
  // src/components/Alert/Alert.tsx
403
- var React6 = __toESM(require("react"), 1);
404
- var import_jsx_runtime6 = require("react/jsx-runtime");
1134
+ var React9 = __toESM(require("react"), 1);
1135
+ var import_fractal_icons7 = require("@mirror-physics/fractal-icons");
1136
+ var import_jsx_runtime13 = require("react/jsx-runtime");
405
1137
  var defaultIcons = {
406
- default: "\u2139",
407
- info: "\u2139",
408
- success: "\u2713",
409
- warning: "\u26A0",
410
- error: "\u2715"
1138
+ default: import_fractal_icons7.CircleInfo,
1139
+ info: import_fractal_icons7.CircleInfo,
1140
+ success: import_fractal_icons7.CircleCheck,
1141
+ warning: import_fractal_icons7.Warning,
1142
+ error: import_fractal_icons7.CircleClose
411
1143
  };
412
- var Alert = React6.forwardRef(
413
- ({ className, variant = "default", icon, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1144
+ var Alert = React9.forwardRef(
1145
+ ({ className, variant = "default", icon, children, ...props }, ref) => {
1146
+ const DefaultIcon = defaultIcons[variant];
1147
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1148
+ "div",
1149
+ {
1150
+ ref,
1151
+ role: "alert",
1152
+ className: cn("fractal-alert", `fractal-alert--${variant}`, className),
1153
+ ...props,
1154
+ children: [
1155
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "fractal-alert-icon", "aria-hidden": true, children: icon ?? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DefaultIcon, { size: 18 }) }),
1156
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fractal-alert-content", children })
1157
+ ]
1158
+ }
1159
+ );
1160
+ }
1161
+ );
1162
+ Alert.displayName = "Alert";
1163
+ var AlertTitle = React9.forwardRef(
1164
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h5", { ref, className: cn("fractal-alert-title", className), ...props })
1165
+ );
1166
+ AlertTitle.displayName = "AlertTitle";
1167
+ var AlertDescription = React9.forwardRef(
1168
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { ref, className: cn("fractal-alert-description", className), ...props })
1169
+ );
1170
+ AlertDescription.displayName = "AlertDescription";
1171
+
1172
+ // src/components/Toast/Toast.tsx
1173
+ var React10 = __toESM(require("react"), 1);
1174
+ var import_react_dom = require("react-dom");
1175
+ var import_fractal_icons8 = require("@mirror-physics/fractal-icons");
1176
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1177
+ var defaultIcons2 = {
1178
+ default: import_fractal_icons8.CircleInfo,
1179
+ info: import_fractal_icons8.CircleInfo,
1180
+ success: import_fractal_icons8.CircleCheck,
1181
+ warning: import_fractal_icons8.Warning,
1182
+ error: import_fractal_icons8.CircleClose
1183
+ };
1184
+ var Toast = React10.forwardRef(function Toast2({
1185
+ action,
1186
+ children,
1187
+ className,
1188
+ dismissLabel = "Dismiss notification",
1189
+ duration,
1190
+ icon,
1191
+ onBlurCapture,
1192
+ onDismiss,
1193
+ onFocusCapture,
1194
+ onPointerEnter,
1195
+ onPointerLeave,
1196
+ role,
1197
+ variant = "default",
1198
+ ...props
1199
+ }, ref) {
1200
+ const [paused, setPaused] = React10.useState(false);
1201
+ const dismissRef = React10.useRef(onDismiss);
1202
+ const DefaultIcon = defaultIcons2[variant];
1203
+ const resolvedDuration = duration === void 0 ? variant === "error" ? null : 5e3 : duration;
1204
+ React10.useEffect(() => {
1205
+ dismissRef.current = onDismiss;
1206
+ }, [onDismiss]);
1207
+ React10.useEffect(() => {
1208
+ if (!dismissRef.current || paused || resolvedDuration === null || resolvedDuration <= 0) return;
1209
+ const timeout = window.setTimeout(() => dismissRef.current?.(), resolvedDuration);
1210
+ return () => window.clearTimeout(timeout);
1211
+ }, [paused, resolvedDuration]);
1212
+ const announcementRole = role ?? (variant === "error" ? "alert" : "status");
1213
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
414
1214
  "div",
415
1215
  {
416
1216
  ref,
417
- role: "alert",
418
- className: cn("fractal-alert", `fractal-alert--${variant}`, className),
1217
+ "aria-atomic": "true",
1218
+ "aria-live": announcementRole === "alert" ? "assertive" : "polite",
1219
+ className: cn("fractal-toast", `fractal-toast--${variant}`, className),
1220
+ role: announcementRole,
1221
+ onBlurCapture: (event) => {
1222
+ onBlurCapture?.(event);
1223
+ if (!event.currentTarget.contains(event.relatedTarget)) setPaused(false);
1224
+ },
1225
+ onFocusCapture: (event) => {
1226
+ onFocusCapture?.(event);
1227
+ setPaused(true);
1228
+ },
1229
+ onPointerEnter: (event) => {
1230
+ onPointerEnter?.(event);
1231
+ setPaused(true);
1232
+ },
1233
+ onPointerLeave: (event) => {
1234
+ onPointerLeave?.(event);
1235
+ setPaused(false);
1236
+ },
419
1237
  ...props,
420
1238
  children: [
421
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "fractal-alert-icon", "aria-hidden": true, children: icon ?? defaultIcons[variant] }),
422
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "fractal-alert-content", children })
1239
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { "aria-hidden": "true", className: "fractal-toast__icon", children: icon ?? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DefaultIcon, { size: 18 }) }),
1240
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "fractal-toast__content", children }),
1241
+ action && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "fractal-toast__action", children: action }),
1242
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1243
+ "button",
1244
+ {
1245
+ "aria-label": dismissLabel,
1246
+ className: "fractal-toast__dismiss",
1247
+ onClick: onDismiss,
1248
+ type: "button",
1249
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_fractal_icons8.Close, { "aria-hidden": "true", size: 14 })
1250
+ }
1251
+ )
423
1252
  ]
424
1253
  }
425
- )
426
- );
427
- Alert.displayName = "Alert";
428
- var AlertTitle = React6.forwardRef(
429
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h5", { ref, className: cn("fractal-alert-title", className), ...props })
430
- );
431
- AlertTitle.displayName = "AlertTitle";
432
- var AlertDescription = React6.forwardRef(
433
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref, className: cn("fractal-alert-description", className), ...props })
434
- );
435
- AlertDescription.displayName = "AlertDescription";
1254
+ );
1255
+ });
1256
+ var ToastViewport = React10.forwardRef(function ToastViewport2({ children, className, portal = true, position = "top-center", ...props }, ref) {
1257
+ const viewport = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1258
+ "div",
1259
+ {
1260
+ ref,
1261
+ className: cn("fractal-toast-viewport", `fractal-toast-viewport--${position}`, className),
1262
+ "data-position": position,
1263
+ ...props,
1264
+ children
1265
+ }
1266
+ );
1267
+ if (!portal || typeof document === "undefined") return viewport;
1268
+ return (0, import_react_dom.createPortal)(viewport, document.body);
1269
+ });
436
1270
 
437
1271
  // src/components/Toggle/Toggle.tsx
438
- var import_jsx_runtime7 = require("react/jsx-runtime");
1272
+ var import_jsx_runtime15 = require("react/jsx-runtime");
439
1273
  function Toggle({ checked, onChange, label, className }) {
440
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1274
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
441
1275
  "button",
442
1276
  {
443
1277
  type: "button",
@@ -446,18 +1280,19 @@ function Toggle({ checked, onChange, label, className }) {
446
1280
  className: cn("fractal-toggle", className),
447
1281
  onClick: () => onChange(!checked),
448
1282
  children: [
449
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: cn("fractal-toggle-track", checked && "fractal-toggle-track--checked"), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: cn("fractal-toggle-thumb", checked && "fractal-toggle-thumb--checked") }) }),
450
- label && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "fractal-toggle-label", children: label })
1283
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: cn("fractal-toggle-track", checked && "fractal-toggle-track--checked"), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: cn("fractal-toggle-thumb", checked && "fractal-toggle-thumb--checked") }) }),
1284
+ label && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "fractal-toggle-label", children: label })
451
1285
  ]
452
1286
  }
453
1287
  );
454
1288
  }
455
1289
 
456
1290
  // src/components/Modal/Modal.tsx
457
- var import_react_dom = require("react-dom");
1291
+ var React11 = __toESM(require("react"), 1);
1292
+ var import_react_dom2 = require("react-dom");
458
1293
 
459
1294
  // src/hooks/useEscapeDismiss.ts
460
- var import_react = require("react");
1295
+ var import_react2 = require("react");
461
1296
  var nextId = 0;
462
1297
  var stack = [];
463
1298
  function insertSorted(entry) {
@@ -490,9 +1325,9 @@ function ensureGlobalListener() {
490
1325
  );
491
1326
  }
492
1327
  function useEscapeDismiss(priority, handler, enabled = true) {
493
- const handlerRef = (0, import_react.useRef)(handler);
1328
+ const handlerRef = (0, import_react2.useRef)(handler);
494
1329
  handlerRef.current = handler;
495
- (0, import_react.useEffect)(() => {
1330
+ (0, import_react2.useEffect)(() => {
496
1331
  if (!enabled) return;
497
1332
  ensureGlobalListener();
498
1333
  const id = nextId++;
@@ -506,27 +1341,99 @@ function useEscapeDismiss(priority, handler, enabled = true) {
506
1341
  }, [priority, enabled]);
507
1342
  }
508
1343
 
1344
+ // src/components/UILoader/UILoader.tsx
1345
+ var import_react3 = require("react");
1346
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1347
+ var LEFT = "M0 53C0 51 1.6 49.5 3.6 49.5h16.5c1 0 1.9.4 2.5 1L42.1 69.6c.7.7 1.1 1.6 1.1 2.5v16.2c0 2-1.6 3.5-3.6 3.5H23.1c-1 0-1.9-.4-2.5-1L1.1 71.7C.4 71 0 70.1 0 69.2V53Z";
1348
+ var CENTER = "M0 3.5C0 1.6 1.6 0 3.6 0h16.5c1 0 1.9.4 2.5 1L92.5 69.6c.7.7 1 1.6 1 2.5v16.2c0 2-1.6 3.5-3.6 3.5H73.4c-1 0-1.9-.4-2.5-1L1.1 22.2C.4 21.6 0 20.7 0 19.7V3.5Z";
1349
+ var RIGHT = "M50.4 3.5C50.4 1.6 52 0 54 0h16.5c1 0 1.9.4 2.5 1l69.8 68.6c.7.7 1.1 1.6 1.1 2.5v16.2c0 2-1.6 3.5-3.6 3.5h-16.5c-1 0-1.9-.4-2.5-1L51.4 22.2c-.7-.6-1-1.5-1-2.5V3.5Z";
1350
+ function UILoader({ size = 48, tone = "neutral", className, style, label = "Loading", ariaHidden = false }) {
1351
+ const rawId = (0, import_react3.useId)().replace(/[^a-zA-Z0-9_-]/g, "");
1352
+ const clipId = `fractal-ui-loader-clip-${rawId}`;
1353
+ const gradientId = `fractal-ui-loader-gradient-${rawId}`;
1354
+ const height = typeof size === "number" ? `${size}px` : size;
1355
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1356
+ "span",
1357
+ {
1358
+ "aria-hidden": ariaHidden || void 0,
1359
+ "aria-label": ariaHidden ? void 0 : label,
1360
+ className: cn("fractal-ui-loader", `fractal-ui-loader--${tone}`, className),
1361
+ role: ariaHidden ? void 0 : "status",
1362
+ style,
1363
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("svg", { "aria-hidden": "true", fill: "none", viewBox: "0 0 144 92", style: { height, width: "auto" }, children: [
1364
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("defs", { children: [
1365
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("linearGradient", { id: gradientId, x1: "59", x2: "94.3", y1: "0", y2: "89.1", gradientUnits: "userSpaceOnUse", children: [
1366
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("stop", { className: "fractal-ui-loader__stop-start", offset: "0" }),
1367
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("stop", { className: "fractal-ui-loader__stop-end", offset: "1" })
1368
+ ] }),
1369
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("linearGradient", { id: `${gradientId}-shine`, x1: "0", x2: "1", y1: "0", y2: "0", children: [
1370
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("stop", { offset: "0.4", stopColor: "white", stopOpacity: "0" }),
1371
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("stop", { offset: "0.5", stopColor: "white", stopOpacity: "0.62" }),
1372
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("stop", { offset: "0.6", stopColor: "white", stopOpacity: "0" })
1373
+ ] }),
1374
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("clipPath", { id: clipId, children: [
1375
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: LEFT }),
1376
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: CENTER }),
1377
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: RIGHT })
1378
+ ] })
1379
+ ] }),
1380
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: CENTER, fill: `url(#${gradientId})` }),
1381
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: RIGHT, fill: `url(#${gradientId})` }),
1382
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: LEFT, fill: `url(#${gradientId})` }),
1383
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("g", { clipPath: `url(#${clipId})`, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("g", { className: "fractal-ui-loader__shine-axis", transform: "translate(72 46) rotate(68.4)", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("rect", { className: "fractal-ui-loader__shine", x: "-160", y: "-100", width: "320", height: "200", fill: `url(#${gradientId}-shine)` }) }) })
1384
+ ] })
1385
+ }
1386
+ );
1387
+ }
1388
+
509
1389
  // src/components/Modal/Modal.tsx
510
- var import_jsx_runtime8 = require("react/jsx-runtime");
511
- var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 4L4 12M4 4L12 12", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
512
- function Modal({ open, onClose, title, children, danger, warn, footer, noClose, size = "sm", className }) {
513
- useEscapeDismiss(80, onClose, open && !noClose);
1390
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1391
+ var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("path", { d: "M12 4L4 12M4 4L12 12", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
1392
+ function Modal({ open, onClose, title, children, danger, warn, footer, noClose, closeDisabled = false, size = "sm", loading = false, loadingLabel = "Loading", scrollable = false, className }) {
1393
+ const [bodyScrolled, setBodyScrolled] = React11.useState(false);
1394
+ useEscapeDismiss(80, onClose, open && !noClose && !closeDisabled);
1395
+ React11.useEffect(() => {
1396
+ if (open) setBodyScrolled(false);
1397
+ }, [open]);
514
1398
  if (!open) return null;
515
1399
  const sizeClass = `fractal-modal-panel--${size}`;
516
1400
  const accentClass = danger ? "fractal-modal-panel--danger" : warn ? "fractal-modal-panel--warn" : "";
517
- return (0, import_react_dom.createPortal)(
518
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "fractal-modal-overlay", onClick: noClose ? void 0 : onClose, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1401
+ return (0, import_react_dom2.createPortal)(
1402
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "fractal-modal-overlay", onClick: noClose || closeDisabled ? void 0 : onClose, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
519
1403
  "div",
520
1404
  {
521
- className: cn("fractal-modal-panel", sizeClass, accentClass, className),
1405
+ className: cn("fractal-modal-panel", sizeClass, accentClass, scrollable && "fractal-modal-panel--scrollable", className),
522
1406
  onClick: (e) => e.stopPropagation(),
523
1407
  children: [
524
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "fractal-modal-title-row", children: [
525
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: cn("fractal-modal-title", danger && "fractal-modal-title--danger", warn && "fractal-modal-title--warn"), children: title }),
526
- !noClose && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: onClose, title: "Close", className: "fractal-modal-close", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CloseIcon, {}) })
527
- ] }),
528
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cn("fractal-modal-body", size === "lg" && "fractal-modal-body--lg"), children }),
529
- footer && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "fractal-modal-footer", children: footer })
1408
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1409
+ "div",
1410
+ {
1411
+ className: cn(
1412
+ "fractal-modal-title-row",
1413
+ scrollable && "fractal-modal-title-row--scrollable",
1414
+ scrollable && bodyScrolled && "fractal-modal-title-row--scrolled"
1415
+ ),
1416
+ children: [
1417
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h3", { className: cn("fractal-modal-title", danger && "fractal-modal-title--danger", warn && "fractal-modal-title--warn"), children: title }),
1418
+ !noClose && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", onClick: onClose, title: "Close", disabled: closeDisabled, className: "fractal-modal-close", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CloseIcon, {}) })
1419
+ ]
1420
+ }
1421
+ ),
1422
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1423
+ "div",
1424
+ {
1425
+ "aria-busy": loading || void 0,
1426
+ className: cn(
1427
+ "fractal-modal-body",
1428
+ size === "lg" && "fractal-modal-body--lg",
1429
+ loading && "fractal-modal-body--loading",
1430
+ scrollable && "fractal-modal-body--scrollable"
1431
+ ),
1432
+ onScroll: scrollable ? (event) => setBodyScrolled(event.currentTarget.scrollTop > 0) : void 0,
1433
+ children: loading ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(UILoader, { label: loadingLabel }) : children
1434
+ }
1435
+ ),
1436
+ footer && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: cn("fractal-modal-footer", scrollable && "fractal-modal-footer--divided"), children: footer })
530
1437
  ]
531
1438
  }
532
1439
  ) }),
@@ -534,94 +1441,461 @@ function Modal({ open, onClose, title, children, danger, warn, footer, noClose,
534
1441
  );
535
1442
  }
536
1443
 
1444
+ // src/components/TableLayout/TableOverflowMenu.tsx
1445
+ var import_react4 = require("react");
1446
+ var import_react_dom3 = require("react-dom");
1447
+ var import_fractal_icons9 = require("@mirror-physics/fractal-icons");
1448
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1449
+ function TableOverflowMenu({ items, label, disabled, onSelect }) {
1450
+ const id = (0, import_react4.useId)();
1451
+ const trigger = (0, import_react4.useRef)(null);
1452
+ const panel = (0, import_react4.useRef)(null);
1453
+ const initialLast = (0, import_react4.useRef)(false);
1454
+ const [open, setOpen] = (0, import_react4.useState)(false);
1455
+ const [position, setPosition] = (0, import_react4.useState)({ top: 0, left: 0 });
1456
+ const buttons = () => [...panel.current?.querySelectorAll("button:not(:disabled)") ?? []];
1457
+ const close = (restore = true) => {
1458
+ setOpen(false);
1459
+ if (restore) trigger.current?.focus({ preventScroll: true });
1460
+ };
1461
+ useEscapeDismiss(100, () => close(), open);
1462
+ (0, import_react4.useLayoutEffect)(() => {
1463
+ if (!open || !trigger.current || !panel.current) return;
1464
+ const place = () => {
1465
+ const anchor = trigger.current.getBoundingClientRect();
1466
+ const menu = panel.current.getBoundingClientRect();
1467
+ const above = window.innerHeight - anchor.bottom < menu.height + 8 && anchor.top > window.innerHeight - anchor.bottom;
1468
+ setPosition({
1469
+ top: Math.max(8, Math.min(above ? anchor.top - menu.height - 4 : anchor.bottom + 4, window.innerHeight - menu.height - 8)),
1470
+ left: Math.max(8, Math.min(anchor.right - menu.width, window.innerWidth - menu.width - 8))
1471
+ });
1472
+ };
1473
+ place();
1474
+ const enabled = buttons();
1475
+ (initialLast.current ? enabled[enabled.length - 1] : enabled[0])?.focus({ preventScroll: true });
1476
+ if (enabled.length === 0) panel.current.focus({ preventScroll: true });
1477
+ const outside = (event) => {
1478
+ if (!panel.current?.contains(event.target) && !trigger.current?.contains(event.target)) setOpen(false);
1479
+ };
1480
+ const scroll = (event) => {
1481
+ if (!panel.current?.contains(event.target)) setOpen(false);
1482
+ };
1483
+ const observer = new ResizeObserver(place);
1484
+ observer.observe(panel.current);
1485
+ document.addEventListener("pointerdown", outside);
1486
+ window.addEventListener("scroll", scroll, true);
1487
+ window.addEventListener("resize", place);
1488
+ return () => {
1489
+ observer.disconnect();
1490
+ document.removeEventListener("pointerdown", outside);
1491
+ window.removeEventListener("scroll", scroll, true);
1492
+ window.removeEventListener("resize", place);
1493
+ };
1494
+ }, [open]);
1495
+ (0, import_react4.useLayoutEffect)(() => {
1496
+ if (disabled || items.length === 0) setOpen(false);
1497
+ }, [disabled, items.length]);
1498
+ if (items.length === 0) return null;
1499
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "fractal-table-overflow-trigger", children: [
1500
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1501
+ Button,
1502
+ {
1503
+ ref: trigger,
1504
+ buttonType: "ghost",
1505
+ controlSize: "sm",
1506
+ hasIconOnly: true,
1507
+ icon: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_fractal_icons9.Ellipsis, { "aria-hidden": true }),
1508
+ "aria-label": label,
1509
+ "aria-haspopup": "menu",
1510
+ "aria-expanded": open,
1511
+ "aria-controls": open ? id : void 0,
1512
+ disabled,
1513
+ onClick: () => {
1514
+ initialLast.current = false;
1515
+ setOpen(!open);
1516
+ },
1517
+ onKeyDown: (event) => {
1518
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1519
+ event.preventDefault();
1520
+ initialLast.current = event.key === "ArrowUp";
1521
+ setOpen(true);
1522
+ }
1523
+ }
1524
+ }
1525
+ ),
1526
+ open && (0, import_react_dom3.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1527
+ "div",
1528
+ {
1529
+ ref: panel,
1530
+ id,
1531
+ role: "menu",
1532
+ tabIndex: -1,
1533
+ "aria-label": label,
1534
+ className: "fractal-table-overflow-menu",
1535
+ style: position,
1536
+ onClick: (event) => event.stopPropagation(),
1537
+ onKeyDown: (event) => {
1538
+ event.stopPropagation();
1539
+ const enabled = buttons(), index = enabled.indexOf(document.activeElement);
1540
+ if (event.key === "Escape") {
1541
+ event.preventDefault();
1542
+ close();
1543
+ } else if (event.key === "Tab") {
1544
+ close();
1545
+ } else if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
1546
+ event.preventDefault();
1547
+ const next = event.key === "Home" ? 0 : event.key === "End" ? enabled.length - 1 : (index + (event.key === "ArrowDown" ? 1 : -1) + enabled.length) % enabled.length;
1548
+ enabled[next]?.focus();
1549
+ } else if (event.key.length === 1 && /\S/.test(event.key)) {
1550
+ const ordered = [...enabled.slice(index + 1), ...enabled.slice(0, index + 1)];
1551
+ ordered.find((button) => button.textContent?.trim().toLocaleLowerCase().startsWith(event.key.toLocaleLowerCase()))?.focus();
1552
+ }
1553
+ },
1554
+ children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1555
+ "button",
1556
+ {
1557
+ type: "button",
1558
+ role: "menuitem",
1559
+ tabIndex: -1,
1560
+ disabled: item.disabled,
1561
+ onClick: () => {
1562
+ onSelect(item.value);
1563
+ close();
1564
+ },
1565
+ children: [
1566
+ item.icon && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { "aria-hidden": "true", children: item.icon }),
1567
+ item.label
1568
+ ]
1569
+ },
1570
+ item.value
1571
+ ))
1572
+ }
1573
+ ), document.body)
1574
+ ] });
1575
+ }
1576
+
1577
+ // src/components/TableLayout/TableLayout.tsx
1578
+ var import_react5 = require("react");
1579
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1580
+ var length = (value) => typeof value === "number" ? `${value}px` : value;
1581
+ function columnStyle(column) {
1582
+ return {
1583
+ width: column.width,
1584
+ "--table-column-width": !column.width || column.width.includes("%") ? void 0 : `calc(${column.width} - 1px)`,
1585
+ "--table-column-min": length(column.minWidth),
1586
+ "--table-column-max": length(column.maxWidth)
1587
+ };
1588
+ }
1589
+ function useOverflow() {
1590
+ const ref = (0, import_react5.useRef)(null);
1591
+ const [overflow, setOverflow] = (0, import_react5.useState)(false);
1592
+ (0, import_react5.useLayoutEffect)(() => {
1593
+ const el = ref.current;
1594
+ if (!el) return;
1595
+ const measure = () => setOverflow(el.scrollWidth > el.clientWidth + 1 || el.scrollHeight > el.clientHeight + 1);
1596
+ const resize = new ResizeObserver(measure);
1597
+ const observe = () => {
1598
+ resize.disconnect();
1599
+ resize.observe(el);
1600
+ for (const child of el.children) resize.observe(child);
1601
+ measure();
1602
+ };
1603
+ const mutation = new MutationObserver(observe);
1604
+ mutation.observe(el, { childList: true, subtree: true, characterData: true });
1605
+ observe();
1606
+ return () => {
1607
+ resize.disconnect();
1608
+ mutation.disconnect();
1609
+ };
1610
+ }, []);
1611
+ return { ref, overflow };
1612
+ }
1613
+ function TableViewport({ children, maxHeight, label }) {
1614
+ const { ref, overflow } = useOverflow();
1615
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { ref, className: "fractal-table-viewport", style: { maxHeight }, tabIndex: overflow ? 0 : void 0, role: overflow ? "region" : void 0, "aria-label": overflow ? `${label}, scrollable table` : void 0, children });
1616
+ }
1617
+ function TableCellContent({ children, label }) {
1618
+ const { ref, overflow } = useOverflow();
1619
+ (0, import_react5.useLayoutEffect)(() => {
1620
+ const cell = ref.current;
1621
+ if (!cell) return;
1622
+ const wheel = (event) => {
1623
+ if (event.defaultPrevented || event.ctrlKey || !event.cancelable || Math.abs(event.deltaX) <= Math.abs(event.deltaY)) return;
1624
+ const range = cell.scrollWidth - cell.clientWidth;
1625
+ if (range <= 1) return;
1626
+ const rtl = getComputedStyle(cell).direction === "rtl";
1627
+ const left = rtl ? -range : 0, right = rtl ? 0 : range;
1628
+ if (event.deltaX > 0 ? cell.scrollLeft < right - 1 : cell.scrollLeft > left + 1) return;
1629
+ const table = cell.closest(".fractal-table-viewport");
1630
+ if (!table) return;
1631
+ const tableRange = table.scrollWidth - table.clientWidth;
1632
+ const tableRtl = getComputedStyle(table).direction === "rtl";
1633
+ const tableLeft = tableRtl ? -tableRange : 0, tableRight = tableRtl ? 0 : tableRange;
1634
+ if (event.deltaX > 0 ? table.scrollLeft >= tableRight - 1 : table.scrollLeft <= tableLeft + 1) return;
1635
+ const unit = event.deltaMode === 1 ? parseFloat(getComputedStyle(cell).lineHeight) || 16 : event.deltaMode === 2 ? table.clientWidth : 1;
1636
+ event.preventDefault();
1637
+ table.scrollLeft += event.deltaX * unit;
1638
+ };
1639
+ cell.addEventListener("wheel", wheel, { passive: false });
1640
+ return () => cell.removeEventListener("wheel", wheel);
1641
+ }, [ref]);
1642
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1643
+ "div",
1644
+ {
1645
+ ref,
1646
+ className: "fractal-table-cell-scroll",
1647
+ "data-overflow": overflow || void 0,
1648
+ tabIndex: overflow ? 0 : void 0,
1649
+ role: overflow ? "region" : void 0,
1650
+ "aria-label": overflow ? `${label}, scroll horizontally for full content` : void 0,
1651
+ onKeyDown: (event) => {
1652
+ if (!overflow || event.target !== event.currentTarget) return;
1653
+ if (["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) {
1654
+ event.preventDefault();
1655
+ event.stopPropagation();
1656
+ const el = event.currentTarget;
1657
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") el.scrollBy({ left: event.key === "ArrowLeft" ? -40 : 40 });
1658
+ else el.scrollLeft = event.key === "Home" ? 0 : el.scrollWidth * (getComputedStyle(el).direction === "rtl" ? -1 : 1);
1659
+ } else if (event.key === " " || event.key === "Enter") event.stopPropagation();
1660
+ },
1661
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "fractal-table-cell-content", children })
1662
+ }
1663
+ );
1664
+ }
1665
+
537
1666
  // src/components/DataTable/DataTable.tsx
538
- var import_jsx_runtime9 = require("react/jsx-runtime");
539
- function DataTable({ columns, data, emptyMessage, header, size = "md", className }) {
540
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: cn("fractal-datatable", `fractal-datatable--${size}`, className), children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "fractal-datatable-surface", children: [
541
- header && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "fractal-datatable-header", children: header }),
542
- data.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "fractal-datatable-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("table", { className: "fractal-datatable-table", children: [
543
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("tr", { className: "fractal-datatable-thead-row", children: columns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { className: "fractal-datatable-th", style: { width: col.width }, children: col.header }, col.key)) }) }),
544
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("tbody", { children: data.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1667
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1668
+ function DataTable({ columns, data, emptyMessage, header, size = "md", className, overflowMenu, layout = "fill", stickyHeader = false, stickyFirstColumn = false, maxHeight, "aria-label": ariaLabel = "Data table" }) {
1669
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn("fractal-table-layout", "fractal-datatable", `fractal-datatable--${size}`, className), "data-overflow-menu": Boolean(overflowMenu), "data-table-layout": layout, "data-sticky-first": stickyFirstColumn, "data-sticky-header": stickyHeader, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "fractal-datatable-surface", children: [
1670
+ header && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "fractal-datatable-header", children: header }),
1671
+ data.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "fractal-datatable-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TableViewport, { maxHeight, label: ariaLabel, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("table", { "aria-label": ariaLabel, className: "fractal-datatable-table", children: [
1672
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("tr", { className: "fractal-datatable-thead-row", children: [
1673
+ columns.map((col, columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("th", { scope: "col", className: "fractal-datatable-th", style: columnStyle(col), "data-pinned-column": columnIndex === 0 ? "data" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TableCellContent, { label: `${col.header} column`, children: col.header }) }, col.key)),
1674
+ overflowMenu && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("th", { scope: "col", "aria-label": "Row actions", className: "fractal-datatable-th fractal-table-overflow-cell", "data-pinned-column": "overflow" })
1675
+ ] }) }),
1676
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("tbody", { children: data.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
545
1677
  "tr",
546
1678
  {
547
1679
  className: "fractal-datatable-row",
548
- children: columns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { className: "fractal-datatable-td", style: { width: col.width }, children: col.render(row, rowIndex) }, col.key))
1680
+ children: [
1681
+ columns.map((col, columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("td", { className: "fractal-datatable-td", style: columnStyle(col), "data-pinned-column": columnIndex === 0 ? "data" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TableCellContent, { label: `${col.header || "Value"}, row ${rowIndex + 1}`, children: col.render(row, rowIndex) }) }, col.key)),
1682
+ overflowMenu && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("td", { className: "fractal-datatable-td fractal-table-overflow-cell", "data-pinned-column": "overflow", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TableOverflowMenu, { items: overflowMenu.items(row, rowIndex), label: overflowMenu.getLabel?.(row, rowIndex) ?? `Actions for row ${rowIndex + 1}`, disabled: overflowMenu.isDisabled?.(row, rowIndex), onSelect: (value) => overflowMenu.onSelect(value, row, rowIndex) }) })
1683
+ ]
549
1684
  },
550
1685
  rowIndex
551
1686
  )) })
552
- ] })
1687
+ ] }) })
553
1688
  ] }) });
554
1689
  }
555
1690
 
556
1691
  // src/components/Dropdown/Dropdown.tsx
557
- var React7 = __toESM(require("react"), 1);
558
- var import_fractal_icons2 = require("@mirror-physics/fractal-icons");
559
- var import_jsx_runtime10 = require("react/jsx-runtime");
560
- var MARGIN = 4;
1692
+ var React12 = __toESM(require("react"), 1);
1693
+ var import_fractal_icons10 = require("@mirror-physics/fractal-icons");
1694
+
1695
+ // src/components/Dropdown/position.ts
1696
+ function isDropdownTriggerVisible(trigger, bounds) {
1697
+ return trigger.right > bounds.left && trigger.left < bounds.right && trigger.bottom > bounds.top && trigger.top < bounds.bottom;
1698
+ }
1699
+ function positionDropdown(trigger, panel, bounds, direction, align) {
1700
+ const gap = 4;
1701
+ const width = Math.min(panel.width, Math.max(0, bounds.right - bounds.left));
1702
+ let maxHeight = Math.max(0, bounds.bottom - bounds.top);
1703
+ let left;
1704
+ let top;
1705
+ if (direction === "left" || direction === "right") {
1706
+ const after = bounds.right - trigger.right - gap;
1707
+ const before = trigger.left - bounds.left - gap;
1708
+ let placeRight = direction === "right";
1709
+ if (width > (placeRight ? after : before) && (placeRight ? before > after : after > before)) {
1710
+ placeRight = !placeRight;
1711
+ }
1712
+ left = placeRight ? trigger.right + gap : trigger.left - width - gap;
1713
+ top = trigger.top;
1714
+ } else {
1715
+ const below = Math.max(0, bounds.bottom - trigger.bottom - gap);
1716
+ const above = Math.max(0, trigger.top - bounds.top - gap);
1717
+ let placeAbove = direction === "up";
1718
+ if (panel.height > (placeAbove ? above : below) && (placeAbove ? below > above : above > below)) {
1719
+ placeAbove = !placeAbove;
1720
+ }
1721
+ maxHeight = Math.min(maxHeight, placeAbove ? above : below);
1722
+ top = placeAbove ? trigger.top - Math.min(panel.height, maxHeight) - gap : trigger.bottom + gap;
1723
+ left = align === "right" ? trigger.right - width : trigger.left;
1724
+ }
1725
+ const height = Math.min(panel.height, maxHeight);
1726
+ return {
1727
+ left: Math.max(bounds.left, Math.min(left, bounds.right - width)),
1728
+ top: Math.max(bounds.top, Math.min(top, bounds.bottom - height)),
1729
+ maxHeight
1730
+ };
1731
+ }
1732
+
1733
+ // src/components/Dropdown/Dropdown.tsx
1734
+ var import_jsx_runtime21 = require("react/jsx-runtime");
561
1735
  function Dropdown({
562
1736
  items,
563
- selectedValue,
1737
+ selectedValue = "",
564
1738
  onSelect,
565
- triggerLabel = "Options",
1739
+ multiple = false,
1740
+ selectedValues = [],
1741
+ onSelectionChange,
1742
+ searchable = false,
1743
+ searchPlaceholder = "Find an option",
1744
+ emptyMessage = "No matching options.",
1745
+ triggerLabel,
1746
+ fieldLabel,
1747
+ clearable = Boolean(fieldLabel),
566
1748
  direction = "down",
567
1749
  align = "left",
1750
+ maxHeight = 320,
1751
+ collisionPadding = 8,
1752
+ triggerOrientation = "horizontal",
568
1753
  triggerContent,
569
1754
  label,
1755
+ iconOnly = false,
570
1756
  noChevron = false,
571
- size = "md",
1757
+ size: sizeAlias = "lg",
1758
+ controlSize,
1759
+ triggerId,
572
1760
  triggerVariant = "outline",
573
1761
  className,
574
1762
  triggerClassName,
575
1763
  panelClassName,
576
- closeOnSelect = true
1764
+ closeOnSelect = true,
1765
+ disabled = false
577
1766
  }) {
578
- const [open, setOpen] = React7.useState(false);
579
- const triggerRef = React7.useRef(null);
580
- const panelRef = React7.useRef(null);
581
- const [panelStyle, setPanelStyle] = React7.useState({});
582
- const visibleItems = items.filter(
1767
+ const [query, setQuery] = React12.useState("");
1768
+ const searchRef = React12.useRef(null);
1769
+ const searchContainerRef = React12.useRef(null);
1770
+ const optionsRef = React12.useRef(null);
1771
+ const [optionsScrolled, setOptionsScrolled] = React12.useState(false);
1772
+ const accessibleLabel = triggerLabel ?? fieldLabel ?? "Options";
1773
+ const size = controlSize ?? sizeAlias;
1774
+ const canClear = clearable && !disabled && (multiple ? selectedValues.length > 0 && Boolean(onSelectionChange) : Boolean(selectedValue) && Boolean(onSelect));
1775
+ const [open, setOpen] = React12.useState(false);
1776
+ React12.useEffect(() => {
1777
+ if (!open) setQuery("");
1778
+ }, [open]);
1779
+ const rootRef = React12.useRef(null);
1780
+ const menuId = React12.useId();
1781
+ const focusOnOpen = React12.useRef(false);
1782
+ const triggerRef = React12.useRef(null);
1783
+ const panelRef = React12.useRef(null);
1784
+ const [panelStyle, setPanelStyle] = React12.useState({});
1785
+ const renderableItems = items.filter(
583
1786
  (item) => item.icon != null || item.description != null && (typeof item.description === "string" ? item.description !== "" : true)
584
1787
  );
585
- if (visibleItems.length === 0) return null;
586
- const selectableItems = visibleItems.filter((i) => i.kind !== "header");
587
- const selected = items.find((i) => i.value === selectedValue && i.kind !== "header") ?? selectableItems[0] ?? visibleItems[0];
588
- const isUp = direction === "up";
589
- const ChevronIcon = isUp ? import_fractal_icons2.ChevronUp : import_fractal_icons2.ChevronDown;
590
- React7.useLayoutEffect(() => {
591
- if (!open || !triggerRef.current || !panelRef.current) return;
592
- const triggerRect = triggerRef.current.getBoundingClientRect();
593
- const panelRect = panelRef.current.getBoundingClientRect();
594
- const vw = window.innerWidth;
595
- const vh = window.innerHeight;
596
- const spaceBelow = vh - triggerRect.bottom - MARGIN;
597
- const spaceAbove = triggerRect.top - MARGIN;
598
- let placeAbove = isUp;
599
- if (placeAbove && panelRect.height > spaceAbove && spaceBelow > spaceAbove) {
600
- placeAbove = false;
601
- } else if (!placeAbove && panelRect.height > spaceBelow && spaceAbove > spaceBelow) {
602
- placeAbove = true;
1788
+ const normalizedQuery = query.trim().toLocaleLowerCase();
1789
+ let groupTitle = "";
1790
+ const matching = new Set(renderableItems.filter((item) => {
1791
+ if (item.kind === "header") {
1792
+ groupTitle = String(item.description ?? "");
1793
+ return false;
603
1794
  }
604
- const top = placeAbove ? -(panelRect.height + MARGIN) : triggerRect.height + MARGIN;
605
- let left;
606
- let right;
607
- if (align === "right") {
608
- right = 0;
609
- const panelLeft = triggerRect.right - panelRect.width;
610
- if (panelLeft < 0) {
611
- right = void 0;
612
- left = 0;
1795
+ return `${groupTitle} ${typeof item.description === "string" ? item.description : item.value}`.toLocaleLowerCase().includes(normalizedQuery);
1796
+ }).map((item) => item.value));
1797
+ const visibleItems = renderableItems.filter((item, index) => {
1798
+ if (item.kind !== "header") return matching.has(item.value);
1799
+ const nextHeader = renderableItems.findIndex((next, i) => i > index && next.kind === "header");
1800
+ return renderableItems.slice(index + 1, nextHeader < 0 ? void 0 : nextHeader).some((next) => matching.has(next.value));
1801
+ });
1802
+ const selectableItems = visibleItems.filter((i) => i.kind !== "header");
1803
+ const selected = items.find((i) => i.value === selectedValue && i.kind !== "header") ?? (fieldLabel && !selectedValue && !multiple ? void 0 : selectableItems[0] ?? visibleItems[0]);
1804
+ const ChevronIcon = direction === "up" ? import_fractal_icons10.ChevronUp : direction === "left" ? import_fractal_icons10.ChevronLeft : direction === "right" ? import_fractal_icons10.ChevronRight : import_fractal_icons10.ChevronDown;
1805
+ useEscapeDismiss(100, () => {
1806
+ setOpen(false);
1807
+ triggerRef.current?.focus();
1808
+ }, open);
1809
+ React12.useLayoutEffect(() => {
1810
+ if (!open) return;
1811
+ if (optionsRef.current) optionsRef.current.scrollTop = 0;
1812
+ setOptionsScrolled(false);
1813
+ }, [open, query]);
1814
+ React12.useLayoutEffect(() => {
1815
+ const root = rootRef.current;
1816
+ const trigger = triggerRef.current;
1817
+ const panel = panelRef.current;
1818
+ if (!open || !root || !trigger || !panel) return;
1819
+ const updatePosition = () => {
1820
+ const viewport = window.visualViewport;
1821
+ const bounds = {
1822
+ left: viewport?.offsetLeft ?? 0,
1823
+ top: viewport?.offsetTop ?? 0,
1824
+ right: (viewport?.offsetLeft ?? 0) + (viewport?.width ?? document.documentElement.clientWidth),
1825
+ bottom: (viewport?.offsetTop ?? 0) + (viewport?.height ?? document.documentElement.clientHeight)
1826
+ };
1827
+ for (let ancestor = root.parentElement; ancestor; ancestor = ancestor.parentElement) {
1828
+ const style = getComputedStyle(ancestor);
1829
+ const rect = ancestor.getBoundingClientRect();
1830
+ if (/(auto|scroll|hidden|clip)/.test(style.overflowX)) {
1831
+ bounds.left = Math.max(bounds.left, rect.left + ancestor.clientLeft);
1832
+ bounds.right = Math.min(bounds.right, rect.left + ancestor.clientLeft + ancestor.clientWidth);
1833
+ }
1834
+ if (/(auto|scroll|hidden|clip)/.test(style.overflowY)) {
1835
+ bounds.top = Math.max(bounds.top, rect.top + ancestor.clientTop);
1836
+ bounds.bottom = Math.min(bounds.bottom, rect.top + ancestor.clientTop + ancestor.clientHeight);
1837
+ }
613
1838
  }
614
- } else {
615
- left = 0;
616
- const panelRight = triggerRect.left + panelRect.width;
617
- if (panelRight > vw) {
618
- left = void 0;
619
- right = 0;
1839
+ const triggerRect = trigger.getBoundingClientRect();
1840
+ if (!isDropdownTriggerVisible(triggerRect, bounds)) {
1841
+ focusOnOpen.current = false;
1842
+ setOpen(false);
1843
+ return false;
620
1844
  }
1845
+ const padding = Math.max(0, collisionPadding);
1846
+ bounds.left += padding;
1847
+ bounds.right = Math.max(bounds.left, bounds.right - padding);
1848
+ bounds.top += padding;
1849
+ bounds.bottom = Math.max(bounds.top, bounds.bottom - padding);
1850
+ const rootRect = root.getBoundingClientRect();
1851
+ const availableWidth = bounds.right - bounds.left;
1852
+ const heightLimit = Math.max(0, maxHeight);
1853
+ panel.style.maxWidth = `${availableWidth}px`;
1854
+ panel.style.minWidth = `${Math.min(triggerRect.width, availableWidth)}px`;
1855
+ const panelRect = panel.getBoundingClientRect();
1856
+ const desiredHeight = Math.min(heightLimit, (optionsRef.current?.scrollHeight ?? 0) + (searchContainerRef.current?.offsetHeight ?? 0) + parseFloat(getComputedStyle(panel).paddingTop) + parseFloat(getComputedStyle(panel).paddingBottom) + panel.offsetHeight - panel.clientHeight);
1857
+ const position = positionDropdown(triggerRect, { width: panelRect.width, height: desiredHeight }, bounds, direction, align);
1858
+ const nextStyle = {
1859
+ left: position.left - rootRect.left - root.clientLeft + root.scrollLeft,
1860
+ top: position.top - rootRect.top - root.clientTop + root.scrollTop,
1861
+ minWidth: Math.min(triggerRect.width, availableWidth),
1862
+ maxWidth: availableWidth,
1863
+ maxHeight: Math.min(heightLimit, position.maxHeight)
1864
+ };
1865
+ for (const key of Object.keys(nextStyle)) {
1866
+ panel.style[key] = `${nextStyle[key]}px`;
1867
+ }
1868
+ setPanelStyle((previous) => Object.entries(nextStyle).every(([key, value]) => previous[key] === value) ? previous : nextStyle);
1869
+ return true;
1870
+ };
1871
+ if (!updatePosition()) return;
1872
+ if (searchable && !panel.contains(document.activeElement)) searchRef.current?.focus({ preventScroll: true });
1873
+ if (focusOnOpen.current) {
1874
+ focusOnOpen.current = false;
1875
+ const option = panel.querySelector('[aria-selected="true"]') ?? panel.querySelector('[role="option"]');
1876
+ option?.focus({ preventScroll: true });
1877
+ option?.scrollIntoView({ block: "nearest" });
1878
+ }
1879
+ const resize = new ResizeObserver(updatePosition);
1880
+ resize.observe(root);
1881
+ resize.observe(panel);
1882
+ if (optionsRef.current) resize.observe(optionsRef.current);
1883
+ for (let ancestor = root.parentElement; ancestor; ancestor = ancestor.parentElement) {
1884
+ resize.observe(ancestor);
621
1885
  }
622
- setPanelStyle({ top, left, right });
623
- }, [open, isUp, align]);
624
- React7.useEffect(() => {
1886
+ window.addEventListener("resize", updatePosition);
1887
+ window.addEventListener("scroll", updatePosition, true);
1888
+ window.visualViewport?.addEventListener("resize", updatePosition);
1889
+ window.visualViewport?.addEventListener("scroll", updatePosition);
1890
+ return () => {
1891
+ resize.disconnect();
1892
+ window.removeEventListener("resize", updatePosition);
1893
+ window.removeEventListener("scroll", updatePosition, true);
1894
+ window.visualViewport?.removeEventListener("resize", updatePosition);
1895
+ window.visualViewport?.removeEventListener("scroll", updatePosition);
1896
+ };
1897
+ }, [open, direction, align, maxHeight, collisionPadding, items, label, query, searchable]);
1898
+ React12.useEffect(() => {
625
1899
  if (!open) return;
626
1900
  const handleClickOutside = (e) => {
627
1901
  const target = e.target;
@@ -631,75 +1905,128 @@ function Dropdown({
631
1905
  document.addEventListener("mousedown", handleClickOutside);
632
1906
  return () => document.removeEventListener("mousedown", handleClickOutside);
633
1907
  }, [open]);
634
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: cn("fractal-dropdown", className), children: [
635
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1908
+ if (renderableItems.length === 0) return null;
1909
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { ref: rootRef, "data-control-size": size, "data-filled": multiple ? selectedValues.length > 0 : Boolean(selectedValue), "data-open": open, "data-clearable": canClear || void 0, className: cn("fractal-dropdown", fieldLabel && "fractal-floating-field fractal-floating-field--dropdown", className), children: [
1910
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
636
1911
  "button",
637
1912
  {
638
1913
  ref: triggerRef,
1914
+ id: triggerId ?? `${menuId}-trigger`,
1915
+ "data-control-size": size,
639
1916
  type: "button",
640
- "aria-haspopup": "listbox",
1917
+ disabled,
1918
+ "aria-haspopup": searchable ? "dialog" : "listbox",
641
1919
  "aria-expanded": open,
642
- "aria-label": triggerLabel,
1920
+ "aria-controls": open ? searchable ? menuId : `${menuId}-options` : void 0,
1921
+ onKeyDown: (event) => {
1922
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1923
+ event.preventDefault();
1924
+ if (open) {
1925
+ const option = panelRef.current?.querySelector('[aria-selected="true"]') ?? panelRef.current?.querySelector('[role="option"]');
1926
+ option?.focus({ preventScroll: true });
1927
+ option?.scrollIntoView({ block: "nearest" });
1928
+ } else {
1929
+ focusOnOpen.current = true;
1930
+ setOpen(true);
1931
+ }
1932
+ }
1933
+ },
1934
+ "aria-label": accessibleLabel,
643
1935
  onClick: () => setOpen((o) => !o),
644
1936
  className: cn(
1937
+ "fractal-control",
645
1938
  "fractal-dropdown-trigger",
646
1939
  `fractal-dropdown-trigger--${size}`,
647
1940
  `fractal-dropdown-trigger--${triggerVariant}`,
1941
+ `fractal-dropdown-trigger--${triggerOrientation}`,
1942
+ iconOnly && "fractal-dropdown-trigger--icon-only",
648
1943
  triggerClassName
649
1944
  ),
650
1945
  children: [
651
- triggerContent != null ? triggerContent : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
652
- selected.icon != null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fractal-dropdown-icon fractal-dropdown-icon--trigger", children: selected.icon }),
653
- selected.description != null && (typeof selected.description !== "string" || selected.description !== "") && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fractal-dropdown-selected-text", children: selected.description })
654
- ] }),
655
- !noChevron && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fractal-dropdown-chevron", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChevronIcon, { size: size === "sm" ? 14 : 16 }) })
1946
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: fieldLabel ? "fractal-dropdown-field-value" : void 0, style: fieldLabel ? void 0 : { display: "contents" }, children: triggerContent != null ? triggerContent : /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
1947
+ selected?.icon != null && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "fractal-dropdown-icon fractal-dropdown-icon--trigger", children: selected?.icon }),
1948
+ selected?.description != null && (typeof selected?.description !== "string" || selected?.description !== "") && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "fractal-dropdown-selected-text", children: selected?.description })
1949
+ ] }) }),
1950
+ !noChevron && !iconOnly && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "fractal-dropdown-chevron", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ChevronIcon, { size: size === "sm" ? 14 : 16 }) })
656
1951
  ]
657
1952
  }
658
1953
  ),
659
- open && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1954
+ canClear && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("button", { type: "button", className: "fractal-dropdown-clear", "aria-label": `Clear ${accessibleLabel}`, onClick: () => {
1955
+ if (multiple) onSelectionChange?.([]);
1956
+ else onSelect?.("");
1957
+ setOpen(false);
1958
+ setQuery("");
1959
+ triggerRef.current?.focus();
1960
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_fractal_icons10.Close, { "aria-hidden": "true" }) }),
1961
+ fieldLabel ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(FloatingFieldBorder, { label: fieldLabel }) : null,
1962
+ fieldLabel ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("label", { className: "fractal-floating-field__label", htmlFor: triggerId ?? `${menuId}-trigger`, children: fieldLabel }) : null,
1963
+ open && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
660
1964
  "div",
661
1965
  {
662
1966
  ref: panelRef,
663
- role: "listbox",
664
- "aria-label": triggerLabel,
1967
+ id: menuId,
1968
+ onKeyDown: (event) => {
1969
+ if (event.target === searchRef.current && event.key !== "ArrowDown") return;
1970
+ if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
1971
+ event.preventDefault();
1972
+ const options = Array.from(panelRef.current?.querySelectorAll('[role="option"]') ?? []);
1973
+ const current = options.indexOf(document.activeElement);
1974
+ const index = event.key === "Home" ? 0 : event.key === "End" ? options.length - 1 : (current + (event.key === "ArrowDown" ? 1 : -1) + options.length) % options.length;
1975
+ options[index]?.focus({ preventScroll: true });
1976
+ options[index]?.scrollIntoView({ block: "nearest" });
1977
+ },
1978
+ role: searchable ? "dialog" : void 0,
1979
+ "aria-label": accessibleLabel,
665
1980
  className: cn("fractal-dropdown-panel", `fractal-dropdown-panel--${size}`, panelClassName),
666
1981
  style: panelStyle,
667
1982
  children: [
668
- label != null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "fractal-dropdown-label", children: label }),
669
- visibleItems.map((item, index) => {
670
- const topDivider = item.border === "top" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "fractal-dropdown-separator" }) : null;
671
- const bottomDivider = item.border === "bottom" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "fractal-dropdown-separator" }) : null;
672
- if (item.kind === "header") {
673
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(React7.Fragment, { children: [
1983
+ searchable && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { ref: searchContainerRef, className: "fractal-dropdown-search-container", "data-scrolled": optionsScrolled, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "fractal-dropdown-search", children: [
1984
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_fractal_icons10.MagnifyingGlass, { "aria-hidden": "true" }),
1985
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("input", { ref: searchRef, type: "search", "aria-label": searchPlaceholder, placeholder: searchPlaceholder, value: query, onChange: (event) => setQuery(event.target.value) })
1986
+ ] }) }),
1987
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { ref: optionsRef, className: "fractal-dropdown-options", onScroll: (event) => setOptionsScrolled(event.currentTarget.scrollTop > 0), role: "listbox", id: `${menuId}-options`, "aria-label": accessibleLabel, "aria-multiselectable": multiple || void 0, children: [
1988
+ label != null && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "fractal-dropdown-label", children: label }),
1989
+ visibleItems.map((item, index) => {
1990
+ const topDivider = item.border === "top" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "fractal-dropdown-separator" }) : null;
1991
+ const bottomDivider = item.border === "bottom" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "fractal-dropdown-separator" }) : null;
1992
+ if (item.kind === "header") {
1993
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(React12.Fragment, { children: [
1994
+ topDivider,
1995
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { role: "presentation", className: "fractal-dropdown-label", children: item.description }),
1996
+ bottomDivider
1997
+ ] }, `header-${index}`);
1998
+ }
1999
+ const isSelected = multiple ? selectedValues.includes(item.value) : item.value === selectedValue;
2000
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(React12.Fragment, { children: [
674
2001
  topDivider,
675
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { role: "presentation", className: "fractal-dropdown-label", children: item.description }),
2002
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2003
+ "button",
2004
+ {
2005
+ type: "button",
2006
+ role: "option",
2007
+ "aria-selected": isSelected,
2008
+ onClick: () => {
2009
+ if (multiple) onSelectionChange?.(isSelected ? selectedValues.filter((value) => value !== item.value) : [...selectedValues, item.value]);
2010
+ else onSelect?.(item.value);
2011
+ if (!multiple && closeOnSelect) {
2012
+ setOpen(false);
2013
+ triggerRef.current?.focus();
2014
+ }
2015
+ },
2016
+ className: "fractal-dropdown-option",
2017
+ children: [
2018
+ multiple && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "fractal-dropdown-checkbox", "data-checked": isSelected, "aria-hidden": "true", children: isSelected && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_fractal_icons10.Check, {}) }),
2019
+ item.icon != null && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "fractal-dropdown-icon", children: item.icon }),
2020
+ item.description != null && (typeof item.description !== "string" || item.description !== "") && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "fractal-dropdown-option-text", children: item.description }),
2021
+ item.trailing != null && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "fractal-dropdown-trailing", children: item.trailing })
2022
+ ]
2023
+ }
2024
+ ),
676
2025
  bottomDivider
677
- ] }, `header-${index}`);
678
- }
679
- const isSelected = item.value === selectedValue;
680
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(React7.Fragment, { children: [
681
- topDivider,
682
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
683
- "button",
684
- {
685
- type: "button",
686
- role: "option",
687
- "aria-selected": isSelected,
688
- onClick: () => {
689
- onSelect(item.value);
690
- if (closeOnSelect) setOpen(false);
691
- },
692
- className: "fractal-dropdown-option",
693
- children: [
694
- item.icon != null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fractal-dropdown-icon", children: item.icon }),
695
- item.description != null && (typeof item.description !== "string" || item.description !== "") && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fractal-dropdown-option-text", children: item.description }),
696
- item.trailing != null && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "fractal-dropdown-trailing", children: item.trailing })
697
- ]
698
- }
699
- ),
700
- bottomDivider
701
- ] }, item.value);
702
- })
2026
+ ] }, item.value);
2027
+ }),
2028
+ !selectableItems.length && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { role: "status", className: "fractal-dropdown-label", children: emptyMessage })
2029
+ ] })
703
2030
  ]
704
2031
  }
705
2032
  )
@@ -707,10 +2034,10 @@ function Dropdown({
707
2034
  }
708
2035
 
709
2036
  // src/components/Link/Link.tsx
710
- var React8 = __toESM(require("react"), 1);
711
- var import_jsx_runtime11 = require("react/jsx-runtime");
712
- var Link = React8.forwardRef(
713
- ({ className, theme = "default", size = "md", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2037
+ var React13 = __toESM(require("react"), 1);
2038
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2039
+ var Link = React13.forwardRef(
2040
+ ({ className, theme = "default", size = "md", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
714
2041
  "a",
715
2042
  {
716
2043
  ref,
@@ -722,10 +2049,10 @@ var Link = React8.forwardRef(
722
2049
  Link.displayName = "Link";
723
2050
 
724
2051
  // src/components/Chip/Chip.tsx
725
- var React9 = __toESM(require("react"), 1);
726
- var import_jsx_runtime12 = require("react/jsx-runtime");
727
- var Chip = React9.forwardRef(
728
- ({ className, tone = "neutral", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2052
+ var React14 = __toESM(require("react"), 1);
2053
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2054
+ var Chip = React14.forwardRef(
2055
+ ({ className, tone = "neutral", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
729
2056
  "span",
730
2057
  {
731
2058
  ref,
@@ -736,36 +2063,128 @@ var Chip = React9.forwardRef(
736
2063
  );
737
2064
  Chip.displayName = "Chip";
738
2065
 
2066
+ // src/components/Checkbox/Checkbox.tsx
2067
+ var import_fractal_icons11 = require("@mirror-physics/fractal-icons");
2068
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2069
+ function Checkbox({
2070
+ checked,
2071
+ indeterminate = false,
2072
+ onCheckedChange,
2073
+ className,
2074
+ disabled,
2075
+ onClick,
2076
+ onKeyDown,
2077
+ ...props
2078
+ }) {
2079
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2080
+ "button",
2081
+ {
2082
+ ...props,
2083
+ className: cn(
2084
+ "fractal-checkbox",
2085
+ (checked || indeterminate) && "fractal-checkbox--checked",
2086
+ indeterminate && "fractal-checkbox--indeterminate",
2087
+ className
2088
+ ),
2089
+ type: "button",
2090
+ role: "checkbox",
2091
+ "aria-checked": indeterminate ? "mixed" : checked,
2092
+ disabled,
2093
+ onClick: (event) => {
2094
+ onClick?.(event);
2095
+ if (!event.defaultPrevented) onCheckedChange(indeterminate || !checked);
2096
+ },
2097
+ onKeyDown: (event) => {
2098
+ onKeyDown?.(event);
2099
+ if (event.key === " " || event.key === "Enter") event.stopPropagation();
2100
+ },
2101
+ children: indeterminate ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: "fractal-checkbox__indeterminate-icon", "aria-hidden": "true", children: [
2102
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fractal_icons11.Checkbox, { size: 18 }),
2103
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fractal_icons11.Minus, { className: "fractal-checkbox__indeterminate-mark", size: 12 })
2104
+ ] }) : checked ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fractal_icons11.CheckboxChecked, { "aria-hidden": "true", size: 18 }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fractal_icons11.Checkbox, { "aria-hidden": "true", size: 18 })
2105
+ }
2106
+ );
2107
+ }
2108
+
2109
+ // src/components/Radio/Radio.tsx
2110
+ var import_jsx_runtime25 = require("react/jsx-runtime");
2111
+ function Radio({
2112
+ checked,
2113
+ className,
2114
+ description,
2115
+ disabled,
2116
+ label,
2117
+ onCheckedChange,
2118
+ ...props
2119
+ }) {
2120
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("label", { className: cn("fractal-radio", disabled && "fractal-radio--disabled", className), children: [
2121
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2122
+ "input",
2123
+ {
2124
+ ...props,
2125
+ checked,
2126
+ className: "fractal-radio__input",
2127
+ disabled,
2128
+ onChange: (event) => onCheckedChange?.(event.currentTarget.checked),
2129
+ type: "radio"
2130
+ }
2131
+ ),
2132
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { "aria-hidden": "true", className: "fractal-radio__control", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "fractal-radio__dot" }) }),
2133
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: "fractal-radio__content", children: [
2134
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "fractal-radio__label", children: label }),
2135
+ description && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "fractal-radio__description", children: description })
2136
+ ] })
2137
+ ] });
2138
+ }
2139
+
739
2140
  // src/components/SortableTable/SortableTable.tsx
740
- var import_react2 = require("react");
741
- var import_fractal_icons3 = require("@mirror-physics/fractal-icons");
742
- var import_jsx_runtime13 = require("react/jsx-runtime");
2141
+ var import_react6 = require("react");
2142
+ var import_fractal_icons12 = require("@mirror-physics/fractal-icons");
2143
+ var import_jsx_runtime26 = require("react/jsx-runtime");
743
2144
  function SortableTable({
744
2145
  columns,
745
2146
  data,
2147
+ "aria-label": ariaLabel,
746
2148
  emptyMessage,
747
2149
  header,
748
2150
  size = "md",
2151
+ embedded = false,
2152
+ layout = "fill",
2153
+ stickyHeader = false,
2154
+ stickyFirstColumn = false,
2155
+ maxHeight,
2156
+ overflowMenu,
749
2157
  className,
2158
+ defaultSort,
750
2159
  defaultSortKey = void 0,
751
2160
  defaultSortDirection = null,
752
2161
  sortKey: controlledKey,
753
2162
  sortDirection: controlledDirection,
754
2163
  onSortChange,
2164
+ pagination,
755
2165
  highlightRow,
756
- onRowClick
2166
+ selection,
2167
+ isRowDisabled,
2168
+ isRowSelectionDisabled,
2169
+ onRowClick,
2170
+ onRowDoubleClick,
2171
+ rowAction
757
2172
  }) {
758
- const [internalKey, setInternalKey] = (0, import_react2.useState)(defaultSortKey ?? null);
759
- const [internalDirection, setInternalDirection] = (0, import_react2.useState)(defaultSortDirection);
2173
+ const [internalKey, setInternalKey] = (0, import_react6.useState)(defaultSortKey ?? null);
2174
+ const [internalDirection, setInternalDirection] = (0, import_react6.useState)(defaultSortDirection);
760
2175
  const isControlled = controlledKey !== void 0 && controlledDirection !== void 0;
761
2176
  const activeKey = isControlled ? controlledKey : internalKey;
762
2177
  const activeDirection = isControlled ? controlledDirection : internalDirection;
763
- const sorted = (0, import_react2.useMemo)(() => {
764
- if (!activeKey || !activeDirection) return data;
765
- const col = columns.find((c) => c.key === activeKey);
2178
+ const baselineKey = defaultSort?.key ?? null;
2179
+ const baselineDirection = defaultSort?.direction ?? null;
2180
+ const effectiveKey = activeKey && activeDirection ? activeKey : baselineKey;
2181
+ const effectiveDirection = activeKey && activeDirection ? activeDirection : baselineDirection;
2182
+ const sorted = (0, import_react6.useMemo)(() => {
2183
+ if (!effectiveKey || !effectiveDirection) return data;
2184
+ const col = columns.find((c) => c.key === effectiveKey);
766
2185
  if (!col) return data;
767
2186
  const accessor = col.sortAccessor ?? ((row) => {
768
- const value = row[activeKey];
2187
+ const value = row[effectiveKey];
769
2188
  return typeof value === "string" || typeof value === "number" ? value : null;
770
2189
  });
771
2190
  const copy = [...data];
@@ -776,26 +2195,41 @@ function SortableTable({
776
2195
  if (av == null) return 1;
777
2196
  if (bv == null) return -1;
778
2197
  if (typeof av === "number" && typeof bv === "number") {
779
- return activeDirection === "asc" ? av - bv : bv - av;
2198
+ return effectiveDirection === "asc" ? av - bv : bv - av;
780
2199
  }
781
2200
  const as = String(av).toLowerCase();
782
2201
  const bs = String(bv).toLowerCase();
783
- if (as < bs) return activeDirection === "asc" ? -1 : 1;
784
- if (as > bs) return activeDirection === "asc" ? 1 : -1;
2202
+ if (as < bs) return effectiveDirection === "asc" ? -1 : 1;
2203
+ if (as > bs) return effectiveDirection === "asc" ? 1 : -1;
785
2204
  return 0;
786
2205
  });
787
2206
  return copy;
788
- }, [data, columns, activeKey, activeDirection]);
2207
+ }, [data, columns, effectiveKey, effectiveDirection]);
2208
+ const visibleRows = (0, import_react6.useMemo)(() => {
2209
+ if (!pagination) return sorted;
2210
+ const pageSize = Math.max(1, pagination.pageSize);
2211
+ const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));
2212
+ const page = Math.min(Math.max(1, pagination.page), totalPages);
2213
+ const startIndex = (page - 1) * pageSize;
2214
+ return sorted.slice(startIndex, startIndex + pageSize);
2215
+ }, [pagination, sorted]);
2216
+ const selectedRows = selection ? sorted.filter(selection.isSelected) : [];
2217
+ const selectableRows = selection ? sorted.filter((row) => !isRowDisabled?.(row) && !isRowSelectionDisabled?.(row)) : [];
2218
+ const selectedRowCount = selection ? selectableRows.filter(selection.isSelected).length : 0;
2219
+ const allRowsSelected = selectableRows.length > 0 && selectedRowCount === selectableRows.length;
2220
+ const someRowsSelected = selectedRows.length > 0 && !allRowsSelected;
789
2221
  const handleSortClick = (key) => {
790
2222
  let nextDirection;
791
- if (activeKey !== key) {
2223
+ if (!activeKey || !activeDirection) {
2224
+ nextDirection = baselineKey === key && baselineDirection ? baselineDirection : "asc";
2225
+ } else if (activeKey !== key) {
792
2226
  nextDirection = "asc";
2227
+ } else if (baselineKey === key && baselineDirection) {
2228
+ nextDirection = activeDirection === baselineDirection ? baselineDirection === "asc" ? "desc" : "asc" : null;
793
2229
  } else if (activeDirection === "asc") {
794
2230
  nextDirection = "desc";
795
- } else if (activeDirection === "desc") {
796
- nextDirection = null;
797
2231
  } else {
798
- nextDirection = "asc";
2232
+ nextDirection = null;
799
2233
  }
800
2234
  const nextKey = nextDirection === null ? null : key;
801
2235
  if (isControlled) {
@@ -806,101 +2240,200 @@ function SortableTable({
806
2240
  onSortChange?.(nextKey, nextDirection);
807
2241
  }
808
2242
  };
809
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: cn("fractal-sortable-table", `fractal-sortable-table--${size}`, className), children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "fractal-sortable-table-surface", children: [
810
- header && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fractal-sortable-table-header", children: header }),
811
- sorted.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "fractal-sortable-table-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("table", { className: "fractal-sortable-table-table", children: [
812
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tr", { className: "fractal-sortable-table-thead-row", children: columns.map((col) => {
813
- const isActive = activeKey === col.key && activeDirection !== null;
814
- if (!col.sortable) {
815
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
816
- "th",
817
- {
818
- className: "fractal-sortable-table-th",
819
- style: { width: col.width },
820
- children: col.header
821
- },
822
- col.key
823
- );
824
- }
825
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2243
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: cn("fractal-table-layout", "fractal-sortable-table", `fractal-sortable-table--${size}`, embedded && "fractal-sortable-table--embedded", className), "data-overflow-menu": Boolean(overflowMenu), "data-table-layout": layout, "data-sticky-first": stickyFirstColumn, "data-sticky-header": stickyHeader, "data-table-selection": Boolean(selection), children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "fractal-sortable-table-surface", children: [
2244
+ header && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "fractal-sortable-table-header", children: header }),
2245
+ visibleRows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "fractal-sortable-table-empty", children: emptyMessage || "No data" }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TableViewport, { maxHeight, label: ariaLabel || "Data table", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("table", { "aria-label": ariaLabel, className: "fractal-sortable-table-table", children: [
2246
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("tr", { className: "fractal-sortable-table-thead-row", children: [
2247
+ selection && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
826
2248
  "th",
827
2249
  {
828
- className: "fractal-sortable-table-th fractal-sortable-table-th--sortable",
829
- style: { width: col.width },
830
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
831
- "button",
2250
+ className: "fractal-sortable-table-th fractal-sortable-table-selection-cell",
2251
+ "data-pinned-column": "selection",
2252
+ scope: "col",
2253
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2254
+ Checkbox,
832
2255
  {
833
- type: "button",
834
- className: cn(
835
- "fractal-sortable-table-sort-button",
836
- isActive && "fractal-sortable-table-sort-button--active"
837
- ),
838
- onClick: () => handleSortClick(col.key),
839
- "aria-sort": isActive ? activeDirection === "asc" ? "ascending" : "descending" : "none",
840
- children: [
841
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: col.header }),
842
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "fractal-sortable-table-sort-icon", "aria-hidden": "true", children: isActive && activeDirection === "asc" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_fractal_icons3.ArrowUp, { size: 14 }) : isActive && activeDirection === "desc" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_fractal_icons3.ArrowDown, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_fractal_icons3.ArrowsUpDown, { size: 14 }) })
843
- ]
2256
+ "aria-label": selection.selectAllLabel ?? "Select all rows",
2257
+ checked: allRowsSelected,
2258
+ disabled: selectableRows.length === 0 && selectedRows.length === 0,
2259
+ indeterminate: someRowsSelected,
2260
+ onCheckedChange: (checked) => {
2261
+ const rows = checked ? selectableRows : selectedRows;
2262
+ if (selection.onSelectAllChange) {
2263
+ selection.onSelectAllChange(rows, checked);
2264
+ return;
2265
+ }
2266
+ rows.forEach((row) => {
2267
+ if (selection.isSelected(row) !== checked) {
2268
+ selection.onSelectedChange(row, checked);
2269
+ }
2270
+ });
2271
+ }
844
2272
  }
845
2273
  )
846
- },
847
- col.key
848
- );
849
- }) }) }),
850
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tbody", { children: sorted.map((row, rowIndex) => {
2274
+ }
2275
+ ),
2276
+ columns.map((col, columnIndex) => {
2277
+ const isActive = activeKey === col.key && activeDirection !== null;
2278
+ if (!col.sortable) {
2279
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2280
+ "th",
2281
+ {
2282
+ className: "fractal-sortable-table-th",
2283
+ scope: "col",
2284
+ style: columnStyle(col),
2285
+ "data-pinned-column": columnIndex === 0 ? "data" : void 0,
2286
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TableCellContent, { label: `${col.header} column`, children: col.header })
2287
+ },
2288
+ col.key
2289
+ );
2290
+ }
2291
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2292
+ "th",
2293
+ {
2294
+ className: "fractal-sortable-table-th fractal-sortable-table-th--sortable",
2295
+ scope: "col",
2296
+ style: columnStyle(col),
2297
+ "data-pinned-column": columnIndex === 0 ? "data" : void 0,
2298
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TableCellContent, { label: `${col.header} column`, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
2299
+ "button",
2300
+ {
2301
+ type: "button",
2302
+ className: cn(
2303
+ "fractal-sortable-table-sort-button",
2304
+ isActive && "fractal-sortable-table-sort-button--active"
2305
+ ),
2306
+ onClick: () => handleSortClick(col.key),
2307
+ "aria-sort": isActive ? activeDirection === "asc" ? "ascending" : "descending" : "none",
2308
+ children: [
2309
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { children: col.header }),
2310
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "fractal-sortable-table-sort-icon", "aria-hidden": "true", children: isActive && activeDirection === "asc" ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_fractal_icons12.ArrowUp, { size: 14 }) : isActive && activeDirection === "desc" ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_fractal_icons12.ArrowDown, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_fractal_icons12.ArrowsUpDown, { size: 14 }) })
2311
+ ]
2312
+ }
2313
+ ) })
2314
+ },
2315
+ col.key
2316
+ );
2317
+ }),
2318
+ overflowMenu && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("th", { scope: "col", "aria-label": "Row actions", className: "fractal-sortable-table-th fractal-table-overflow-cell", "data-pinned-column": "overflow" })
2319
+ ] }) }),
2320
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("tbody", { children: visibleRows.map((row, rowIndex) => {
851
2321
  const highlighted = highlightRow?.(row, rowIndex) ?? false;
852
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2322
+ const disabled = isRowDisabled?.(row) ?? false;
2323
+ const selectionDisabled = disabled || (isRowSelectionDisabled?.(row) ?? false);
2324
+ const interactive = !disabled && Boolean(onRowClick || onRowDoubleClick);
2325
+ const action = !disabled && rowAction ? {
2326
+ label: rowAction.label(row, rowIndex),
2327
+ icon: rowAction.icon(row, rowIndex)
2328
+ } : null;
2329
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
853
2330
  "tr",
854
2331
  {
855
2332
  className: cn(
856
2333
  "fractal-sortable-table-row",
857
2334
  highlighted && "fractal-sortable-table-row--highlighted",
858
- onRowClick && "fractal-sortable-table-row--interactive"
2335
+ interactive && "fractal-sortable-table-row--interactive",
2336
+ disabled && "fractal-sortable-table-row--disabled"
859
2337
  ),
860
- onClick: onRowClick ? () => onRowClick(row, rowIndex) : void 0,
861
- onKeyDown: onRowClick ? (event) => {
2338
+ "data-disabled": disabled || void 0,
2339
+ onClick: !disabled && onRowClick ? () => onRowClick(row, rowIndex) : void 0,
2340
+ onDoubleClick: !disabled && onRowDoubleClick ? () => onRowDoubleClick(row, rowIndex) : void 0,
2341
+ onKeyDown: interactive ? (event) => {
862
2342
  if (event.key === "Enter" || event.key === " ") {
863
2343
  event.preventDefault();
864
- onRowClick(row, rowIndex);
2344
+ const activateRow = onRowClick ?? onRowDoubleClick;
2345
+ activateRow?.(row, rowIndex);
865
2346
  }
866
2347
  } : void 0,
867
- role: onRowClick ? "button" : void 0,
868
- tabIndex: onRowClick ? 0 : void 0,
869
- children: columns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
870
- "td",
871
- {
872
- className: "fractal-sortable-table-td",
873
- style: { width: col.width, textAlign: col.align ?? "left" },
874
- children: col.render(row, rowIndex)
875
- },
876
- col.key
877
- ))
2348
+ "aria-label": action?.label,
2349
+ "aria-disabled": disabled || void 0,
2350
+ role: interactive ? "button" : void 0,
2351
+ tabIndex: interactive ? 0 : void 0,
2352
+ children: [
2353
+ selection && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("td", { className: "fractal-sortable-table-td fractal-sortable-table-selection-cell", "data-pinned-column": "selection", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2354
+ Checkbox,
2355
+ {
2356
+ "aria-label": selection.getRowLabel(row),
2357
+ checked: !selectionDisabled && selection.isSelected(row),
2358
+ disabled: selectionDisabled,
2359
+ onClick: (event) => event.stopPropagation(),
2360
+ onCheckedChange: (checked) => {
2361
+ if (!selectionDisabled) selection.onSelectedChange(row, checked);
2362
+ }
2363
+ }
2364
+ ) }),
2365
+ columns.map((col, columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
2366
+ "td",
2367
+ {
2368
+ className: cn(
2369
+ "fractal-sortable-table-td",
2370
+ action && columnIndex === columns.length - 1 && "fractal-sortable-table-td--row-action"
2371
+ ),
2372
+ style: { ...columnStyle(col), textAlign: col.align ?? "left" },
2373
+ "data-column-align": col.align,
2374
+ "data-pinned-column": columnIndex === 0 ? "data" : void 0,
2375
+ children: [
2376
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TableCellContent, { label: `${col.header || "Value"}, row ${rowIndex + 1}`, children: col.render(row, rowIndex) }),
2377
+ action && columnIndex === columns.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "fractal-sortable-table-row-action", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "fractal-sortable-table-row-action__icon", children: action.icon }) })
2378
+ ]
2379
+ },
2380
+ col.key
2381
+ )),
2382
+ overflowMenu && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2383
+ "td",
2384
+ {
2385
+ className: "fractal-sortable-table-td fractal-table-overflow-cell",
2386
+ "data-pinned-column": "overflow",
2387
+ onClick: (event) => event.stopPropagation(),
2388
+ onDoubleClick: (event) => event.stopPropagation(),
2389
+ onKeyDown: (event) => event.stopPropagation(),
2390
+ children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TableOverflowMenu, { items: overflowMenu.items(row, rowIndex), label: overflowMenu.getLabel?.(row, rowIndex) ?? `Actions for row ${rowIndex + 1}`, disabled: disabled || overflowMenu.isDisabled?.(row, rowIndex), onSelect: (value) => overflowMenu.onSelect(value, row, rowIndex) })
2391
+ }
2392
+ )
2393
+ ]
878
2394
  },
879
2395
  rowIndex
880
2396
  );
881
2397
  }) })
882
- ] })
2398
+ ] }) })
883
2399
  ] }) });
884
2400
  }
885
2401
 
886
2402
  // src/components/Tabs/Tabs.tsx
887
- var import_react3 = require("react");
888
- var import_jsx_runtime14 = require("react/jsx-runtime");
2403
+ var import_react7 = require("react");
2404
+ var import_jsx_runtime27 = require("react/jsx-runtime");
889
2405
  function Tabs({
890
2406
  items,
891
2407
  defaultValue,
892
2408
  value: controlledValue,
893
2409
  onValueChange,
894
2410
  className,
895
- ariaLabel
2411
+ ariaLabel,
2412
+ divider = false,
2413
+ variant = "line"
896
2414
  }) {
897
- const reactId = (0, import_react3.useId)();
898
- const [internalValue, setInternalValue] = (0, import_react3.useState)(
2415
+ const reactId = (0, import_react7.useId)();
2416
+ const [internalValue, setInternalValue] = (0, import_react7.useState)(
899
2417
  defaultValue ?? items.find((i) => !i.disabled)?.id ?? items[0]?.id ?? ""
900
2418
  );
901
2419
  const isControlled = controlledValue !== void 0;
902
2420
  const activeId = isControlled ? controlledValue : internalValue;
903
- const tabRefs = (0, import_react3.useRef)({});
2421
+ const tabListRef = (0, import_react7.useRef)(null);
2422
+ const tabRefs = (0, import_react7.useRef)({});
2423
+ const [indicator, setIndicator] = (0, import_react7.useState)(null);
2424
+ (0, import_react7.useLayoutEffect)(() => {
2425
+ const updateIndicator = () => {
2426
+ const activeTab = tabRefs.current[activeId];
2427
+ if (!activeTab) return;
2428
+ setIndicator({ offset: activeTab.offsetLeft, width: activeTab.offsetWidth });
2429
+ };
2430
+ updateIndicator();
2431
+ const list = tabListRef.current;
2432
+ if (!list || typeof ResizeObserver === "undefined") return;
2433
+ const observer = new ResizeObserver(updateIndicator);
2434
+ observer.observe(list);
2435
+ return () => observer.disconnect();
2436
+ }, [activeId, items.length]);
904
2437
  const selectTab = (id) => {
905
2438
  if (isControlled) {
906
2439
  onValueChange?.(id);
@@ -910,6 +2443,10 @@ function Tabs({
910
2443
  }
911
2444
  };
912
2445
  const focusable = items.filter((i) => !i.disabled);
2446
+ const indicatorStyle = indicator ? {
2447
+ "--fractal-tabs-indicator-offset": `${indicator.offset}px`,
2448
+ "--fractal-tabs-indicator-width": `${indicator.width}px`
2449
+ } : void 0;
913
2450
  const onKeyDown = (event) => {
914
2451
  const currentIndex = focusable.findIndex((i) => i.id === activeId);
915
2452
  if (currentIndex === -1) return;
@@ -935,41 +2472,52 @@ function Tabs({
935
2472
  selectTab(nextId2);
936
2473
  requestAnimationFrame(() => tabRefs.current[nextId2]?.focus());
937
2474
  };
938
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: cn("fractal-tabs", className), children: [
939
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { role: "tablist", "aria-label": ariaLabel, className: "fractal-tabs-list", children: items.map((item) => {
940
- const isActive = item.id === activeId;
941
- const tabId = `${reactId}-tab-${item.id}`;
942
- const panelId = `${reactId}-panel-${item.id}`;
943
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
944
- "button",
945
- {
946
- ref: (el) => {
947
- tabRefs.current[item.id] = el;
948
- },
949
- id: tabId,
950
- role: "tab",
951
- type: "button",
952
- "aria-selected": isActive,
953
- "aria-controls": panelId,
954
- tabIndex: isActive ? 0 : -1,
955
- disabled: item.disabled,
956
- className: cn(
957
- "fractal-tabs-trigger",
958
- isActive && "fractal-tabs-trigger--active",
959
- item.disabled && "fractal-tabs-trigger--disabled"
960
- ),
961
- onClick: () => !item.disabled && selectTab(item.id),
962
- onKeyDown,
963
- children: item.label
964
- },
965
- item.id
966
- );
967
- }) }),
2475
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: cn("fractal-tabs", `fractal-tabs--${variant}`, divider && "fractal-tabs--divider", className), children: [
2476
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2477
+ "div",
2478
+ {
2479
+ ref: tabListRef,
2480
+ role: "tablist",
2481
+ "aria-label": ariaLabel,
2482
+ className: "fractal-tabs-list",
2483
+ "data-indicator-ready": indicator ? "true" : void 0,
2484
+ style: indicatorStyle,
2485
+ children: items.map((item) => {
2486
+ const isActive = item.id === activeId;
2487
+ const tabId = `${reactId}-tab-${item.id}`;
2488
+ const panelId = `${reactId}-panel-${item.id}`;
2489
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2490
+ "button",
2491
+ {
2492
+ ref: (el) => {
2493
+ tabRefs.current[item.id] = el;
2494
+ },
2495
+ id: tabId,
2496
+ role: "tab",
2497
+ type: "button",
2498
+ "aria-selected": isActive,
2499
+ "aria-controls": panelId,
2500
+ tabIndex: isActive ? 0 : -1,
2501
+ disabled: item.disabled,
2502
+ className: cn(
2503
+ "fractal-tabs-trigger",
2504
+ isActive && "fractal-tabs-trigger--active",
2505
+ item.disabled && "fractal-tabs-trigger--disabled"
2506
+ ),
2507
+ onClick: () => !item.disabled && selectTab(item.id),
2508
+ onKeyDown,
2509
+ children: item.label
2510
+ },
2511
+ item.id
2512
+ );
2513
+ })
2514
+ }
2515
+ ),
968
2516
  items.map((item) => {
969
2517
  const tabId = `${reactId}-tab-${item.id}`;
970
2518
  const panelId = `${reactId}-panel-${item.id}`;
971
2519
  const isActive = item.id === activeId;
972
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2520
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
973
2521
  "div",
974
2522
  {
975
2523
  id: panelId,
@@ -985,10 +2533,112 @@ function Tabs({
985
2533
  ] });
986
2534
  }
987
2535
 
2536
+ // src/components/ContentSwitcher/ContentSwitcher.tsx
2537
+ var React15 = __toESM(require("react"), 1);
2538
+ var import_jsx_runtime28 = require("react/jsx-runtime");
2539
+ function ContentSwitcher({ items, value, onValueChange, ariaLabel, fullWidth = false, className }) {
2540
+ const switcherRef = React15.useRef(null);
2541
+ const buttonRefs = React15.useRef([]);
2542
+ const indicatorMotionReadyRef = React15.useRef(false);
2543
+ const activeIndex = items.findIndex((item) => item.value === value);
2544
+ React15.useLayoutEffect(() => {
2545
+ const switcher = switcherRef.current;
2546
+ const activeButton = buttonRefs.current[activeIndex];
2547
+ let motionFrame = 0;
2548
+ if (!switcher || !activeButton) {
2549
+ switcher?.removeAttribute("data-indicator-ready");
2550
+ switcher?.removeAttribute("data-indicator-animate");
2551
+ indicatorMotionReadyRef.current = false;
2552
+ return;
2553
+ }
2554
+ const updateIndicator = () => {
2555
+ const switcherRect = switcher.getBoundingClientRect();
2556
+ const activeButtonRect = activeButton.getBoundingClientRect();
2557
+ switcher.style.setProperty("--fractal-content-switcher-indicator-x", `${activeButtonRect.left - switcherRect.left - switcher.clientLeft}px`);
2558
+ switcher.style.setProperty("--fractal-content-switcher-indicator-y", `${activeButtonRect.top - switcherRect.top - switcher.clientTop}px`);
2559
+ switcher.style.setProperty("--fractal-content-switcher-indicator-width", `${activeButtonRect.width}px`);
2560
+ switcher.style.setProperty("--fractal-content-switcher-indicator-height", `${activeButtonRect.height}px`);
2561
+ switcher.setAttribute("data-indicator-ready", "true");
2562
+ if (!indicatorMotionReadyRef.current && motionFrame === 0) {
2563
+ motionFrame = window.requestAnimationFrame(() => {
2564
+ indicatorMotionReadyRef.current = true;
2565
+ switcher.setAttribute("data-indicator-animate", "true");
2566
+ motionFrame = 0;
2567
+ });
2568
+ }
2569
+ };
2570
+ updateIndicator();
2571
+ if (typeof ResizeObserver === "undefined") {
2572
+ return () => {
2573
+ if (motionFrame !== 0) window.cancelAnimationFrame(motionFrame);
2574
+ };
2575
+ }
2576
+ const resizeObserver = new ResizeObserver(updateIndicator);
2577
+ resizeObserver.observe(switcher);
2578
+ buttonRefs.current.forEach((button) => {
2579
+ if (button) resizeObserver.observe(button);
2580
+ });
2581
+ return () => {
2582
+ resizeObserver.disconnect();
2583
+ if (motionFrame !== 0) window.cancelAnimationFrame(motionFrame);
2584
+ };
2585
+ }, [activeIndex, items.length]);
2586
+ const moveFocus = (index, direction) => {
2587
+ let nextIndex = index;
2588
+ do {
2589
+ nextIndex = (nextIndex + direction + items.length) % items.length;
2590
+ } while (items[nextIndex]?.disabled && nextIndex !== index);
2591
+ if (!items[nextIndex]?.disabled) {
2592
+ buttonRefs.current[nextIndex]?.focus();
2593
+ onValueChange(items[nextIndex].value);
2594
+ }
2595
+ };
2596
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
2597
+ "div",
2598
+ {
2599
+ className: cn("fractal-content-switcher", fullWidth && "fractal-content-switcher--full-width", className),
2600
+ role: "group",
2601
+ "aria-label": ariaLabel,
2602
+ ref: switcherRef,
2603
+ children: [
2604
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "fractal-content-switcher__indicator", "aria-hidden": true }),
2605
+ items.map((item, index) => {
2606
+ const active = item.value === value;
2607
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2608
+ "button",
2609
+ {
2610
+ className: cn("fractal-content-switcher__item", active && "fractal-content-switcher__item--active"),
2611
+ type: "button",
2612
+ "aria-pressed": active,
2613
+ disabled: item.disabled,
2614
+ onClick: () => onValueChange(item.value),
2615
+ onKeyDown: (event) => {
2616
+ if (event.key === "ArrowRight") {
2617
+ event.preventDefault();
2618
+ moveFocus(index, 1);
2619
+ }
2620
+ if (event.key === "ArrowLeft") {
2621
+ event.preventDefault();
2622
+ moveFocus(index, -1);
2623
+ }
2624
+ },
2625
+ ref: (element) => {
2626
+ buttonRefs.current[index] = element;
2627
+ },
2628
+ children: item.label
2629
+ },
2630
+ item.value
2631
+ );
2632
+ })
2633
+ ]
2634
+ }
2635
+ );
2636
+ }
2637
+
988
2638
  // src/components/Disclosure/Disclosure.tsx
989
- var import_react4 = require("react");
990
- var import_fractal_icons4 = require("@mirror-physics/fractal-icons");
991
- var import_jsx_runtime15 = require("react/jsx-runtime");
2639
+ var import_react8 = require("react");
2640
+ var import_fractal_icons13 = require("@mirror-physics/fractal-icons");
2641
+ var import_jsx_runtime29 = require("react/jsx-runtime");
992
2642
  function Disclosure({
993
2643
  title,
994
2644
  meta,
@@ -996,16 +2646,18 @@ function Disclosure({
996
2646
  defaultOpen = false,
997
2647
  open: controlledOpen,
998
2648
  onOpenChange,
2649
+ disabled = false,
999
2650
  className,
1000
2651
  variant = "card"
1001
2652
  }) {
1002
- const reactId = (0, import_react4.useId)();
2653
+ const reactId = (0, import_react8.useId)();
1003
2654
  const headerId = `${reactId}-header`;
1004
2655
  const panelId = `${reactId}-panel`;
1005
- const [internalOpen, setInternalOpen] = (0, import_react4.useState)(defaultOpen);
2656
+ const [internalOpen, setInternalOpen] = (0, import_react8.useState)(defaultOpen);
1006
2657
  const isControlled = controlledOpen !== void 0;
1007
2658
  const isOpen = isControlled ? controlledOpen : internalOpen;
1008
2659
  const toggle = () => {
2660
+ if (disabled) return;
1009
2661
  const next = !isOpen;
1010
2662
  if (isControlled) {
1011
2663
  onOpenChange?.(next);
@@ -1014,17 +2666,18 @@ function Disclosure({
1014
2666
  onOpenChange?.(next);
1015
2667
  }
1016
2668
  };
1017
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2669
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1018
2670
  "div",
1019
2671
  {
1020
2672
  className: cn(
1021
2673
  "fractal-disclosure",
1022
2674
  variant === "card" && "fractal-disclosure--card",
1023
2675
  isOpen && "fractal-disclosure--open",
2676
+ disabled && "fractal-disclosure--disabled",
1024
2677
  className
1025
2678
  ),
1026
2679
  children: [
1027
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2680
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1028
2681
  "button",
1029
2682
  {
1030
2683
  type: "button",
@@ -1032,15 +2685,16 @@ function Disclosure({
1032
2685
  className: "fractal-disclosure-trigger",
1033
2686
  "aria-expanded": isOpen,
1034
2687
  "aria-controls": panelId,
2688
+ disabled,
1035
2689
  onClick: toggle,
1036
2690
  children: [
1037
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "fractal-disclosure-icon", "aria-hidden": "true", children: isOpen ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_fractal_icons4.ChevronDown, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_fractal_icons4.ChevronRight, { size: 16 }) }),
1038
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "fractal-disclosure-title", children: title }),
1039
- meta && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "fractal-disclosure-meta", children: meta })
2691
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "fractal-disclosure-icon", "aria-hidden": "true", children: isOpen ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_fractal_icons13.ChevronDown, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_fractal_icons13.ChevronRight, { size: 16 }) }),
2692
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "fractal-disclosure-title", children: title }),
2693
+ meta && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "fractal-disclosure-meta", children: meta })
1040
2694
  ]
1041
2695
  }
1042
2696
  ),
1043
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2697
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1044
2698
  "div",
1045
2699
  {
1046
2700
  id: panelId,
@@ -1048,7 +2702,7 @@ function Disclosure({
1048
2702
  "aria-labelledby": headerId,
1049
2703
  className: "fractal-disclosure-panel",
1050
2704
  hidden: !isOpen,
1051
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "fractal-disclosure-panel-inner", children })
2705
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "fractal-disclosure-panel-inner", children })
1052
2706
  }
1053
2707
  )
1054
2708
  ]
@@ -1056,39 +2710,260 @@ function Disclosure({
1056
2710
  );
1057
2711
  }
1058
2712
 
2713
+ // src/components/LatticeLoader/LatticeLoader.tsx
2714
+ var import_jsx_runtime30 = require("react/jsx-runtime");
2715
+ var LATTICE_COUNT = 160;
2716
+ var WAVE_PERIOD = 80;
2717
+ function LatticeLoader({ className, label = "Loading", ...props }) {
2718
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
2719
+ "span",
2720
+ {
2721
+ "aria-label": label,
2722
+ className: cn("fractal-lattice-loader", className),
2723
+ role: "progressbar",
2724
+ ...props,
2725
+ children: Array.from({ length: LATTICE_COUNT }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
2726
+ "span",
2727
+ {
2728
+ "aria-hidden": "true",
2729
+ className: "fractal-lattice-loader__cell",
2730
+ style: { "--fractal-lattice-index": index % WAVE_PERIOD }
2731
+ },
2732
+ index
2733
+ ))
2734
+ }
2735
+ );
2736
+ }
2737
+
2738
+ // src/components/Ghost/Ghost.tsx
2739
+ var import_jsx_runtime31 = require("react/jsx-runtime");
2740
+ var toCssLength = (value) => typeof value === "number" ? `${value}px` : value;
2741
+ function Ghost({ className, width, height, radius, style, ...props }) {
2742
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2743
+ "div",
2744
+ {
2745
+ "aria-hidden": "true",
2746
+ className: cn("fractal-ghost", className),
2747
+ style: {
2748
+ width: toCssLength(width),
2749
+ height: toCssLength(height),
2750
+ borderRadius: toCssLength(radius),
2751
+ ...style
2752
+ },
2753
+ ...props
2754
+ }
2755
+ );
2756
+ }
2757
+ function GhostLine({ className, size = "md", width = "100%", ...props }) {
2758
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Ghost, { className: cn("fractal-ghost-line", `fractal-ghost-line--${size}`, className), width, ...props });
2759
+ }
2760
+ function ButtonGhost({ className, size = "lg", controlSize, iconOnly = false, width, ...props }) {
2761
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Ghost, { "data-control-size": controlSize ?? size, className: cn("fractal-ghost-button", `fractal-ghost-button--${size}`, iconOnly && "fractal-ghost-button--icon-only", className), width, ...props });
2762
+ }
2763
+ function CardGhost({ className, lines = 3, showHeader = true, ...props }) {
2764
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-card", className), ...props, children: [
2765
+ showHeader && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: "38%" }),
2766
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "fractal-ghost-stack", children: Array.from({ length: lines }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: index === lines - 1 ? "62%" : "100%" }, index)) })
2767
+ ] });
2768
+ }
2769
+ function DataTableGhost({
2770
+ className,
2771
+ showHeader = true,
2772
+ headers,
2773
+ columns = 4,
2774
+ rows = 5,
2775
+ columnWidths,
2776
+ size = "md",
2777
+ ...props
2778
+ }) {
2779
+ const cells = Array.from({ length: columns }, (_, index) => index);
2780
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { "aria-busy": "true", "aria-label": "Loading table", className: cn("fractal-ghost-table", `fractal-ghost-table--${size}`, className), role: "status", ...props, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("table", { className: "fractal-ghost-table__table", children: [
2781
+ (showHeader || headers) && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("tr", { className: "fractal-ghost-table__head-row", children: cells.map((index) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("th", { style: { width: columnWidths?.[index] }, children: headers?.[index] ?? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { size: "sm", width: index === columns - 1 ? "62%" : "76%" }) }, index)) }) }),
2782
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("tbody", { children: Array.from({ length: rows }, (_, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("tr", { className: "fractal-ghost-table__row", children: cells.map((columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("td", { style: { width: columnWidths?.[columnIndex] }, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: `${[72, 54, 82, 63, 46][(rowIndex + columnIndex) % 5]}%` }) }, columnIndex)) }, rowIndex)) })
2783
+ ] }) });
2784
+ }
2785
+ function InputGhost({ className, labelWidth = "26%", ...props }) {
2786
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(FormFieldGhost, { className, labelWidth, ...props });
2787
+ }
2788
+ function TextareaGhost({ className, labelWidth = "26%", ...props }) {
2789
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(FormFieldGhost, { className, labelWidth, multiline: true, ...props });
2790
+ }
2791
+ function LabelGhost({ className, width = "26%", ...props }) {
2792
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { className: cn("fractal-ghost-label", className), size: "sm", width, ...props });
2793
+ }
2794
+ function FormFieldGhost({ className, labelWidth = "26%", multiline = false, controlSize = "lg", ...props }) {
2795
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { "aria-hidden": "true", "data-control-size": controlSize, className: cn("fractal-ghost-field", className), ...props, children: [
2796
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(LabelGhost, { width: labelWidth }),
2797
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Ghost, { className: cn("fractal-ghost-field__control", multiline && "fractal-ghost-field__control--multiline") })
2798
+ ] });
2799
+ }
2800
+ function AlertGhost({ className, ...props }) {
2801
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-alert", className), ...props, children: [
2802
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: "30%" }),
2803
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { size: "sm", width: "78%" })
2804
+ ] });
2805
+ }
2806
+ function CheckboxGhost({ className, ...props }) {
2807
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-choice", className), ...props, children: [
2808
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Ghost, { className: "fractal-ghost-choice__control" }),
2809
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: "42%" })
2810
+ ] });
2811
+ }
2812
+ var ToggleGhost = CheckboxGhost;
2813
+ function ChipGhost({ className, width = 74, ...props }) {
2814
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Ghost, { className: cn("fractal-ghost-chip", className), height: 24, width, ...props });
2815
+ }
2816
+ function LinkGhost({ className, width = 96, ...props }) {
2817
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { className: cn("fractal-ghost-link", className), size: "sm", width, ...props });
2818
+ }
2819
+ var TextButtonGhost = LinkGhost;
2820
+ function DropdownGhost({ className, controlSize = "lg", ...props }) {
2821
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { "aria-hidden": "true", "data-control-size": controlSize, className: cn("fractal-ghost-dropdown", className), ...props, children: [
2822
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: "38%" }),
2823
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Ghost, { className: "fractal-ghost-dropdown__chevron" })
2824
+ ] });
2825
+ }
2826
+ function ContentSwitcherGhost({ className, options = 3, ...props }) {
2827
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { "aria-hidden": "true", className: cn("fractal-ghost-switcher", className), ...props, children: Array.from({ length: options }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Ghost, {}, index)) });
2828
+ }
2829
+ var TabsGhost = ContentSwitcherGhost;
2830
+ function DisclosureGhost({ className, ...props }) {
2831
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-disclosure", className), ...props, children: [
2832
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Ghost, { className: "fractal-ghost-disclosure__icon" }),
2833
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: "42%" })
2834
+ ] });
2835
+ }
2836
+ function PaginationGhost({ className, pages = 5, ...props }) {
2837
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { "aria-hidden": "true", className: cn("fractal-ghost-pagination", className), ...props, children: Array.from({ length: pages }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Ghost, {}, index)) });
2838
+ }
2839
+ function FileDropzoneGhost({ className, ...props }) {
2840
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-dropzone", className), ...props, children: [
2841
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Ghost, { className: "fractal-ghost-dropzone__icon" }),
2842
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: "26%" }),
2843
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { size: "sm", width: "46%" })
2844
+ ] });
2845
+ }
2846
+ function PromptCardGhost({ className, ...props }) {
2847
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(CardGhost, { className: cn("fractal-ghost-prompt-card", className), lines: 4, ...props });
2848
+ }
2849
+ function ConversationGhost({
2850
+ className,
2851
+ "aria-label": ariaLabel = "Loading conversation",
2852
+ ...props
2853
+ }) {
2854
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
2855
+ "div",
2856
+ {
2857
+ "aria-busy": "true",
2858
+ "aria-label": ariaLabel,
2859
+ className: cn("fractal-ghost-conversation", className),
2860
+ role: "status",
2861
+ ...props,
2862
+ children: [
2863
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { "aria-hidden": "true", className: "fractal-ghost-conversation__message fractal-ghost-conversation__message--user", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "fractal-ghost-conversation__bubble", children: [
2864
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: "100%" }),
2865
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: "86%" }),
2866
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: "64%" })
2867
+ ] }) }),
2868
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { "aria-hidden": "true", className: "fractal-ghost-conversation__message fractal-ghost-conversation__message--assistant", children: [
2869
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: "92%" }),
2870
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: "78%" }),
2871
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: "58%" })
2872
+ ] })
2873
+ ]
2874
+ }
2875
+ );
2876
+ }
2877
+ function SortableTableGhost(props) {
2878
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(DataTableGhost, { ...props });
2879
+ }
2880
+ function SidebarGhost({ className, items = 6, ...props }) {
2881
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("aside", { "aria-hidden": "true", className: cn("fractal-ghost-sidebar", className), ...props, children: [
2882
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Ghost, { className: "fractal-ghost-sidebar__brand" }),
2883
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "fractal-ghost-sidebar__items", children: Array.from({ length: items }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "fractal-ghost-sidebar__item", children: [
2884
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Ghost, {}),
2885
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: `${[54, 68, 44, 61][index % 4]}%` })
2886
+ ] }, index)) })
2887
+ ] });
2888
+ }
2889
+ function ModalGhost({ className, title, lines = 3, ...props }) {
2890
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SurfaceGhost, { className: cn("fractal-ghost-modal", className), title, lines, ...props });
2891
+ }
2892
+ function SidePanelGhost({ className, title, lines = 5, ...props }) {
2893
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SurfaceGhost, { className: cn("fractal-ghost-side-panel", className), title, lines, ...props });
2894
+ }
2895
+ function SurfaceGhost({ className, title, lines = 3, ...props }) {
2896
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-surface", className), ...props, children: [
2897
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "fractal-ghost-surface__header", children: [
2898
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: "42%" }),
2899
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Ghost, { className: "fractal-ghost-surface__close" })
2900
+ ] }),
2901
+ title && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "fractal-ghost-surface__title", children: title }),
2902
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "fractal-ghost-stack", children: Array.from({ length: lines }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GhostLine, { width: index === lines - 1 ? "64%" : "100%" }, index)) })
2903
+ ] });
2904
+ }
2905
+
1059
2906
  // src/components/Sidebar/Sidebar.tsx
1060
- var React10 = __toESM(require("react"), 1);
1061
- var import_jsx_runtime16 = require("react/jsx-runtime");
1062
- var Sidebar = React10.forwardRef(
1063
- ({ className, width, style, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2907
+ var React16 = __toESM(require("react"), 1);
2908
+ var import_jsx_runtime32 = require("react/jsx-runtime");
2909
+ var Sidebar = React16.forwardRef(
2910
+ ({ className, width, compact = false, style, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1064
2911
  "aside",
1065
2912
  {
1066
2913
  ref,
1067
2914
  className: cn("fractal-sidebar", className),
2915
+ "data-compact": compact || void 0,
1068
2916
  style: { ...style, ...width ? { "--fractal-sidebar-width": width } : {} },
1069
2917
  ...props
1070
2918
  }
1071
2919
  )
1072
2920
  );
1073
2921
  Sidebar.displayName = "Sidebar";
1074
- var SidebarHeader = React10.forwardRef(
1075
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref, className: cn("fractal-sidebar__header", className), ...props })
2922
+ var SidebarHeader = React16.forwardRef(
2923
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { ref, className: cn("fractal-sidebar__header", className), ...props })
1076
2924
  );
1077
2925
  SidebarHeader.displayName = "SidebarHeader";
1078
- var SidebarBrand = React10.forwardRef(
1079
- ({ className, type = "button", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { ref, type, className: cn("fractal-sidebar__brand", className), ...props })
2926
+ var SidebarBrand = React16.forwardRef(
2927
+ ({ className, type = "button", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("button", { ref, type, className: cn("fractal-sidebar__brand", className), ...props })
1080
2928
  );
1081
2929
  SidebarBrand.displayName = "SidebarBrand";
1082
- var SidebarNav = React10.forwardRef(
1083
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("nav", { ref, className: cn("fractal-sidebar__nav", className), ...props })
2930
+ function SidebarAccountMenu({
2931
+ name,
2932
+ items,
2933
+ onSelect,
2934
+ triggerLabel = "Account menu",
2935
+ icon,
2936
+ className
2937
+ }) {
2938
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2939
+ Dropdown,
2940
+ {
2941
+ align: "left",
2942
+ className: cn("fractal-sidebar__account", className),
2943
+ items,
2944
+ onSelect,
2945
+ panelClassName: "fractal-sidebar__account-panel",
2946
+ selectedValue: "",
2947
+ triggerClassName: "fractal-sidebar__account-trigger",
2948
+ triggerContent: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
2949
+ icon && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "fractal-sidebar__account-icon", "aria-hidden": "true", children: icon }),
2950
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "fractal-sidebar__account-name", children: name })
2951
+ ] }),
2952
+ triggerLabel,
2953
+ triggerVariant: "tertiary"
2954
+ }
2955
+ );
2956
+ }
2957
+ var SidebarNav = React16.forwardRef(
2958
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("nav", { ref, className: cn("fractal-sidebar__nav", className), ...props })
1084
2959
  );
1085
2960
  SidebarNav.displayName = "SidebarNav";
1086
- var SidebarSection = React10.forwardRef(
1087
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref, className: cn("fractal-sidebar__section", className), ...props })
2961
+ var SidebarSection = React16.forwardRef(
2962
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { ref, className: cn("fractal-sidebar__section", className), ...props })
1088
2963
  );
1089
2964
  SidebarSection.displayName = "SidebarSection";
1090
- var SidebarNavItem = React10.forwardRef(
1091
- ({ active = false, icon, endAdornment, className, type = "button", children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2965
+ var SidebarNavItem = React16.forwardRef(
2966
+ ({ active = false, icon, endAdornment, className, type = "button", children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
1092
2967
  "button",
1093
2968
  {
1094
2969
  ref,
@@ -1098,28 +2973,60 @@ var SidebarNavItem = React10.forwardRef(
1098
2973
  "aria-current": active ? "page" : void 0,
1099
2974
  ...props,
1100
2975
  children: [
1101
- icon && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "fractal-sidebar__nav-item-icon", "aria-hidden": "true", children: icon }),
1102
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "fractal-sidebar__nav-item-label", children }),
1103
- endAdornment && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "fractal-sidebar__nav-item-end", children: endAdornment })
2976
+ icon && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "fractal-sidebar__nav-item-icon", "aria-hidden": "true", children: icon }),
2977
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "fractal-sidebar__nav-item-label", children }),
2978
+ endAdornment && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "fractal-sidebar__nav-item-end", children: endAdornment })
1104
2979
  ]
1105
2980
  }
1106
2981
  )
1107
2982
  );
1108
2983
  SidebarNavItem.displayName = "SidebarNavItem";
1109
- var SidebarFooter = React10.forwardRef(
1110
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref, className: cn("fractal-sidebar__footer", className), ...props })
2984
+ var SidebarFooter = React16.forwardRef(
2985
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { ref, className: cn("fractal-sidebar__footer", className), ...props })
1111
2986
  );
1112
2987
  SidebarFooter.displayName = "SidebarFooter";
1113
2988
 
1114
2989
  // src/components/SidePanel/SidePanel.tsx
1115
- var import_react_dom2 = require("react-dom");
1116
- var import_fractal_icons5 = require("@mirror-physics/fractal-icons");
1117
- var import_jsx_runtime17 = require("react/jsx-runtime");
1118
- function SidePanel({ open, onClose, title, description, children, footer, size = "md", className }) {
2990
+ var React17 = __toESM(require("react"), 1);
2991
+ var import_react_dom4 = require("react-dom");
2992
+ var import_fractal_icons14 = require("@mirror-physics/fractal-icons");
2993
+ var import_jsx_runtime33 = require("react/jsx-runtime");
2994
+ function SidePanel({ open, onClose, title, description, children, footer, size = "md", loading = false, loadingLabel = "Loading", className }) {
1119
2995
  useEscapeDismiss(80, onClose, open);
2996
+ const bodyRef = React17.useRef(null);
2997
+ const [scrollEdges, setScrollEdges] = React17.useState({ above: false, below: false });
2998
+ React17.useLayoutEffect(() => {
2999
+ const body = bodyRef.current;
3000
+ if (!open || !body) return;
3001
+ const measure = () => {
3002
+ const maxScroll = Math.max(0, body.scrollHeight - body.clientHeight);
3003
+ const top = Math.max(0, Math.min(body.scrollTop, maxScroll));
3004
+ const above = maxScroll > 1 && top > 0;
3005
+ const below = maxScroll - top > 1;
3006
+ setScrollEdges((previous) => previous.above === above && previous.below === below ? previous : { above, below });
3007
+ };
3008
+ const resize = new ResizeObserver(measure);
3009
+ const observeContent = () => {
3010
+ resize.disconnect();
3011
+ resize.observe(body);
3012
+ for (const child of body.children) resize.observe(child);
3013
+ measure();
3014
+ };
3015
+ const mutations = new MutationObserver(observeContent);
3016
+ mutations.observe(body, { childList: true, subtree: true, characterData: true, attributes: true });
3017
+ body.addEventListener("scroll", measure, { passive: true });
3018
+ body.addEventListener("load", measure, true);
3019
+ observeContent();
3020
+ return () => {
3021
+ resize.disconnect();
3022
+ mutations.disconnect();
3023
+ body.removeEventListener("scroll", measure);
3024
+ body.removeEventListener("load", measure, true);
3025
+ };
3026
+ }, [open, loading]);
1120
3027
  if (!open) return null;
1121
- return (0, import_react_dom2.createPortal)(
1122
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "fractal-side-panel-overlay", onMouseDown: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3028
+ return (0, import_react_dom4.createPortal)(
3029
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "fractal-side-panel-overlay", onMouseDown: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
1123
3030
  "aside",
1124
3031
  {
1125
3032
  className: cn("fractal-side-panel", `fractal-side-panel--${size}`, className),
@@ -1128,15 +3035,15 @@ function SidePanel({ open, onClose, title, description, children, footer, size =
1128
3035
  role: "dialog",
1129
3036
  onMouseDown: (event) => event.stopPropagation(),
1130
3037
  children: [
1131
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("header", { className: "fractal-side-panel__header", children: [
1132
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "fractal-side-panel__heading", children: [
1133
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h2", { className: "fractal-side-panel__title", children: title }),
1134
- description && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "fractal-side-panel__description", children: description })
3038
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("header", { className: "fractal-side-panel__header", "data-scroll-divider": scrollEdges.above, children: [
3039
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "fractal-side-panel__heading", children: [
3040
+ typeof title === "string" ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("h2", { className: "fractal-side-panel__title", children: title }) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "fractal-side-panel__title", children: title }),
3041
+ description && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "fractal-side-panel__description", children: description })
1135
3042
  ] }),
1136
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "fractal-side-panel__close", "aria-label": "Close panel", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_fractal_icons5.Close, { size: 18 }) })
3043
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("button", { type: "button", className: "fractal-side-panel__close", "aria-label": "Close panel", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_fractal_icons14.Close, { size: 18 }) })
1137
3044
  ] }),
1138
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "fractal-side-panel__body", children }),
1139
- footer && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("footer", { className: "fractal-side-panel__footer", children: footer })
3045
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { ref: bodyRef, "aria-busy": loading || void 0, className: cn("fractal-side-panel__body", loading && "fractal-side-panel__body--loading"), children: loading ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(UILoader, { label: loadingLabel }) : children }),
3046
+ footer && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("footer", { className: "fractal-side-panel__footer", "data-scroll-divider": scrollEdges.below, children: footer })
1140
3047
  ]
1141
3048
  }
1142
3049
  ) }),
@@ -1145,17 +3052,57 @@ function SidePanel({ open, onClose, title, description, children, footer, size =
1145
3052
  }
1146
3053
 
1147
3054
  // src/components/Textarea/Textarea.tsx
1148
- var React11 = __toESM(require("react"), 1);
1149
- var import_jsx_runtime18 = require("react/jsx-runtime");
1150
- var Textarea = React11.forwardRef(
1151
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("textarea", { ref, className: cn("fractal-textarea", className), ...props })
3055
+ var React18 = __toESM(require("react"), 1);
3056
+ var import_jsx_runtime34 = require("react/jsx-runtime");
3057
+ var Textarea = React18.forwardRef(
3058
+ ({ className, fieldLabel, textStyle = "source", id, placeholder, ...props }, ref) => {
3059
+ const generatedId = React18.useId();
3060
+ const fieldId = id ?? generatedId;
3061
+ const input = /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
3062
+ "textarea",
3063
+ {
3064
+ ref,
3065
+ id: fieldLabel ? fieldId : id,
3066
+ placeholder: fieldLabel ? placeholder || " " : placeholder,
3067
+ className: cn("fractal-textarea", className),
3068
+ "data-text-style": textStyle,
3069
+ ...props
3070
+ }
3071
+ );
3072
+ return fieldLabel ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "fractal-floating-field fractal-floating-field--textarea", "data-control-size": "md", children: [
3073
+ input,
3074
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(FloatingFieldBorder, { label: fieldLabel ?? "" }),
3075
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("label", { className: "fractal-floating-field__label", htmlFor: fieldId, children: fieldLabel })
3076
+ ] }) : input;
3077
+ }
1152
3078
  );
1153
3079
  Textarea.displayName = "Textarea";
1154
3080
 
3081
+ // src/components/TextButton/TextButton.tsx
3082
+ var React19 = __toESM(require("react"), 1);
3083
+ var import_jsx_runtime35 = require("react/jsx-runtime");
3084
+ var TextButton = React19.forwardRef(
3085
+ ({ className, icon, iconPosition = "start", size = "md", tone = "primary", children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
3086
+ "button",
3087
+ {
3088
+ ref,
3089
+ className: cn("fractal-text-button", `fractal-text-button--${size}`, `fractal-text-button--${tone}`, className),
3090
+ type: "button",
3091
+ ...props,
3092
+ children: [
3093
+ icon && iconPosition === "start" && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "fractal-text-button__icon", "aria-hidden": "true", children: icon }),
3094
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { children }),
3095
+ icon && iconPosition === "end" && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "fractal-text-button__icon", "aria-hidden": "true", children: icon })
3096
+ ]
3097
+ }
3098
+ )
3099
+ );
3100
+ TextButton.displayName = "TextButton";
3101
+
1155
3102
  // src/components/FileDropzone/FileDropzone.tsx
1156
- var React12 = __toESM(require("react"), 1);
1157
- var import_fractal_icons6 = require("@mirror-physics/fractal-icons");
1158
- var import_jsx_runtime19 = require("react/jsx-runtime");
3103
+ var React20 = __toESM(require("react"), 1);
3104
+ var import_fractal_icons15 = require("@mirror-physics/fractal-icons");
3105
+ var import_jsx_runtime36 = require("react/jsx-runtime");
1159
3106
  function FileDropzone({
1160
3107
  files,
1161
3108
  onFilesChange,
@@ -1166,17 +3113,17 @@ function FileDropzone({
1166
3113
  description = "Files stay attached to this record.",
1167
3114
  className
1168
3115
  }) {
1169
- const inputId = React12.useId();
1170
- const inputRef = React12.useRef(null);
1171
- const [isDragging, setIsDragging] = React12.useState(false);
3116
+ const inputId = React20.useId();
3117
+ const inputRef = React20.useRef(null);
3118
+ const [isDragging, setIsDragging] = React20.useState(false);
1172
3119
  const addFiles = (nextFiles) => {
1173
3120
  if (disabled) return;
1174
3121
  const additions = Array.from(nextFiles);
1175
3122
  if (additions.length === 0) return;
1176
3123
  onFilesChange(multiple ? [...files, ...additions] : additions.slice(0, 1));
1177
3124
  };
1178
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: cn("fractal-file-dropzone", className), children: [
1179
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3125
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: cn("fractal-file-dropzone", className), children: [
3126
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
1180
3127
  "input",
1181
3128
  {
1182
3129
  ref: inputRef,
@@ -1192,7 +3139,7 @@ function FileDropzone({
1192
3139
  }
1193
3140
  }
1194
3141
  ),
1195
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
3142
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
1196
3143
  "button",
1197
3144
  {
1198
3145
  className: "fractal-file-dropzone__target",
@@ -1215,18 +3162,18 @@ function FileDropzone({
1215
3162
  addFiles(event.dataTransfer.files);
1216
3163
  },
1217
3164
  children: [
1218
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_fractal_icons6.FileArrowUp, { "aria-hidden": "true", size: 20 }),
1219
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "fractal-file-dropzone__copy", children: [
1220
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "fractal-file-dropzone__title", children: title }),
1221
- description && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "fractal-file-dropzone__description", children: description })
3165
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_fractal_icons15.FileArrowUp, { "aria-hidden": "true", size: 20 }),
3166
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { className: "fractal-file-dropzone__copy", children: [
3167
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "fractal-file-dropzone__title", children: title }),
3168
+ description && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "fractal-file-dropzone__description", children: description })
1222
3169
  ] })
1223
3170
  ]
1224
3171
  }
1225
3172
  ),
1226
- files.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ul", { className: "fractal-file-dropzone__files", "aria-label": "Attached files", children: files.map((file, index) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("li", { className: "fractal-file-dropzone__file", children: [
1227
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "fractal-file-dropzone__file-name", children: file.name }),
1228
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "fractal-file-dropzone__file-meta", children: formatFileSize(file.size) }),
1229
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3173
+ files.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { className: "fractal-file-dropzone__files", "aria-label": "Attached files", children: files.map((file, index) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("li", { className: "fractal-file-dropzone__file", children: [
3174
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "fractal-file-dropzone__file-name", children: file.name }),
3175
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "fractal-file-dropzone__file-meta", children: formatFileSize(file.size) }),
3176
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
1230
3177
  "button",
1231
3178
  {
1232
3179
  className: "fractal-file-dropzone__remove",
@@ -1234,7 +3181,7 @@ function FileDropzone({
1234
3181
  "aria-label": `Remove ${file.name}`,
1235
3182
  disabled,
1236
3183
  onClick: () => onFilesChange(files.filter((_, fileIndex) => fileIndex !== index)),
1237
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_fractal_icons6.Close, { "aria-hidden": "true", size: 14 })
3184
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_fractal_icons15.Close, { "aria-hidden": "true", size: 14 })
1238
3185
  }
1239
3186
  )
1240
3187
  ] }, `${file.name}-${file.size}-${index}`)) })
@@ -1245,43 +3192,373 @@ function formatFileSize(bytes) {
1245
3192
  if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
1246
3193
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1247
3194
  }
3195
+
3196
+ // src/components/Pagination/Pagination.tsx
3197
+ var import_fractal_icons16 = require("@mirror-physics/fractal-icons");
3198
+ var import_jsx_runtime37 = require("react/jsx-runtime");
3199
+ function Pagination({
3200
+ page,
3201
+ pageSize,
3202
+ totalItems,
3203
+ onPageChange,
3204
+ pageSizeOptions,
3205
+ onPageSizeChange,
3206
+ ariaLabel = "Pagination",
3207
+ className
3208
+ }) {
3209
+ const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
3210
+ const currentPage = Math.min(Math.max(page, 1), totalPages);
3211
+ const start = totalItems === 0 ? 0 : (currentPage - 1) * pageSize + 1;
3212
+ const end = Math.min(currentPage * pageSize, totalItems);
3213
+ const canChangePageSize = pageSizeOptions != null && pageSizeOptions.length > 0 && onPageSizeChange != null;
3214
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("nav", { className: cn("fractal-pagination", className), "aria-label": ariaLabel, children: [
3215
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "fractal-pagination__summary", children: totalItems === 0 ? "No results" : `${start}-${end} of ${totalItems}` }),
3216
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "fractal-pagination__controls", children: [
3217
+ canChangePageSize && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3218
+ Dropdown,
3219
+ {
3220
+ align: "right",
3221
+ className: "fractal-pagination__page-size",
3222
+ direction: "up",
3223
+ items: pageSizeOptions.map((option) => ({
3224
+ value: String(option),
3225
+ description: `${option} per page`
3226
+ })),
3227
+ onSelect: (value) => onPageSizeChange(Number(value)),
3228
+ selectedValue: String(pageSize),
3229
+ size: "sm",
3230
+ triggerLabel: "Results per page",
3231
+ triggerVariant: "tertiary"
3232
+ }
3233
+ ),
3234
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3235
+ "button",
3236
+ {
3237
+ className: "fractal-pagination__button",
3238
+ type: "button",
3239
+ "aria-label": "Previous page",
3240
+ title: "Previous page",
3241
+ disabled: currentPage === 1 || totalItems === 0,
3242
+ onClick: () => onPageChange(currentPage - 1),
3243
+ children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_fractal_icons16.ArrowLeft, { "aria-hidden": "true", size: 16 })
3244
+ }
3245
+ ),
3246
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "fractal-pagination__page", "aria-current": "page", children: [
3247
+ currentPage,
3248
+ " of ",
3249
+ totalPages
3250
+ ] }),
3251
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
3252
+ "button",
3253
+ {
3254
+ className: "fractal-pagination__button",
3255
+ type: "button",
3256
+ "aria-label": "Next page",
3257
+ title: "Next page",
3258
+ disabled: currentPage === totalPages || totalItems === 0,
3259
+ onClick: () => onPageChange(currentPage + 1),
3260
+ children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_fractal_icons16.ArrowRight, { "aria-hidden": "true", size: 16 })
3261
+ }
3262
+ )
3263
+ ] })
3264
+ ] });
3265
+ }
3266
+
3267
+ // src/components/ColumnEditor/ColumnEditor.tsx
3268
+ var React21 = __toESM(require("react"), 1);
3269
+ var import_fractal_icons17 = require("@mirror-physics/fractal-icons");
3270
+ var import_jsx_runtime38 = require("react/jsx-runtime");
3271
+ var equal = (a, b) => a.length === b.length && a.every((id, index) => id === b[index]);
3272
+ function ColumnEditor({ items, value, onChange, label = "Edit columns", hiddenLabel = "Hidden columns", className }) {
3273
+ const [open, setOpen] = React21.useState(false);
3274
+ const [preview, setPreview] = React21.useState(null);
3275
+ const [announcement, setAnnouncement] = React21.useState("");
3276
+ const root = React21.useRef(null);
3277
+ const trigger = React21.useRef(null);
3278
+ const panel = React21.useRef(null);
3279
+ const drag = React21.useRef(null);
3280
+ const positions = React21.useRef(null);
3281
+ const id = React21.useId();
3282
+ function capturePositions() {
3283
+ positions.current = new Map(Array.from(panel.current?.querySelectorAll("[data-column-editor-item]") ?? []).map((row2) => [row2.dataset.columnEditorItem, row2.getBoundingClientRect()]));
3284
+ }
3285
+ React21.useLayoutEffect(() => {
3286
+ const before = positions.current;
3287
+ positions.current = null;
3288
+ if (!before || !panel.current || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
3289
+ const styles = getComputedStyle(panel.current);
3290
+ const token = styles.getPropertyValue("--duration-normal").trim();
3291
+ const duration = parseFloat(token) * (token.endsWith("ms") ? 1 : 1e3);
3292
+ const easing = styles.getPropertyValue("--ease-out").trim();
3293
+ for (const row2 of panel.current.querySelectorAll("[data-column-editor-item]")) {
3294
+ const previous = before.get(row2.dataset.columnEditorItem);
3295
+ row2.getAnimations().filter((animation) => animation.id === "fractal-column-position").forEach((animation) => animation.cancel());
3296
+ if (!previous) continue;
3297
+ const next = row2.getBoundingClientRect();
3298
+ const x = previous.left - next.left, y = previous.top - next.top;
3299
+ if (Math.abs(x) < 1 && Math.abs(y) < 1) continue;
3300
+ row2.animate([{ transform: `translate(${x}px, ${y}px)` }, { transform: "translate(0, 0)" }], { duration, easing, id: "fractal-column-position" });
3301
+ }
3302
+ });
3303
+ const valid = [...new Set(value)].filter((key) => items.some((item) => item.id === key));
3304
+ const committed = [...valid, ...items.filter((item) => item.required && !valid.includes(item.id)).map((item) => item.id)];
3305
+ const visible = preview?.visible ?? committed;
3306
+ const hidden = preview?.hidden ?? items.filter((item) => !committed.includes(item.id)).map((item) => item.id);
3307
+ function cancel() {
3308
+ capturePositions();
3309
+ drag.current = null;
3310
+ setPreview(null);
3311
+ }
3312
+ React21.useEffect(() => {
3313
+ if (!open) return;
3314
+ panel.current?.focus();
3315
+ function outside(event) {
3316
+ if (!drag.current && !root.current?.contains(event.target)) setOpen(false);
3317
+ }
3318
+ function escape(event) {
3319
+ if (event.key !== "Escape") return;
3320
+ event.preventDefault();
3321
+ if (drag.current) {
3322
+ capturePositions();
3323
+ drag.current = null;
3324
+ setPreview(null);
3325
+ setAnnouncement("Reordering cancelled");
3326
+ } else {
3327
+ setOpen(false);
3328
+ trigger.current?.focus();
3329
+ }
3330
+ }
3331
+ function blur(event) {
3332
+ if (event.relatedTarget instanceof Node && !root.current?.contains(event.relatedTarget)) setOpen(false);
3333
+ }
3334
+ const element = root.current;
3335
+ document.addEventListener("pointerdown", outside);
3336
+ document.addEventListener("keydown", escape);
3337
+ element?.addEventListener("focusout", blur);
3338
+ return () => {
3339
+ document.removeEventListener("pointerdown", outside);
3340
+ document.removeEventListener("keydown", escape);
3341
+ element?.removeEventListener("focusout", blur);
3342
+ };
3343
+ }, [open]);
3344
+ function commit(next, message) {
3345
+ capturePositions();
3346
+ onChange(next);
3347
+ setAnnouncement(message);
3348
+ }
3349
+ function move(key, offset) {
3350
+ const index = committed.indexOf(key);
3351
+ const next = committed.filter((item) => item !== key);
3352
+ if (offset > 0 && index === committed.length - 1 && !items.find((item) => item.id === key)?.required) {
3353
+ commit(next, `${items.find((item) => item.id === key)?.label} hidden`);
3354
+ return;
3355
+ }
3356
+ next.splice(Math.max(0, Math.min(next.length, index + offset)), 0, key);
3357
+ commit(next, `${items.find((item) => item.id === key)?.label} moved ${offset < 0 ? "up" : "down"}`);
3358
+ }
3359
+ function start(event, key) {
3360
+ if (event.button !== 0 || event.target.closest("button")) return;
3361
+ drag.current = { id: key, pointerId: event.pointerId, x: event.clientX, y: event.clientY, active: false, visible: [...committed], hidden: [...hidden] };
3362
+ panel.current?.setPointerCapture(event.pointerId);
3363
+ event.preventDefault();
3364
+ }
3365
+ function update(event) {
3366
+ const current = drag.current;
3367
+ if (!current || event.pointerId !== current.pointerId) return;
3368
+ if (!current.active && Math.hypot(event.clientX - current.x, event.clientY - current.y) < 4) return;
3369
+ current.active = true;
3370
+ const container = panel.current;
3371
+ if (!container) return;
3372
+ const bounds = container.getBoundingClientRect();
3373
+ const hiddenGroup = container.querySelector('[data-column-editor-group="hidden"]');
3374
+ const inside = event.clientX >= bounds.left && event.clientX <= bounds.right && event.clientY >= bounds.top && event.clientY <= bounds.bottom;
3375
+ if (!inside) return;
3376
+ const destination = event.clientY >= hiddenGroup.getBoundingClientRect().top ? "hidden" : "visible";
3377
+ if (destination === "hidden" && items.find((item) => item.id === current.id)?.required) return;
3378
+ const slots = Array.from(container.querySelectorAll(`[data-column-editor-group="${destination}"] [data-column-editor-item]`));
3379
+ const localY = event.clientY - bounds.top + container.scrollTop - container.clientTop;
3380
+ const ownSlot = slots.find((row2) => row2.dataset.columnEditorItem === current.id);
3381
+ if (ownSlot && localY >= ownSlot.offsetTop && localY <= ownSlot.offsetTop + ownSlot.offsetHeight) return;
3382
+ const nextVisible = current.visible.filter((key) => key !== current.id);
3383
+ let nextHidden = current.hidden.filter((key) => key !== current.id);
3384
+ if (destination === "visible") {
3385
+ const index = slots.filter((row2) => row2.dataset.columnEditorItem !== current.id && localY > row2.offsetTop + row2.offsetHeight / 2).length;
3386
+ nextVisible.splice(index, 0, current.id);
3387
+ } else {
3388
+ nextHidden.push(current.id);
3389
+ nextHidden = items.filter((item) => nextHidden.includes(item.id)).map((item) => item.id);
3390
+ }
3391
+ if (!equal(nextVisible, current.visible) || !equal(nextHidden, current.hidden)) {
3392
+ capturePositions();
3393
+ current.visible = nextVisible;
3394
+ current.hidden = nextHidden;
3395
+ }
3396
+ setPreview({ visible: current.visible, hidden: current.hidden, active: current.id });
3397
+ if (event.clientY < bounds.top + 32) container.scrollTop -= 12;
3398
+ else if (event.clientY > bounds.bottom - 32) container.scrollTop += 12;
3399
+ }
3400
+ function finish(event) {
3401
+ const current = drag.current;
3402
+ if (!current || current.pointerId !== event.pointerId) return;
3403
+ const hit = document.elementFromPoint(event.clientX, event.clientY);
3404
+ if (current.active && hit && panel.current?.contains(hit)) commit(current.visible, "Column order updated");
3405
+ cancel();
3406
+ if (panel.current?.hasPointerCapture(event.pointerId)) panel.current.releasePointerCapture(event.pointerId);
3407
+ }
3408
+ function row(key, isHidden, index) {
3409
+ const item = items.find((item2) => item2.id === key);
3410
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
3411
+ "li",
3412
+ {
3413
+ className: "fractal-column-editor__row",
3414
+ "data-column-editor-item": key,
3415
+ "data-hidden": isHidden || void 0,
3416
+ "data-dragging": preview?.active === key || void 0,
3417
+ onPointerDown: (event) => start(event, key),
3418
+ children: [
3419
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_fractal_icons17.DotsSixVertical, { className: "fractal-column-editor__grip", "aria-hidden": "true" }),
3420
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "fractal-column-editor__name", children: item.label }),
3421
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "fractal-column-editor__actions", onPointerDown: (event) => event.stopPropagation(), children: isHidden ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Button, { buttonType: "ghost", controlSize: "sm", hasIconOnly: true, icon: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_fractal_icons17.Eye, { "aria-hidden": "true" }), "aria-label": `Show ${item.label} column`, onClick: () => commit([...committed, key], `${item.label} shown`) }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
3422
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Button, { buttonType: "ghost", controlSize: "sm", hasIconOnly: true, icon: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_fractal_icons17.ArrowUp, { "aria-hidden": "true" }), "aria-label": `Move ${item.label} up`, disabled: index === 0, onClick: () => move(key, -1) }),
3423
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Button, { buttonType: "ghost", controlSize: "sm", hasIconOnly: true, icon: index === visible.length - 1 && !item.required ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_fractal_icons17.EyeSlash, { "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_fractal_icons17.ArrowDown, { "aria-hidden": "true" }), "aria-label": index === visible.length - 1 && !item.required ? `Hide ${item.label} column` : `Move ${item.label} down`, disabled: index === visible.length - 1 && item.required, onClick: () => move(key, 1) })
3424
+ ] }) })
3425
+ ]
3426
+ },
3427
+ key
3428
+ );
3429
+ }
3430
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: cn("fractal-column-editor", className), ref: root, children: [
3431
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Button, { ref: trigger, controlSize: "lg", buttonType: "secondary", icon: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_fractal_icons17.Gear, { "aria-hidden": "true" }), iconPosition: "right", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": id, onClick: () => {
3432
+ cancel();
3433
+ setOpen((current) => !current);
3434
+ }, children: label }),
3435
+ open ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { id, className: "fractal-column-editor__panel", ref: panel, role: "dialog", "data-dragging": Boolean(preview) || void 0, "aria-label": label, tabIndex: -1, onPointerMove: update, onPointerUp: finish, onPointerCancel: cancel, onLostPointerCapture: cancel, children: [
3436
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("ol", { className: "fractal-column-editor__list", "data-column-editor-group": "visible", "aria-label": "Visible columns", children: visible.map((key, index) => row(key, false, index)) }),
3437
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("section", { "data-column-editor-group": "hidden", className: "fractal-column-editor__hidden", "aria-label": hiddenLabel, children: [
3438
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "fractal-column-editor__divider", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("span", { children: [
3439
+ hiddenLabel,
3440
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_fractal_icons17.EyeSlash, { "aria-hidden": "true" })
3441
+ ] }) }),
3442
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("ol", { className: "fractal-column-editor__list", children: hidden.map((key, index) => row(key, true, index)) }),
3443
+ !hidden.length ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "fractal-column-editor__empty", children: "Drag columns here to hide them." }) : null
3444
+ ] }),
3445
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "fractal-column-editor__announcement", role: "status", "aria-live": "polite", children: announcement })
3446
+ ] }) : null
3447
+ ] });
3448
+ }
3449
+
3450
+ // src/components/Tooltip/Tooltip.tsx
3451
+ var TooltipPrimitive = __toESM(require("@radix-ui/react-tooltip"), 1);
3452
+ var import_jsx_runtime39 = require("react/jsx-runtime");
3453
+ function Tooltip({
3454
+ content,
3455
+ children,
3456
+ placement = "bottom",
3457
+ maxWidth = 320,
3458
+ delay = 0,
3459
+ disabled = false,
3460
+ className
3461
+ }) {
3462
+ if (disabled || content == null || content === "") return children;
3463
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(TooltipPrimitive.Provider, { delayDuration: delay, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(TooltipPrimitive.Root, { children: [
3464
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(TooltipPrimitive.Trigger, { asChild: true, children }),
3465
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(TooltipPrimitive.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
3466
+ TooltipPrimitive.Content,
3467
+ {
3468
+ side: placement,
3469
+ sideOffset: 8,
3470
+ collisionPadding: 8,
3471
+ className: cn("fractal-tooltip", className),
3472
+ style: { "--tooltip-max-width": typeof maxWidth === "number" ? `${maxWidth}px` : maxWidth },
3473
+ children: [
3474
+ content,
3475
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(TooltipPrimitive.Arrow, { className: "fractal-tooltip-arrow", width: 8, height: 4, "aria-hidden": "true" })
3476
+ ]
3477
+ }
3478
+ ) })
3479
+ ] }) });
3480
+ }
1248
3481
  // Annotate the CommonJS export names for ESM import in node:
1249
3482
  0 && (module.exports = {
1250
3483
  Alert,
1251
3484
  AlertDescription,
3485
+ AlertGhost,
1252
3486
  AlertTitle,
3487
+ Breadcrumbs,
1253
3488
  Button,
3489
+ ButtonGhost,
1254
3490
  Card,
1255
3491
  CardContent,
1256
3492
  CardDescription,
1257
3493
  CardFooter,
3494
+ CardGhost,
1258
3495
  CardHeader,
1259
3496
  CardTitle,
3497
+ Checkbox,
3498
+ CheckboxGhost,
1260
3499
  Chip,
3500
+ ChipGhost,
3501
+ CodeBlock,
3502
+ ColumnEditor,
3503
+ ContentSwitcher,
3504
+ ContentSwitcherGhost,
3505
+ ConversationGhost,
1261
3506
  DataTable,
3507
+ DataTableGhost,
1262
3508
  Disclosure,
3509
+ DisclosureGhost,
1263
3510
  Dropdown,
3511
+ DropdownGhost,
1264
3512
  FeatureCard,
1265
3513
  FileDropzone,
3514
+ FileDropzoneGhost,
1266
3515
  FolderCard,
3516
+ Ghost,
3517
+ GhostLine,
1267
3518
  Input,
3519
+ InputGhost,
1268
3520
  Label,
3521
+ LabelGhost,
3522
+ LatticeLoader,
1269
3523
  Link,
3524
+ LinkGhost,
1270
3525
  Modal,
3526
+ ModalGhost,
3527
+ NumberInput,
3528
+ Pagination,
3529
+ PaginationGhost,
3530
+ PasswordInput,
1271
3531
  PromptCard,
3532
+ PromptCardGhost,
1272
3533
  PromptGrid,
3534
+ Radio,
1273
3535
  SidePanel,
3536
+ SidePanelGhost,
1274
3537
  Sidebar,
3538
+ SidebarAccountMenu,
1275
3539
  SidebarBrand,
1276
3540
  SidebarFooter,
3541
+ SidebarGhost,
1277
3542
  SidebarHeader,
1278
3543
  SidebarNav,
1279
3544
  SidebarNavItem,
1280
3545
  SidebarSection,
3546
+ Slider,
1281
3547
  SortableTable,
3548
+ SortableTableGhost,
3549
+ TableSearch,
1282
3550
  Tabs,
3551
+ TabsGhost,
3552
+ TextButton,
3553
+ TextButtonGhost,
1283
3554
  Textarea,
3555
+ TextareaGhost,
3556
+ Toast,
3557
+ ToastViewport,
1284
3558
  Toggle,
3559
+ ToggleGhost,
3560
+ Tooltip,
3561
+ UILoader,
1285
3562
  cn,
1286
3563
  useEscapeDismiss
1287
3564
  });