@oods/components-react 0.3.3 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ported.cjs CHANGED
@@ -26,337 +26,6 @@ function _interopNamespace(e) {
26
26
  var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
27
27
 
28
28
  // src/ported.tsx
29
-
30
- // src/status-map.json
31
- var status_map_default = {
32
- domains: {
33
- subscription: {
34
- future: {
35
- description: "Subscription is scheduled but not yet active.",
36
- chip: {
37
- foreground: "color.text.info",
38
- background: "color.background.info.subtle",
39
- border: "color.border.info",
40
- icon: "icon.status.scheduled"
41
- },
42
- banner: {
43
- foreground: "color.text.info",
44
- background: "color.background.surface.default",
45
- border: "color.border.info",
46
- icon: "icon.status.scheduled"
47
- }
48
- },
49
- trialing: {
50
- description: "Subscription is in a trial period before billing begins.",
51
- chip: {
52
- foreground: "color.text.accent",
53
- background: "color.background.accent.subtle",
54
- border: "color.border.accent",
55
- icon: "icon.status.trial"
56
- },
57
- banner: {
58
- foreground: "color.text.accent",
59
- background: "color.background.surface.default",
60
- border: "color.border.accent",
61
- icon: "icon.status.trial"
62
- }
63
- },
64
- active: {
65
- description: "Subscription is paid and service is provisioned.",
66
- chip: {
67
- foreground: "color.text.positive",
68
- background: "color.background.positive.subtle",
69
- border: "color.border.positive",
70
- icon: "icon.status.success"
71
- },
72
- banner: {
73
- foreground: "color.text.positive",
74
- background: "color.background.surface.default",
75
- border: "color.border.positive",
76
- icon: "icon.status.success"
77
- }
78
- },
79
- paused: {
80
- description: "Billing paused temporarily by finance or customer request.",
81
- chip: {
82
- foreground: "color.text.neutral",
83
- background: "color.background.neutral.subtle",
84
- border: "color.border.neutral",
85
- icon: "icon.status.paused"
86
- },
87
- banner: {
88
- foreground: "color.text.neutral",
89
- background: "color.background.surface.muted",
90
- border: "color.border.neutral",
91
- icon: "icon.status.paused"
92
- }
93
- },
94
- pending_cancellation: {
95
- description: "Subscription will cancel at period end but remains active now.",
96
- chip: {
97
- foreground: "color.text.info",
98
- background: "color.background.info.subtle",
99
- border: "color.border.info",
100
- icon: "icon.status.pending"
101
- },
102
- banner: {
103
- foreground: "color.text.info",
104
- background: "color.background.surface.default",
105
- border: "color.border.info",
106
- icon: "icon.status.pending"
107
- }
108
- },
109
- past_due: {
110
- description: "Renewal payment failed; smart retries ongoing and access retained during the grace window.",
111
- chip: {
112
- foreground: "color.text.danger",
113
- background: "color.background.danger.subtle",
114
- border: "color.border.danger",
115
- icon: "icon.status.warning"
116
- },
117
- banner: {
118
- foreground: "color.text.onbold",
119
- background: "color.background.danger.bold",
120
- border: "color.border.danger",
121
- icon: "icon.status.warning"
122
- }
123
- },
124
- unpaid: {
125
- description: "Payment retries exhausted; service access revoked.",
126
- chip: {
127
- foreground: "color.text.danger",
128
- background: "color.background.danger.subtle",
129
- border: "color.border.danger",
130
- icon: "icon.status.error"
131
- },
132
- banner: {
133
- foreground: "color.text.onbold",
134
- background: "color.background.danger.bold",
135
- border: "color.border.danger",
136
- icon: "icon.status.error"
137
- }
138
- },
139
- terminated: {
140
- description: "Subscription ended; service access revoked and billing stopped.",
141
- chip: {
142
- foreground: "color.text.disabled",
143
- background: "color.background.disabled",
144
- border: "color.border.disabled",
145
- icon: "icon.status.canceled"
146
- },
147
- banner: {
148
- foreground: "color.text.neutral",
149
- background: "color.background.neutral.subtle",
150
- border: "color.border.neutral",
151
- icon: "icon.status.canceled"
152
- }
153
- }
154
- },
155
- invoice: {
156
- draft: {
157
- description: "Invoice is editable and not yet finalized.",
158
- chip: {
159
- foreground: "color.text.neutral",
160
- background: "color.background.neutral.subtle",
161
- border: "color.border.neutral",
162
- icon: "icon.status.draft"
163
- },
164
- banner: {
165
- foreground: "color.text.neutral",
166
- background: "color.background.surface.muted",
167
- border: "color.border.neutral",
168
- icon: "icon.status.draft"
169
- }
170
- },
171
- posted: {
172
- description: "Invoice finalized with terms before collection.",
173
- chip: {
174
- foreground: "color.text.info",
175
- background: "color.background.info.subtle",
176
- border: "color.border.info",
177
- icon: "icon.status.pending"
178
- },
179
- banner: {
180
- foreground: "color.text.info",
181
- background: "color.background.surface.default",
182
- border: "color.border.info",
183
- icon: "icon.status.pending"
184
- }
185
- },
186
- paid: {
187
- description: "Invoice collected successfully in full.",
188
- chip: {
189
- foreground: "color.text.positive",
190
- background: "color.background.positive.subtle",
191
- border: "color.border.positive",
192
- icon: "icon.status.success"
193
- },
194
- banner: {
195
- foreground: "color.text.positive",
196
- background: "color.background.surface.default",
197
- border: "color.border.positive",
198
- icon: "icon.status.success"
199
- }
200
- },
201
- past_due: {
202
- description: "Invoice is unpaid past its due date.",
203
- chip: {
204
- foreground: "color.text.danger",
205
- background: "color.background.danger.subtle",
206
- border: "color.border.danger",
207
- icon: "icon.status.error"
208
- },
209
- banner: {
210
- foreground: "color.text.onbold",
211
- background: "color.background.danger.bold",
212
- border: "color.border.danger",
213
- icon: "icon.status.error"
214
- }
215
- },
216
- void: {
217
- description: "Invoice canceled and no longer payable.",
218
- chip: {
219
- foreground: "color.text.disabled",
220
- background: "color.background.disabled",
221
- border: "color.border.disabled",
222
- icon: "icon.status.void"
223
- },
224
- banner: {
225
- foreground: "color.text.neutral",
226
- background: "color.background.neutral.subtle",
227
- border: "color.border.neutral",
228
- icon: "icon.status.void"
229
- }
230
- }
231
- }
232
- }
233
- };
234
-
235
- // src/status.ts
236
- var TONE_ALIASES = {
237
- info: "info",
238
- informational: "info",
239
- accent: "accent",
240
- highlight: "accent",
241
- positive: "success",
242
- success: "success",
243
- ok: "success",
244
- warning: "warning",
245
- caution: "warning",
246
- danger: "critical",
247
- critical: "critical",
248
- error: "critical",
249
- negative: "critical",
250
- neutral: "neutral",
251
- pending: "info"
252
- };
253
- var toLabel = (status) => status.split(/[_-]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
254
- var inferTone = (entry) => {
255
- if (!entry) return "neutral";
256
- for (const reference of [entry.foreground, entry.background, entry.border]) {
257
- const segments = reference.replace(/^\{|\}$/g, "").split(/[\s./_-]+/).reverse();
258
- for (const segment of segments) {
259
- const tone = TONE_ALIASES[segment.toLowerCase()];
260
- if (tone) return tone;
261
- }
262
- }
263
- return "neutral";
264
- };
265
- var componentTokenSet = (tone) => {
266
- const normalized = tone === "positive" ? "success" : tone === "danger" ? "critical" : tone;
267
- return {
268
- background: `var(--sys-status-${normalized}-surface)`,
269
- border: `var(--sys-status-${normalized}-border)`,
270
- foreground: `var(--sys-status-${normalized}-text)`
271
- };
272
- };
273
- var rawTokenSet = (entry) => {
274
- const tone = inferTone(entry);
275
- return componentTokenSet(tone);
276
- };
277
- var bannerFallback = (tone) => tone === "neutral" ? {
278
- background: "var(--cmp-banner-background)",
279
- border: "var(--cmp-banner-border)",
280
- foreground: "var(--cmp-banner-text)"
281
- } : componentTokenSet(tone);
282
- var fallback = (domain, status, tone = "neutral") => ({
283
- domain,
284
- status,
285
- label: toLabel(status || "Unknown"),
286
- description: "Status not found in registry; falling back to the requested token set.",
287
- tone,
288
- badge: { subtle: componentTokenSet(tone), solid: componentTokenSet(tone) },
289
- banner: { subtle: bannerFallback(tone), solid: componentTokenSet(tone) }
290
- });
291
- var registry = /* @__PURE__ */ new Map();
292
- for (const [domain, entries] of Object.entries(status_map_default.domains)) {
293
- const domainEntries = /* @__PURE__ */ new Map();
294
- for (const [status, entry] of Object.entries(entries)) {
295
- const tone = inferTone(entry.chip);
296
- domainEntries.set(status.toLowerCase(), {
297
- domain,
298
- status,
299
- label: toLabel(status),
300
- description: entry.description,
301
- tone,
302
- iconName: entry.chip.icon ?? entry.banner?.icon,
303
- badge: { subtle: rawTokenSet(entry.chip), solid: componentTokenSet(tone) },
304
- banner: {
305
- subtle: entry.banner ? rawTokenSet(entry.banner) : bannerFallback(tone),
306
- solid: componentTokenSet(tone)
307
- }
308
- });
309
- }
310
- registry.set(domain, domainEntries);
311
- }
312
- var LEGACY_STATUS_ALIASES = {
313
- subscription: { delinquent: "unpaid" }
314
- };
315
- Object.freeze([...registry.keys()]);
316
- function getStatusPresentation(domain, status) {
317
- const entries = registry.get(domain);
318
- if (!entries) return fallback(domain, status);
319
- const normalized = status.toLowerCase();
320
- const direct = entries.get(normalized);
321
- if (direct) return direct;
322
- const alias = LEGACY_STATUS_ALIASES[domain]?.[normalized];
323
- const aliased = alias ? entries.get(alias) : void 0;
324
- return aliased ? { ...aliased, status, label: toLabel(status) } : fallback(domain, status);
325
- }
326
- function getToneTokenSet(tone) {
327
- return componentTokenSet(tone);
328
- }
329
- function getBannerToneTokenSet(tone) {
330
- return bannerFallback(tone);
331
- }
332
- var ICON_GLYPHS = {
333
- success: "\u2714\uFE0E",
334
- warning: "\u26A0\uFE0E",
335
- critical: "\u2A2F",
336
- danger: "\u2A2F",
337
- error: "\u2A2F",
338
- negative: "\u2A2F",
339
- pending: "\u2026",
340
- processing: "\u27F3",
341
- paused: "\u23F8",
342
- canceled: "\u2205",
343
- cancelled: "\u2205",
344
- draft: "\u270E",
345
- void: "\u2205",
346
- paid: "\u2714\uFE0E",
347
- info: "\u2139\uFE0E",
348
- trial: "\u2605",
349
- future: "\u23F2",
350
- scheduled: "\u23F2",
351
- refunded: "\u21BA",
352
- locked: "\u{1F512}",
353
- unlocked: "\u{1F513}",
354
- default: "\u2022"
355
- };
356
- function resolveStatusGlyph(iconName) {
357
- const segment = iconName?.split(".").pop()?.toLowerCase();
358
- return segment ? ICON_GLYPHS[segment] ?? ICON_GLYPHS.default : void 0;
359
- }
360
29
  var classes = (...values) => values.filter(Boolean).join(" ");
361
30
  var describedByIds = (id, help, validation, nativeDescription) => [
362
31
  nativeDescription,
@@ -383,7 +52,7 @@ var FieldFrame = ({
383
52
  }) => {
384
53
  const resolvedHelp = help ?? description;
385
54
  const validationTone = validation?.state === "error" ? "critical" : validation?.state === "warning" ? "warning" : validation?.state;
386
- const validationTokens = validationTone ? getToneTokenSet(validationTone) : void 0;
55
+ const validationTokens = validationTone ? componentContracts.getToneTokenSet(validationTone) : void 0;
387
56
  const validationStyle = validationTokens ? {
388
57
  "--cmp-input-message-border": validationTokens.border,
389
58
  "--cmp-input-message-text": validationTokens.foreground,
@@ -583,6 +252,84 @@ var Checkbox = React3__namespace.forwardRef(
583
252
  }
584
253
  );
585
254
  Checkbox.displayName = "OODS.Checkbox";
255
+ var Switch = React3__namespace.forwardRef(
256
+ ({
257
+ id,
258
+ label,
259
+ checked,
260
+ defaultChecked = false,
261
+ required,
262
+ help,
263
+ validation,
264
+ size = "md",
265
+ className,
266
+ style,
267
+ onClick,
268
+ onCheckedChange,
269
+ onUpdate,
270
+ "aria-describedby": nativeDescription,
271
+ ...rest
272
+ }, ref) => {
273
+ const [internalChecked, setInternalChecked] = React3__namespace.useState(defaultChecked);
274
+ const on = checked ?? internalChecked;
275
+ const validationId = validation?.id ?? `${id}-validation`;
276
+ const describedBy = [nativeDescription, help ? `${id}-help` : void 0, validation?.message ? validationId : void 0].filter(Boolean).join(" ") || void 0;
277
+ const tone = validation ? validation.state === "error" ? "critical" : validation.state : void 0;
278
+ const handleClick = (event) => {
279
+ onClick?.(event);
280
+ if (event.defaultPrevented) return;
281
+ const next = !on;
282
+ if (checked === void 0) setInternalChecked(next);
283
+ onCheckedChange?.(next);
284
+ onUpdate?.(next);
285
+ };
286
+ return /* @__PURE__ */ jsxRuntime.jsxs(
287
+ "div",
288
+ {
289
+ className: classes("oods-field", "oods-switch", className),
290
+ style: {
291
+ ...tone ? {
292
+ "--cmp-input-message-border": `var(--sys-status-${tone}-border)`,
293
+ "--cmp-input-message-text": `var(--sys-status-${tone}-text)`
294
+ } : {},
295
+ ...style
296
+ },
297
+ "data-oods-component": "Switch",
298
+ "data-size": size,
299
+ "data-validation-state": validation?.state,
300
+ children: [
301
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "oods-switch__row", children: [
302
+ /* @__PURE__ */ jsxRuntime.jsx(
303
+ "button",
304
+ {
305
+ ref,
306
+ id,
307
+ type: "button",
308
+ role: "switch",
309
+ className: "oods-switch__control",
310
+ "aria-checked": on,
311
+ "aria-labelledby": `${id}-label`,
312
+ "aria-describedby": describedBy,
313
+ "aria-required": required || void 0,
314
+ "aria-invalid": validation?.state === "error" || void 0,
315
+ onClick: handleClick,
316
+ ...rest,
317
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "oods-switch__thumb", "aria-hidden": "true" })
318
+ }
319
+ ),
320
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { id: `${id}-label`, className: "oods-field-label oods-switch__label", htmlFor: id, children: [
321
+ label,
322
+ required ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "oods-field-required", "aria-hidden": "true", children: "*" }) : null
323
+ ] })
324
+ ] }),
325
+ help ? /* @__PURE__ */ jsxRuntime.jsx("p", { id: `${id}-help`, className: "oods-field-help", children: help }) : null,
326
+ validation?.message ? /* @__PURE__ */ jsxRuntime.jsx("p", { id: validationId, className: "oods-field-error", role: validation.state === "error" ? "alert" : void 0, children: validation.message }) : null
327
+ ]
328
+ }
329
+ );
330
+ }
331
+ );
332
+ Switch.displayName = "OODS.Switch";
586
333
  var Select = React3__namespace.forwardRef(
587
334
  ({
588
335
  id,
@@ -721,6 +468,20 @@ var Textarea = React3__namespace.forwardRef(
721
468
  }
722
469
  );
723
470
  Textarea.displayName = "OODS.Textarea";
471
+ var REACT_NAMES = {
472
+ viewBox: "viewBox",
473
+ fill: "fill",
474
+ stroke: "stroke",
475
+ "stroke-width": "strokeWidth",
476
+ "stroke-linecap": "strokeLinecap",
477
+ "stroke-linejoin": "strokeLinejoin",
478
+ "aria-hidden": "aria-hidden",
479
+ focusable: "focusable"
480
+ };
481
+ var svgProps = Object.fromEntries(Object.entries(componentContracts.STATUS_ICON_SVG_ATTRS).map(([name, value]) => [REACT_NAMES[name] ?? name, value]));
482
+ function StatusIcon({ name }) {
483
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { ...svgProps, children: componentContracts.STATUS_ICON_SHAPES[name].map((shape, index) => React3__namespace.createElement(shape.tag, { key: index, ...shape.attrs })) });
484
+ }
724
485
  var classes2 = (...values) => values.filter(Boolean).join(" ");
725
486
  var Badge = React3__namespace.forwardRef(
726
487
  ({
@@ -739,11 +500,12 @@ var Badge = React3__namespace.forwardRef(
739
500
  "aria-label": ariaLabel,
740
501
  ...rest
741
502
  }, ref) => {
742
- const presentation = status ? getStatusPresentation(domain, status) : void 0;
503
+ const presentation = status ? componentContracts.getStatusPresentation(domain, status) : void 0;
743
504
  const tone = toneOverride ?? presentation?.tone ?? "neutral";
744
- const tokenSet = toneOverride !== void 0 ? getToneTokenSet(tone) : emphasis === "solid" ? presentation?.badge.solid ?? getToneTokenSet(tone) : presentation?.badge.subtle ?? getToneTokenSet(tone);
505
+ const tokenSet = toneOverride !== void 0 ? componentContracts.getToneTokenSet(tone) : emphasis === "solid" ? presentation?.badge.solid ?? componentContracts.getToneTokenSet(tone) : presentation?.badge.subtle ?? componentContracts.getToneTokenSet(tone);
745
506
  const resolvedContent = children ?? content ?? presentation?.label ?? status;
746
- const resolvedIcon = icon ?? resolveStatusGlyph(presentation?.iconName);
507
+ const statusIcon = componentContracts.resolveStatusIcon(presentation?.iconName);
508
+ const resolvedIcon = icon ?? (statusIcon ? /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { name: statusIcon }) : void 0);
747
509
  const resolvedShowIcon = showIcon ?? resolvedIcon !== void 0;
748
510
  const iconNode = resolvedShowIcon && resolvedIcon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "oods-badge__icon", "aria-hidden": "true", children: resolvedIcon }) : null;
749
511
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -795,13 +557,14 @@ var Banner = React3__namespace.forwardRef(
795
557
  style,
796
558
  ...rest
797
559
  }, ref) => {
798
- const presentation = status ? getStatusPresentation(domain, status) : void 0;
560
+ const presentation = status ? componentContracts.getStatusPresentation(domain, status) : void 0;
799
561
  const tone = toneOverride ?? presentation?.tone ?? "neutral";
800
- const tokenSet = emphasis === "solid" ? presentation?.banner.solid ?? getToneTokenSet(tone) : presentation?.banner.subtle ?? getBannerToneTokenSet(tone);
562
+ const tokenSet = emphasis === "solid" ? presentation?.banner.solid ?? componentContracts.getToneTokenSet(tone) : presentation?.banner.subtle ?? componentContracts.getBannerToneTokenSet(tone);
801
563
  const heading = title ?? presentation?.label ?? status;
802
564
  const body = detail ?? description ?? children ?? content ?? presentation?.description;
803
565
  const role = tone === "critical" || tone === "danger" ? "alert" : "status";
804
- const resolvedIcon = icon ?? resolveStatusGlyph(presentation?.iconName);
566
+ const statusIcon = componentContracts.resolveStatusIcon(presentation?.iconName);
567
+ const resolvedIcon = icon ?? (statusIcon ? /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { name: statusIcon }) : void 0);
805
568
  const resolvedShowIcon = showIcon ?? resolvedIcon !== void 0;
806
569
  return /* @__PURE__ */ jsxRuntime.jsxs(
807
570
  "div",
@@ -836,7 +599,7 @@ var Banner = React3__namespace.forwardRef(
836
599
  className: "oods-banner-dismiss oods-banner__dismiss",
837
600
  "aria-label": dismissLabel,
838
601
  onClick: () => onDismiss(),
839
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\xD7" })
602
+ children: /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { name: "x" })
840
603
  }
841
604
  ) : null
842
605
  ]
@@ -891,6 +654,131 @@ var Button = React3__namespace.forwardRef(
891
654
  }
892
655
  );
893
656
  Button.displayName = "OODS.Button";
657
+ var noSubscription = () => () => void 0;
658
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? React3__namespace.useEffect : React3__namespace.useLayoutEffect;
659
+ var canShowModal = () => typeof HTMLDialogElement !== "undefined" && typeof HTMLDialogElement.prototype.showModal === "function";
660
+ var hasContent = (node) => node !== void 0 && node !== null && node !== false && node !== "";
661
+ var pageLocks = 0;
662
+ var pageStyle;
663
+ function lockPageScroll() {
664
+ const page = document.documentElement;
665
+ if (pageLocks++ === 0) {
666
+ const scrollbar = window.innerWidth - page.clientWidth;
667
+ pageStyle = { overflow: page.style.overflow, paddingRight: page.style.paddingRight };
668
+ if (scrollbar > 0) page.style.paddingRight = `${parseFloat(getComputedStyle(page).paddingRight) + scrollbar}px`;
669
+ page.style.overflow = "hidden";
670
+ }
671
+ let held = true;
672
+ return () => {
673
+ if (!held) return;
674
+ held = false;
675
+ if (--pageLocks === 0 && pageStyle) {
676
+ page.style.overflow = pageStyle.overflow;
677
+ page.style.paddingRight = pageStyle.paddingRight;
678
+ pageStyle = void 0;
679
+ }
680
+ };
681
+ }
682
+ var outsideBox = (event, dialog) => {
683
+ const box = dialog.getBoundingClientRect();
684
+ return event.clientX < box.left || event.clientX > box.right || event.clientY < box.top || event.clientY > box.bottom;
685
+ };
686
+ var Dialog = React3__namespace.forwardRef(
687
+ ({
688
+ id,
689
+ open = false,
690
+ modal = true,
691
+ title,
692
+ description,
693
+ dismissLabel = "Close",
694
+ size = "md",
695
+ actions,
696
+ children,
697
+ className,
698
+ onClose,
699
+ onKeyDown,
700
+ onMouseDown,
701
+ onClick,
702
+ ...rest
703
+ }, ref) => {
704
+ const generatedId = React3__namespace.useId().replace(/[^\w-]/g, "");
705
+ const dialogId = id ?? `oods-dialog-${generatedId}`;
706
+ const element = React3__namespace.useRef(null);
707
+ const openRef = React3__namespace.useRef(open);
708
+ const scrollLock = React3__namespace.useRef(null);
709
+ const pressedBackdrop = React3__namespace.useRef(false);
710
+ const serverMarkup = React3__namespace.useSyncExternalStore(noSubscription, () => false, () => true);
711
+ const managed = modal && !serverMarkup && canShowModal();
712
+ const setRef = React3__namespace.useCallback((node) => {
713
+ element.current = node;
714
+ if (typeof ref === "function") ref(node);
715
+ else if (ref) ref.current = node;
716
+ }, [ref]);
717
+ const releaseScroll = React3__namespace.useCallback(() => {
718
+ scrollLock.current?.();
719
+ scrollLock.current = null;
720
+ }, []);
721
+ useIsomorphicLayoutEffect(() => {
722
+ openRef.current = open;
723
+ const dialog = element.current;
724
+ if (!dialog || !managed) return;
725
+ if (open && !dialog.open) dialog.showModal();
726
+ else if (!open && dialog.open) dialog.close();
727
+ if (open && dialog.open) scrollLock.current ??= lockPageScroll();
728
+ }, [open, managed]);
729
+ React3__namespace.useEffect(() => releaseScroll, [releaseScroll]);
730
+ const requestClose = (event) => {
731
+ event.preventDefault();
732
+ onClose?.();
733
+ };
734
+ return /* @__PURE__ */ jsxRuntime.jsxs(
735
+ "dialog",
736
+ {
737
+ ref: setRef,
738
+ id: dialogId,
739
+ className: classes2("oods-dialog", className),
740
+ "data-oods-component": "Dialog",
741
+ "data-size": size,
742
+ "aria-labelledby": `${dialogId}-title`,
743
+ "aria-describedby": hasContent(description) ? `${dialogId}-description` : void 0,
744
+ open: managed ? void 0 : open,
745
+ onKeyDown: (event) => {
746
+ onKeyDown?.(event);
747
+ if (event.key === "Escape" && !event.defaultPrevented) requestClose(event);
748
+ },
749
+ onCancel: requestClose,
750
+ onClose: () => {
751
+ if (element.current?.open) return;
752
+ releaseScroll();
753
+ if (openRef.current) onClose?.();
754
+ },
755
+ onMouseDown: (event) => {
756
+ onMouseDown?.(event);
757
+ pressedBackdrop.current = managed && event.target === event.currentTarget && outsideBox(event, event.currentTarget);
758
+ },
759
+ onClick: (event) => {
760
+ onClick?.(event);
761
+ const backdrop = pressedBackdrop.current && event.target === event.currentTarget && outsideBox(event, event.currentTarget);
762
+ pressedBackdrop.current = false;
763
+ if (backdrop && !event.defaultPrevented) onClose?.();
764
+ },
765
+ ...rest,
766
+ children: [
767
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "oods-dialog__header", children: [
768
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "oods-dialog__heading", children: [
769
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { id: `${dialogId}-title`, className: "oods-dialog__title", children: title }),
770
+ hasContent(description) ? /* @__PURE__ */ jsxRuntime.jsx("p", { id: `${dialogId}-description`, className: "oods-dialog__description", children: description }) : null
771
+ ] }),
772
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "oods-dialog__close", "aria-label": dismissLabel, onClick: () => onClose?.(), children: /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { name: "x" }) })
773
+ ] }),
774
+ hasContent(children) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "oods-dialog__body", children }) : null,
775
+ hasContent(actions) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "oods-dialog__footer", children: actions }) : null
776
+ ]
777
+ }
778
+ );
779
+ }
780
+ );
781
+ Dialog.displayName = "OODS.Dialog";
894
782
  var Card = React3__namespace.forwardRef(
895
783
  ({ as = "div", elevated = false, className, ...rest }, ref) => {
896
784
  const Element = as;
@@ -961,7 +849,7 @@ var StatusBadge = React3__namespace.forwardRef(
961
849
  ...rest
962
850
  }, ref) => {
963
851
  const resolvedStatus = status ?? value ?? "unknown";
964
- const presentation = getStatusPresentation(domain, resolvedStatus);
852
+ const presentation = componentContracts.getStatusPresentation(domain, resolvedStatus);
965
853
  return /* @__PURE__ */ jsxRuntime.jsx(
966
854
  Badge,
967
855
  {
@@ -982,8 +870,8 @@ var StatusBadge = React3__namespace.forwardRef(
982
870
  }
983
871
  );
984
872
  StatusBadge.displayName = "OODS.StatusBadge";
985
- function formatPrice(amount, currency, fallback2) {
986
- if (amount === void 0 || !Number.isFinite(amount)) return fallback2;
873
+ function formatPrice(amount, currency, fallback) {
874
+ if (amount === void 0 || !Number.isFinite(amount)) return fallback;
987
875
  if (!currency) return new Intl.NumberFormat(void 0, { maximumFractionDigits: 2 }).format(amount);
988
876
  try {
989
877
  return new Intl.NumberFormat(void 0, {
@@ -1326,7 +1214,7 @@ var SearchInput = React3__namespace.forwardRef(
1326
1214
  "aria-label": "Clear search",
1327
1215
  disabled,
1328
1216
  onClick: clear,
1329
- children: "\xD7"
1217
+ children: /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { name: "x" })
1330
1218
  }
1331
1219
  ) : null
1332
1220
  ]
@@ -1514,7 +1402,7 @@ var RelativeTimestamp = React3__namespace.forwardRef(
1514
1402
  const date = asDate(raw);
1515
1403
  if (hideWhenEmpty && !date) return null;
1516
1404
  const nowDate = asDate(now) ?? /* @__PURE__ */ new Date();
1517
- const iso = date?.toISOString() ?? (raw === void 0 ? void 0 : String(raw));
1405
+ const iso = date?.toISOString() ?? (raw === void 0 || raw === "" ? void 0 : String(raw));
1518
1406
  const absolute = componentContracts.formatDateTime(date, { timeZone: timezone });
1519
1407
  const content = relative ?? label ?? text ?? (now !== void 0 && date ? relativeLabel(date, nowDate) : absolute || children || "Unknown time");
1520
1408
  return /* @__PURE__ */ jsxRuntime.jsx(
package/dist/ported.js CHANGED
@@ -1,2 +1,2 @@
1
- export { AuditTimeline, CancellationSummary, PaginationBar, PriceBadge, RelativeTimestamp, SearchInput, StatusBadge, StatusTimeline } from './chunk-BPEHI36W.js';
2
- import './chunk-G74PKSW3.js';
1
+ export { AuditTimeline, CancellationSummary, PaginationBar, PriceBadge, RelativeTimestamp, SearchInput, StatusBadge, StatusTimeline } from './chunk-NLWXBLPA.js';
2
+ import './chunk-2VEQWKGK.js';