@oods/components-vue 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,3112 @@
1
+ 'use strict';
2
+
3
+ var vue = require('vue');
4
+ var componentContracts = require('@oods/component-contracts');
5
+
6
+ // src/primitives.ts
7
+ var toneProp = String;
8
+ var emphasisProp = String;
9
+ var sizeProp = String;
10
+ var STATUS_TONES = {
11
+ active: "success",
12
+ paid: "success",
13
+ success: "success",
14
+ successful: "success",
15
+ future: "info",
16
+ pending: "info",
17
+ pending_cancellation: "info",
18
+ posted: "info",
19
+ processing: "info",
20
+ trialing: "accent",
21
+ caution: "warning",
22
+ warning: "warning",
23
+ delinquent: "critical",
24
+ error: "critical",
25
+ failed: "critical",
26
+ past_due: "critical",
27
+ unpaid: "critical"
28
+ };
29
+ var DOMAIN_STATUS_TONES = {
30
+ subscription: {
31
+ future: "info",
32
+ trialing: "accent",
33
+ active: "success",
34
+ paused: "neutral",
35
+ pending_cancellation: "info",
36
+ past_due: "critical",
37
+ unpaid: "critical",
38
+ terminated: "neutral"
39
+ },
40
+ invoice: {
41
+ draft: "neutral",
42
+ posted: "info",
43
+ paid: "success",
44
+ past_due: "critical",
45
+ void: "neutral"
46
+ }
47
+ };
48
+ function normalizeStatus(status) {
49
+ return status?.trim().toLowerCase().replace(/[\s-]+/g, "_") ?? "";
50
+ }
51
+ function resolveTone(tone, status, domain) {
52
+ const normalizedStatus = normalizeStatus(status);
53
+ const normalizedDomain = domain?.trim().toLowerCase() ?? "";
54
+ return tone ?? DOMAIN_STATUS_TONES[normalizedDomain]?.[normalizedStatus] ?? STATUS_TONES[normalizedStatus] ?? "neutral";
55
+ }
56
+ function tokenTone(tone) {
57
+ if (tone === "positive") return "success";
58
+ if (tone === "danger") return "critical";
59
+ return tone;
60
+ }
61
+ function statusLabel(status) {
62
+ return normalizeStatus(status).split("_").filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join(" ");
63
+ }
64
+ function slotOrValue(slot, value) {
65
+ return slot ? slot() : value;
66
+ }
67
+ var Badge = vue.defineComponent({
68
+ name: "OodsBadge",
69
+ props: {
70
+ content: { type: [String, Number], default: void 0 },
71
+ status: String,
72
+ domain: { type: String, default: "subscription" },
73
+ tone: toneProp,
74
+ emphasis: { type: emphasisProp, default: "subtle" },
75
+ icon: { type: [String, Number, Object], default: void 0 }
76
+ },
77
+ setup(props, { slots }) {
78
+ return () => {
79
+ const tone = resolveTone(props.tone, props.status, props.domain);
80
+ const palette = tokenTone(tone);
81
+ return vue.h("span", {
82
+ class: "oods-badge",
83
+ "data-oods-component": "Badge",
84
+ "data-status": props.status,
85
+ "data-domain": props.domain,
86
+ "data-tone": tone,
87
+ "data-emphasis": props.emphasis,
88
+ style: {
89
+ "--cmp-badge-background": `var(--sys-status-${palette}-surface)`,
90
+ "--cmp-badge-border": `var(--sys-status-${palette}-border)`,
91
+ "--cmp-badge-text": `var(--sys-status-${palette}-text)`
92
+ }
93
+ }, [
94
+ props.icon || slots.icon ? vue.h("span", { class: "oods-badge-icon", "aria-hidden": "true" }, [slotOrValue(slots.icon, props.icon)]) : null,
95
+ slotOrValue(slots.default, props.content ?? statusLabel(props.status))
96
+ ]);
97
+ };
98
+ }
99
+ });
100
+ var Banner = vue.defineComponent({
101
+ name: "OodsBanner",
102
+ props: {
103
+ title: { type: String, default: "" },
104
+ detail: { type: String, default: "" },
105
+ content: { type: [String, Number], default: "" },
106
+ status: String,
107
+ domain: { type: String, default: "subscription" },
108
+ tone: toneProp,
109
+ emphasis: { type: emphasisProp, default: "subtle" },
110
+ dismissLabel: String
111
+ },
112
+ emits: {
113
+ dismiss: () => true
114
+ },
115
+ setup(props, { emit, slots }) {
116
+ return () => {
117
+ const tone = resolveTone(props.tone, props.status, props.domain);
118
+ const palette = tokenTone(tone);
119
+ return vue.h("section", {
120
+ class: "oods-banner",
121
+ "data-oods-component": "Banner",
122
+ "data-status": props.status,
123
+ "data-domain": props.domain,
124
+ "data-tone": tone,
125
+ "data-emphasis": props.emphasis,
126
+ role: tone === "critical" || tone === "danger" ? "alert" : "status",
127
+ "aria-live": tone === "critical" || tone === "danger" ? "assertive" : "polite",
128
+ style: {
129
+ "--cmp-banner-background": `var(--sys-status-${palette}-surface)`,
130
+ "--cmp-banner-border": `var(--sys-status-${palette}-border)`,
131
+ "--cmp-banner-text": `var(--sys-status-${palette}-text)`
132
+ }
133
+ }, [
134
+ vue.h("div", { class: "oods-banner-content" }, [
135
+ props.title || props.status || slots.title ? vue.h("strong", { class: "oods-banner-title" }, [
136
+ slotOrValue(slots.title, props.title || statusLabel(props.status))
137
+ ]) : null,
138
+ props.detail ? vue.h("p", { class: "oods-banner-detail" }, props.detail) : null,
139
+ slots.default || props.content ? vue.h(slots.default ? "div" : "p", { class: "oods-banner-body" }, [slotOrValue(slots.default, props.content)]) : null,
140
+ slots.actions ? vue.h("div", { class: "oods-banner-actions" }, slots.actions()) : null
141
+ ]),
142
+ props.dismissLabel ? vue.h("button", {
143
+ type: "button",
144
+ class: "oods-banner-dismiss",
145
+ "aria-label": props.dismissLabel,
146
+ onClick: () => emit("dismiss")
147
+ }, [vue.h("span", { "aria-hidden": "true" }, "\xD7")]) : null
148
+ ]);
149
+ };
150
+ }
151
+ });
152
+ var Button = vue.defineComponent({
153
+ name: "OodsButton",
154
+ props: {
155
+ content: { type: [String, Number], default: "" },
156
+ intent: { type: String, default: "neutral" },
157
+ size: { type: sizeProp, default: "md" },
158
+ disabled: Boolean,
159
+ type: { type: String, default: "button" }
160
+ },
161
+ emits: {
162
+ activate: (_event) => true
163
+ },
164
+ setup(props, { emit, slots }) {
165
+ return () => vue.h("button", {
166
+ type: props.type,
167
+ disabled: props.disabled,
168
+ class: "oods-button",
169
+ "data-oods-component": "Button",
170
+ "data-intent": props.intent,
171
+ "data-size": props.size,
172
+ style: {
173
+ "--cmp-button-background": "var(--sys-surface-interactive-primary-default)",
174
+ "--cmp-button-background-hover": "var(--sys-surface-interactive-primary-hover)",
175
+ "--cmp-button-background-disabled": "var(--sys-surface-disabled)",
176
+ "--cmp-button-border": "var(--sys-border-subtle)",
177
+ "--cmp-button-text": "var(--sys-text-on-interactive)",
178
+ "--cmp-button-text-disabled": "var(--sys-text-disabled)"
179
+ },
180
+ onClick: (event) => emit("activate", event)
181
+ }, [slotOrValue(slots.default, props.content)]);
182
+ }
183
+ });
184
+ var SAFE_CARD_ELEMENTS = /* @__PURE__ */ new Set(["div", "article", "section", "aside"]);
185
+ var Card = vue.defineComponent({
186
+ name: "OodsCard",
187
+ props: {
188
+ elevated: Boolean,
189
+ as: { type: String, default: "div" }
190
+ },
191
+ setup(props, { slots }) {
192
+ return () => vue.h(SAFE_CARD_ELEMENTS.has(props.as) ? props.as : "div", {
193
+ class: "oods-card",
194
+ "data-oods-component": "Card",
195
+ "data-elevated": String(props.elevated)
196
+ }, slots.default?.());
197
+ }
198
+ });
199
+ var GAP_VALUES = {
200
+ xs: "var(--cmp-spacing-inline-xs, 0.5rem)",
201
+ sm: "var(--cmp-spacing-inline-sm, 0.75rem)",
202
+ md: "var(--cmp-spacing-stack-default, 1rem)",
203
+ lg: "var(--cmp-spacing-stack-lg, 1.5rem)",
204
+ xl: "var(--cmp-spacing-stack-xl, 2rem)"
205
+ };
206
+ function resolveGap(gap) {
207
+ return GAP_VALUES[gap] ?? gap;
208
+ }
209
+ function maxColumns(columns) {
210
+ const count = Number(columns);
211
+ return Number.isFinite(count) && count > 0 ? count : void 0;
212
+ }
213
+ var Grid = vue.defineComponent({
214
+ name: "OodsGrid",
215
+ props: {
216
+ columns: { type: [Number, String], default: "auto-fit" },
217
+ minColumnWidth: { type: String, default: "16rem" },
218
+ gap: { type: String, default: "md" },
219
+ align: { type: String, default: "stretch" },
220
+ justify: { type: String, default: "normal" }
221
+ },
222
+ setup(props, { slots }) {
223
+ return () => vue.h("div", {
224
+ class: "oods-grid",
225
+ "data-oods-component": "Grid",
226
+ "data-max-columns": maxColumns(props.columns),
227
+ style: {
228
+ "--oods-grid-columns": String(props.columns),
229
+ "--oods-grid-min-column": props.minColumnWidth,
230
+ "--oods-layout-gap": resolveGap(props.gap),
231
+ "--oods-layout-align": props.align,
232
+ "--oods-layout-justify": props.justify
233
+ }
234
+ }, slots.default?.());
235
+ }
236
+ });
237
+ var Stack = vue.defineComponent({
238
+ name: "OodsStack",
239
+ props: {
240
+ direction: { type: String, default: "column" },
241
+ gap: { type: String, default: "md" },
242
+ align: { type: String, default: "stretch" },
243
+ justify: { type: String, default: "flex-start" },
244
+ wrap: Boolean
245
+ },
246
+ setup(props, { slots }) {
247
+ return () => vue.h("div", {
248
+ class: "oods-stack",
249
+ "data-oods-component": "Stack",
250
+ style: {
251
+ "--oods-stack-direction": props.direction,
252
+ "--oods-stack-wrap": props.wrap ? "wrap" : "nowrap",
253
+ "--oods-layout-gap": resolveGap(props.gap),
254
+ "--oods-layout-align": props.align,
255
+ "--oods-layout-justify": props.justify
256
+ }
257
+ }, slots.default?.());
258
+ }
259
+ });
260
+ var SAFE_TEXT_ELEMENTS = /* @__PURE__ */ new Set([
261
+ "span",
262
+ "p",
263
+ "strong",
264
+ "em",
265
+ "small",
266
+ "div",
267
+ "label",
268
+ "h1",
269
+ "h2",
270
+ "h3",
271
+ "h4",
272
+ "h5",
273
+ "h6"
274
+ ]);
275
+ var Text = vue.defineComponent({
276
+ name: "OodsText",
277
+ props: {
278
+ content: { type: [String, Number], default: "" },
279
+ label: String,
280
+ as: { type: String, default: "span" },
281
+ size: { type: sizeProp, default: "md" },
282
+ weight: { type: String, default: "normal" }
283
+ },
284
+ setup(props, { slots }) {
285
+ return () => vue.h(SAFE_TEXT_ELEMENTS.has(props.as) ? props.as : "span", {
286
+ class: "oods-text",
287
+ "data-oods-component": "Text",
288
+ "data-size": props.size,
289
+ "data-weight": props.weight,
290
+ "aria-description": props.label
291
+ }, [slotOrValue(slots.default, props.content)]);
292
+ }
293
+ });
294
+ function firstText(...values) {
295
+ return values.find((value) => typeof value === "string" && value.trim().length > 0);
296
+ }
297
+ function headingElement(as, level, fallback) {
298
+ if (as && /^h[1-6]$/.test(as)) return as;
299
+ const resolvedLevel = level !== void 0 && Number.isFinite(level) ? Math.min(6, Math.max(1, Math.trunc(level))) : fallback;
300
+ return `h${resolvedLevel}`;
301
+ }
302
+ function authoredContent(value) {
303
+ function flatten(child) {
304
+ if (Array.isArray(child)) return child.flatMap(flatten);
305
+ if (child == null || typeof child === "boolean" || vue.isVNode(child) && child.type === vue.Comment) return [];
306
+ if (vue.isVNode(child) && child.type === vue.Fragment) return flatten(child.children);
307
+ return [child];
308
+ }
309
+ const content = flatten(value);
310
+ const meaningful = content.some((child) => {
311
+ if (typeof child === "string") return child.trim().length > 0;
312
+ if (vue.isVNode(child) && child.type === vue.Text) return String(child.children ?? "").trim().length > 0;
313
+ return true;
314
+ });
315
+ return meaningful ? content : [];
316
+ }
317
+ function scalarContent(content) {
318
+ if (!content.length) return void 0;
319
+ if (!content.every((child) => typeof child === "string" || typeof child === "number" || vue.isVNode(child) && child.type === vue.Text)) return void 0;
320
+ return content.map((child) => String(vue.isVNode(child) ? child.children : child)).join("");
321
+ }
322
+ var DetailHeader = vue.defineComponent({
323
+ name: "OodsDetailHeader",
324
+ props: {
325
+ title: String,
326
+ label: String,
327
+ text: String,
328
+ subtitle: String,
329
+ sublabel: String,
330
+ description: String,
331
+ metadata: String,
332
+ meta: String,
333
+ as: String,
334
+ level: Number
335
+ },
336
+ setup(props, { slots }) {
337
+ return () => {
338
+ const content = authoredContent(slots.default?.());
339
+ const scalar = scalarContent(content);
340
+ const title = scalar ?? firstText(props.title, props.label, props.text) ?? "Details";
341
+ const subtitle = notEcho(firstText(props.subtitle, props.sublabel, props.description), title);
342
+ const metadata = firstText(props.metadata, props.meta);
343
+ return vue.h(
344
+ "header",
345
+ { class: "oods-detail-header", "data-oods-component": "DetailHeader" },
346
+ content.length && scalar === void 0 ? content : [
347
+ vue.h(headingElement(props.as, props.level, 2), title),
348
+ subtitle ? vue.h("span", { "data-oods-subtitle": "true" }, subtitle) : null,
349
+ metadata ? vue.h("span", { "data-oods-metadata": "true" }, metadata) : null
350
+ ]
351
+ );
352
+ };
353
+ }
354
+ });
355
+ function notEcho(supporting, heading) {
356
+ if (!supporting || !heading) return supporting;
357
+ return supporting.trim() === heading.trim() ? void 0 : supporting;
358
+ }
359
+ var CardHeader = vue.defineComponent({
360
+ name: "OodsCardHeader",
361
+ props: {
362
+ title: String,
363
+ label: String,
364
+ text: String,
365
+ supporting: String,
366
+ supportingText: String,
367
+ subtitle: String,
368
+ description: String,
369
+ as: String,
370
+ level: Number
371
+ },
372
+ setup(props, { slots }) {
373
+ return () => {
374
+ const content = authoredContent(slots.default?.());
375
+ const scalar = scalarContent(content);
376
+ const title = scalar ?? firstText(props.title, props.label, props.text) ?? "Card";
377
+ const supporting = notEcho(firstText(props.supporting, props.supportingText, props.subtitle, props.description), title);
378
+ return vue.h(
379
+ "header",
380
+ { class: "oods-card-header", "data-oods-component": "CardHeader" },
381
+ content.length && scalar === void 0 ? content : [
382
+ vue.h(headingElement(props.as, props.level, 2), title),
383
+ supporting ? vue.h("span", { "data-oods-supporting": "true" }, supporting) : null
384
+ ]
385
+ );
386
+ };
387
+ }
388
+ });
389
+ var ColorSwatch = vue.defineComponent({
390
+ name: "OodsColorSwatch",
391
+ props: { color: String, value: String, state: String, label: String },
392
+ setup(props, { slots }) {
393
+ return () => {
394
+ const color = firstText(props.color, props.value, props.state) ?? "default";
395
+ const content = authoredContent(slots.default?.());
396
+ const scalar = scalarContent(content);
397
+ const label = firstText(scalar, props.label) ?? color;
398
+ return vue.h("span", {
399
+ class: "oods-color-swatch",
400
+ "data-oods-component": "ColorSwatch",
401
+ "data-summary-type": "color-swatch",
402
+ "data-swatch-color": color,
403
+ style: { "--oods-swatch-color": color === "default" ? void 0 : color }
404
+ }, [
405
+ vue.h("span", { "data-oods-swatch-chip": "true", "aria-hidden": "true" }),
406
+ vue.h("span", { "data-oods-swatch-label": "true" }, label),
407
+ ...scalar === void 0 ? content : []
408
+ ]);
409
+ };
410
+ }
411
+ });
412
+ var ColorizedBadge = vue.defineComponent({
413
+ name: "OodsColorizedBadge",
414
+ props: {
415
+ label: String,
416
+ text: String,
417
+ state: String,
418
+ value: String,
419
+ status: String,
420
+ color: String,
421
+ hue: String,
422
+ swatch: String,
423
+ variant: String,
424
+ tone: String,
425
+ emphasis: String
426
+ },
427
+ setup(props, { slots }) {
428
+ return () => {
429
+ const content = authoredContent(slots.default?.());
430
+ const scalar = scalarContent(content);
431
+ const label = firstText(scalar, props.label, props.text, props.state, props.value) ?? "Color";
432
+ const status = firstText(props.status, props.state, props.value);
433
+ const color = firstText(props.color, props.hue, props.swatch, props.state);
434
+ const variant = firstText(props.variant) ?? "colorized";
435
+ return vue.h(Badge, {
436
+ status,
437
+ tone: props.tone,
438
+ emphasis: props.emphasis,
439
+ "data-oods-component": "ColorizedBadge",
440
+ "data-badge-status": status,
441
+ "data-badge-color": color,
442
+ "data-badge-variant": variant,
443
+ style: { "--oods-badge-color": color }
444
+ }, {
445
+ icon: () => vue.h("span", { "data-oods-badge-marker": "true", "aria-hidden": "true" }),
446
+ default: () => [
447
+ vue.h("span", { "data-oods-badge-label": "true" }, label),
448
+ ...scalar === void 0 ? content : []
449
+ ]
450
+ });
451
+ };
452
+ }
453
+ });
454
+ var VizAreaPreview = vue.defineComponent({
455
+ name: "OodsVizAreaPreview",
456
+ props: {
457
+ svg: String,
458
+ /** The same chart rendered at the narrow size; the figure shows it instead of svg when its own inline size is at most 600px. */
459
+ svgNarrow: String,
460
+ /** The same chart rendered at the wide size; the figure shows it instead of svg when its own inline size is at least 900px. */
461
+ svgWide: String,
462
+ title: String,
463
+ description: String,
464
+ width: { type: Number, default: 640 },
465
+ height: { type: Number, default: 360 }
466
+ },
467
+ setup(props, { slots }) {
468
+ return () => {
469
+ const content = authoredContent(slots.default?.());
470
+ if (props.svg !== void 0) return vue.h("figure", {
471
+ class: "oods-viz-area-preview",
472
+ "data-oods-component": "VizAreaPreview",
473
+ "data-viz-preview-type": "area",
474
+ "data-viz-rendered": "true",
475
+ "data-viz-width": props.width,
476
+ "data-viz-height": props.height,
477
+ ...props.svgNarrow !== void 0 ? { "data-viz-narrow": "true" } : {},
478
+ ...props.svgWide !== void 0 ? { "data-viz-wide": "true" } : {},
479
+ role: "img",
480
+ "aria-label": props.title ?? props.description ?? "Payment amounts",
481
+ style: { "--oods-viz-width": `${props.width}px` }
482
+ }, [
483
+ ...props.title && !componentContracts.svgCarriesTitle(props.svg, props.title) ? [vue.h("figcaption", props.title)] : [],
484
+ vue.h("div", { "data-viz-svg": "true", innerHTML: componentContracts.assertStaticSvg(props.svg) }),
485
+ ...props.svgNarrow !== void 0 ? [vue.h("div", { "data-viz-svg-narrow": "true", innerHTML: componentContracts.assertStaticSvg(props.svgNarrow) })] : [],
486
+ ...props.svgWide !== void 0 ? [vue.h("div", { "data-viz-svg-wide": "true", innerHTML: componentContracts.assertStaticSvg(props.svgWide) })] : [],
487
+ ...props.description ? [vue.h("p", { "data-viz-description": "true" }, props.description)] : []
488
+ ]);
489
+ return vue.h("div", {
490
+ class: "oods-viz-area-preview",
491
+ "data-oods-component": "VizAreaPreview",
492
+ "data-viz-preview-type": "area",
493
+ "data-viz-width": props.width,
494
+ "data-viz-height": props.height,
495
+ style: { "--oods-viz-width": `${props.width}px`, "--oods-viz-height": `${props.height}px` }
496
+ }, content.length ? content : [
497
+ vue.h("div", { "data-viz-preview-placeholder": "true" }, `Area preview (${props.width} x ${props.height})`)
498
+ ]);
499
+ };
500
+ }
501
+ });
502
+ function firstScalar(...values) {
503
+ for (const value of values) {
504
+ if (typeof value === "string") {
505
+ if (value.trim().length > 0) return value;
506
+ continue;
507
+ }
508
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
509
+ }
510
+ return void 0;
511
+ }
512
+ function isRecord(value) {
513
+ return value !== null && typeof value === "object" && !Array.isArray(value);
514
+ }
515
+ function descriptorLabel(descriptor) {
516
+ return firstText(descriptor.label, descriptor.field) ?? "Filter";
517
+ }
518
+ var PANEL_SECTION_PROPS = {
519
+ title: String,
520
+ label: String,
521
+ heading: String,
522
+ name: String,
523
+ subtitle: String,
524
+ description: String,
525
+ metadata: String,
526
+ summary: String,
527
+ text: String,
528
+ body: String,
529
+ emptyMessage: String
530
+ };
531
+ function createPanelSection(component, className, panelType, defaultTitle) {
532
+ return vue.defineComponent({
533
+ name: `Oods${component}`,
534
+ props: PANEL_SECTION_PROPS,
535
+ setup(props, { slots }) {
536
+ return () => {
537
+ const content = authoredContent(slots.default?.());
538
+ const title = firstText(props.title, props.label, props.heading, props.name) ?? defaultTitle;
539
+ const subtitle = firstText(props.subtitle, props.description, props.metadata);
540
+ const fallback = firstText(props.summary, props.text, props.body, props.emptyMessage);
541
+ return vue.h("section", {
542
+ class: className,
543
+ "data-oods-component": component,
544
+ "data-panel-type": panelType
545
+ }, [
546
+ vue.h("header", { "data-panel-header": "true" }, [
547
+ vue.h("h2", title),
548
+ subtitle ? vue.h("span", { "data-panel-subtitle": "true" }, subtitle) : null
549
+ ]),
550
+ vue.h("div", { "data-panel-content": "true" }, content.length ? content : fallback ? [vue.h("span", { "data-panel-summary": "true" }, fallback)] : [])
551
+ ]);
552
+ };
553
+ }
554
+ });
555
+ }
556
+ var ClassificationPanel = createPanelSection("ClassificationPanel", "oods-classification-panel", "classification", "Classification");
557
+ var AddressCollectionPanel = createPanelSection("AddressCollectionPanel", "oods-address-collection-panel", "address", "Addresses");
558
+ var MembershipPanel = createPanelSection("MembershipPanel", "oods-membership-panel", "membership", "Membership");
559
+ var PreferencePanel = createPanelSection("PreferencePanel", "oods-preference-panel", "preference", "Preferences");
560
+ var FilterPanel = vue.defineComponent({
561
+ name: "OodsFilterPanel",
562
+ props: {
563
+ filters: { type: Array, default: () => [] },
564
+ activeFilters: { type: Array, default: () => [] },
565
+ mode: String,
566
+ collapsible: { type: Boolean, default: true }
567
+ },
568
+ setup(props, { slots }) {
569
+ return () => {
570
+ const content = authoredContent(slots.default?.());
571
+ const mode = firstText(props.mode) ?? "immediate";
572
+ const descriptors = (Array.isArray(props.filters) ? props.filters : []).filter(isRecord);
573
+ const activeCount = Array.isArray(props.activeFilters) ? props.activeFilters.length : 0;
574
+ return vue.h("aside", {
575
+ class: "oods-filter-panel",
576
+ "data-oods-component": "FilterPanel",
577
+ "data-behavioral": "filter",
578
+ "data-filter-mode": mode,
579
+ role: "region",
580
+ "aria-label": "Filters"
581
+ }, content.length ? content : [
582
+ activeCount > 0 ? vue.h("div", { "data-active-filters": "true", "aria-live": "polite" }, [
583
+ vue.h("span", { "data-filter-count": "true" }, `${activeCount} active`),
584
+ vue.h("button", { type: "button", "data-filter-clear-all": "true" }, "Clear all")
585
+ ]) : null,
586
+ ...descriptors.map((descriptor, index) => vue.h("fieldset", {
587
+ key: index,
588
+ "data-filter-section": "true",
589
+ "data-collapsible": props.collapsible ? "true" : void 0
590
+ }, [vue.h("legend", descriptorLabel(descriptor))])),
591
+ mode === "batch" ? vue.h("button", { type: "button", "data-filter-apply": "true" }, "Apply") : null
592
+ ]);
593
+ };
594
+ }
595
+ });
596
+ var PriceSummary = vue.defineComponent({
597
+ name: "OodsPriceSummary",
598
+ props: {
599
+ title: String,
600
+ label: String,
601
+ heading: String,
602
+ name: String,
603
+ amount: [String, Number],
604
+ amountCents: [String, Number],
605
+ unitAmountCents: [String, Number],
606
+ minorUnits: Number,
607
+ currency: String,
608
+ currencyCode: String,
609
+ model: String,
610
+ pricingModel: String,
611
+ interval: String,
612
+ billingInterval: String,
613
+ summary: String,
614
+ text: String,
615
+ description: String
616
+ },
617
+ setup(props, { slots }) {
618
+ return () => {
619
+ const content = authoredContent(slots.default?.());
620
+ const title = firstText(props.title, props.label, props.heading, props.name) ?? "Price Summary";
621
+ const terms = [
622
+ ["Amount", [props.amount, props.amountCents, props.unitAmountCents, props.currency, props.currencyCode, props.model, props.pricingModel, props.interval, props.billingInterval].some((value) => value !== void 0) ? componentContracts.formatPriceAmount(props.amount ?? props.amountCents ?? props.unitAmountCents, firstText(props.currency, props.currencyCode), props.minorUnits) : void 0],
623
+ ["Currency", firstScalar(props.currency, props.currencyCode)],
624
+ ["Model", componentContracts.formatPriceCode(props.model ?? props.pricingModel)],
625
+ ["Interval", componentContracts.formatPriceCode(props.interval ?? props.billingInterval)]
626
+ ].filter((entry) => entry[1] !== void 0);
627
+ const fallback = firstText(props.summary, props.text, props.description);
628
+ return vue.h("section", {
629
+ class: "oods-price-summary",
630
+ "data-oods-component": "PriceSummary",
631
+ "data-summary-type": "price"
632
+ }, [
633
+ vue.h("h2", { "data-summary-title": "true" }, title),
634
+ ...content.length ? content : terms.length ? [vue.h("dl", terms.map(([term, value]) => vue.h("div", { key: term, "data-summary-item": "true" }, [vue.h("dt", term), vue.h("dd", value)])))] : fallback ? [vue.h("p", { "data-summary-fallback": "true" }, fallback)] : [vue.h("dl")]
635
+ ]);
636
+ };
637
+ }
638
+ });
639
+ function normalizeTagItems(value) {
640
+ if (!Array.isArray(value)) return [];
641
+ const items = [];
642
+ for (const entry of value) {
643
+ if (entry === void 0 || entry === null) continue;
644
+ if (isRecord(entry)) {
645
+ const text = firstScalar(entry.label, entry.name, entry.role, entry.value, entry.id);
646
+ if (text) items.push(text);
647
+ continue;
648
+ }
649
+ items.push(typeof entry === "object" ? JSON.stringify(entry) : String(entry));
650
+ }
651
+ return items.filter((item) => item.length > 0);
652
+ }
653
+ var TagManager = vue.defineComponent({
654
+ name: "OodsTagManager",
655
+ props: {
656
+ title: String,
657
+ label: String,
658
+ heading: String,
659
+ name: String,
660
+ description: String,
661
+ subtitle: String,
662
+ hint: String,
663
+ tags: Array,
664
+ value: Array
665
+ },
666
+ setup(props, { slots }) {
667
+ return () => {
668
+ const content = authoredContent(slots.default?.());
669
+ const title = firstText(props.title, props.label, props.heading, props.name) ?? "Tag Manager";
670
+ const subtitle = firstText(props.description, props.subtitle, props.hint);
671
+ const items = normalizeTagItems(props.tags ?? props.value);
672
+ return vue.h("form", {
673
+ class: "oods-tag-manager",
674
+ "data-oods-component": "TagManager",
675
+ "data-form-type": "tag-manager",
676
+ // The add control is unwired: submitting never navigates.
677
+ onSubmit: (event) => event.preventDefault()
678
+ }, [
679
+ vue.h("header", { "data-form-header": "true" }, [
680
+ vue.h("h2", title),
681
+ subtitle ? vue.h("span", { "data-form-subtitle": "true" }, subtitle) : null
682
+ ]),
683
+ vue.h("div", { "data-form-content": "true" }, content.length ? content : [
684
+ vue.h("div", { "data-tag-list": "true" }, items.map((tag, index) => vue.h("span", { key: index, "data-tag-item": "true" }, tag))),
685
+ vue.h("label", { "data-form-control": "input" }, [
686
+ vue.h("span", "Add Tag"),
687
+ vue.h("input", { type: "text", name: "newTag", placeholder: "Type a tag" })
688
+ ])
689
+ ])
690
+ ]);
691
+ };
692
+ }
693
+ });
694
+ function asCount(value) {
695
+ if (typeof value === "number" && Number.isFinite(value)) return value;
696
+ if (typeof value === "string" && value.trim().length > 0) {
697
+ const parsed = Number.parseInt(value, 10);
698
+ if (Number.isFinite(parsed)) return parsed;
699
+ }
700
+ return void 0;
701
+ }
702
+ function createBadgeFamily(options) {
703
+ const props = {
704
+ label: String,
705
+ text: String,
706
+ value: String,
707
+ status: String,
708
+ state: String,
709
+ variant: String,
710
+ tone: String,
711
+ emphasis: String
712
+ };
713
+ for (const key of options.extraProps) props[key] = String;
714
+ for (const key of options.booleanProps ?? []) props[key] = { type: [String, Boolean], default: void 0 };
715
+ return vue.defineComponent({
716
+ name: `Oods${options.component}`,
717
+ props,
718
+ setup(componentProps, { slots }) {
719
+ return () => {
720
+ const record = componentProps;
721
+ const content = authoredContent(slots.default?.());
722
+ const scalar = scalarContent(content);
723
+ const read = (key) => typeof record[key] === "boolean" && options.booleanProps?.includes(key) ? String(record[key]) : record[key];
724
+ const label = scalar ?? firstText(...options.labelKeys.map((key) => options.component === "CancellationBadge" && typeof record[key] === "boolean" && options.booleanProps?.includes(key) ? record[key] ? "Cancellation scheduled" : "No cancellation scheduled" : read(key))) ?? options.defaultLabel;
725
+ const status = firstText(...options.statusKeys.map(read));
726
+ const variant = firstText(record.variant) ?? options.defaultVariant;
727
+ return vue.h(Badge, {
728
+ status,
729
+ tone: record.tone,
730
+ emphasis: record.emphasis,
731
+ class: options.className,
732
+ "data-oods-component": options.component,
733
+ "data-badge-status": status,
734
+ "data-badge-variant": variant
735
+ }, {
736
+ default: () => content.length && scalar === void 0 ? content : [vue.h("span", { "data-oods-badge-label": "true" }, label)]
737
+ });
738
+ };
739
+ }
740
+ });
741
+ }
742
+ var AddressSummaryBadge = createBadgeFamily({
743
+ component: "AddressSummaryBadge",
744
+ className: "oods-address-summary-badge",
745
+ defaultLabel: "Address",
746
+ defaultVariant: "address",
747
+ labelKeys: ["label", "text", "role", "value"],
748
+ statusKeys: ["status", "state", "role"],
749
+ extraProps: ["role"]
750
+ });
751
+ var MessageStatusBadge = createBadgeFamily({
752
+ component: "MessageStatusBadge",
753
+ className: "oods-message-status-badge",
754
+ defaultLabel: "Message",
755
+ defaultVariant: "message",
756
+ labelKeys: ["label", "text", "status", "delivery", "value"],
757
+ statusKeys: ["status", "state", "delivery", "value"],
758
+ extraProps: ["delivery"]
759
+ });
760
+ var PreferenceSummaryBadge = createBadgeFamily({
761
+ component: "PreferenceSummaryBadge",
762
+ className: "oods-preference-summary-badge",
763
+ defaultLabel: "Preferences",
764
+ defaultVariant: "preference",
765
+ labelKeys: ["label", "text", "namespace", "value"],
766
+ statusKeys: ["status", "state", "version"],
767
+ extraProps: ["namespace", "version"]
768
+ });
769
+ var RoleBadgeList = vue.defineComponent({
770
+ name: "OodsRoleBadgeList",
771
+ props: {
772
+ roles: Array,
773
+ badges: Array,
774
+ roleLabels: Array,
775
+ value: Array,
776
+ variant: String,
777
+ tone: String,
778
+ label: String,
779
+ text: String
780
+ },
781
+ setup(props, { slots }) {
782
+ return () => {
783
+ const content = authoredContent(slots.default?.());
784
+ const items = normalizeTagItems(props.roles ?? props.badges ?? props.roleLabels ?? props.value);
785
+ const variant = firstText(props.variant, props.tone) ?? "roles";
786
+ return vue.h("span", {
787
+ class: "oods-role-badge-list",
788
+ "data-oods-component": "RoleBadgeList",
789
+ "data-badge-variant": variant
790
+ }, content.length ? content : items.length ? items.map((role, index) => vue.h("span", { key: index, "data-role-badge": "true" }, role)) : [firstText(props.label, props.text) ?? "Roles"]);
791
+ };
792
+ }
793
+ });
794
+ var TagPills = vue.defineComponent({
795
+ name: "OodsTagPills",
796
+ props: {
797
+ tags: Array,
798
+ value: Array,
799
+ maxVisible: [Number, String],
800
+ overflowLabel: String
801
+ },
802
+ setup(props, { slots }) {
803
+ return () => {
804
+ const content = authoredContent(slots.default?.());
805
+ const items = normalizeTagItems(props.tags ?? props.value);
806
+ const limit = asCount(props.maxVisible) ?? items.length;
807
+ const visible = items.slice(0, Math.max(0, limit));
808
+ const overflow = items.length - visible.length;
809
+ const template = firstText(props.overflowLabel);
810
+ const overflowText = overflow > 0 ? template ? template.replace("{{ tag_count }}", String(items.length)) : `+${overflow}` : "";
811
+ return vue.h("div", {
812
+ class: "oods-tag-pills",
813
+ "data-oods-component": "TagPills",
814
+ "data-summary-type": "tag-pills"
815
+ }, content.length ? content : [
816
+ ...visible.map((tag, index) => vue.h("span", { key: index, "data-tag-pill": "true" }, tag)),
817
+ overflowText ? vue.h("span", { "data-tag-overflow": "true" }, overflowText) : null
818
+ ]);
819
+ };
820
+ }
821
+ });
822
+ function normalizeTimelineItems(raw) {
823
+ if (!Array.isArray(raw)) return [];
824
+ const items = [];
825
+ for (const entry of raw) {
826
+ if (entry === void 0 || entry === null) continue;
827
+ if (isRecord(entry)) {
828
+ items.push({
829
+ label: firstScalar(entry.label, entry.title, entry.event, entry.status, entry.state, entry.text, entry.name) ?? "Event",
830
+ timestamp: firstScalar(entry.timestamp, entry.datetime, entry.time, entry.at, entry.createdAt, entry.updatedAt),
831
+ detail: firstScalar(entry.detail, entry.description, entry.reason, entry.message, entry.from, entry.to)
832
+ });
833
+ continue;
834
+ }
835
+ items.push({ label: typeof entry === "object" ? JSON.stringify(entry) : String(entry) });
836
+ }
837
+ return items;
838
+ }
839
+ function createTimelineFamily(options) {
840
+ const props = { title: String, label: String, heading: String, name: String };
841
+ for (const key of options.eventKeys) props[key] = Array;
842
+ return vue.defineComponent({
843
+ name: `Oods${options.component}`,
844
+ props,
845
+ setup(componentProps, { slots }) {
846
+ return () => {
847
+ const record = componentProps;
848
+ const content = authoredContent(slots.default?.());
849
+ const title = firstText(record.title, record.label, record.heading, record.name) ?? options.defaultTitle;
850
+ const events = normalizeTimelineItems(options.eventKeys.map((key) => record[key]).find((value) => Array.isArray(value)));
851
+ return vue.h("div", {
852
+ class: options.className,
853
+ "data-oods-component": options.component,
854
+ "data-timeline-type": options.timelineType,
855
+ role: "log",
856
+ "aria-label": title
857
+ }, [
858
+ vue.h("h2", { "data-timeline-title": "true" }, title),
859
+ vue.h("ol", { "data-timeline-events": "true" }, content.length ? content : events.length ? events.map((item, index) => vue.h("li", { key: index }, [vue.h("article", { "data-timeline-event": "true" }, [
860
+ vue.h("p", { "data-timeline-label": "true" }, item.label),
861
+ item.timestamp ? vue.h("time", { "data-timeline-time": "true", datetime: item.timestamp }, item.timestamp) : null,
862
+ item.detail ? vue.h("p", { "data-timeline-detail": "true" }, item.detail) : null
863
+ ])])) : [vue.h("li", { "data-timeline-empty": "true" }, "No events")])
864
+ ]);
865
+ };
866
+ }
867
+ });
868
+ }
869
+ var AddressValidationTimeline = createTimelineFamily({
870
+ component: "AddressValidationTimeline",
871
+ className: "oods-address-validation-timeline",
872
+ defaultTitle: "Address Validation Timeline",
873
+ timelineType: "address-validation",
874
+ eventKeys: ["events", "validations", "history"]
875
+ });
876
+ var MembershipAuditTimeline = createTimelineFamily({
877
+ component: "MembershipAuditTimeline",
878
+ className: "oods-membership-audit-timeline",
879
+ defaultTitle: "Membership Timeline",
880
+ timelineType: "membership",
881
+ eventKeys: ["events", "memberships", "history"]
882
+ });
883
+ var MessageEventTimeline = createTimelineFamily({
884
+ component: "MessageEventTimeline",
885
+ className: "oods-message-event-timeline",
886
+ defaultTitle: "Message Timeline",
887
+ timelineType: "message",
888
+ eventKeys: ["events", "messages", "statuses"]
889
+ });
890
+ var PreferenceTimeline = createTimelineFamily({
891
+ component: "PreferenceTimeline",
892
+ className: "oods-preference-timeline",
893
+ defaultTitle: "Preference Timeline",
894
+ timelineType: "preference",
895
+ eventKeys: ["events", "changes", "history"]
896
+ });
897
+ var AuditEvent = vue.defineComponent({
898
+ name: "OodsAuditEvent",
899
+ props: {
900
+ label: String,
901
+ title: String,
902
+ event: String,
903
+ status: String,
904
+ state: String,
905
+ reason: String,
906
+ text: String,
907
+ timestamp: String,
908
+ datetime: String,
909
+ time: String,
910
+ at: String,
911
+ createdAt: String,
912
+ updatedAt: String,
913
+ detail: String,
914
+ description: String,
915
+ message: String,
916
+ from: String,
917
+ to: String,
918
+ code: String
919
+ },
920
+ setup(props, { slots }) {
921
+ return () => {
922
+ const content = authoredContent(slots.default?.());
923
+ const label = firstText(props.label, props.title, props.event, props.status, props.state, props.reason, props.text) ?? "Audit Event";
924
+ const timestamp = firstText(props.timestamp, props.datetime, props.time, props.at, props.createdAt, props.updatedAt);
925
+ const detail = firstText(props.detail, props.description, props.reason, props.message, props.from, props.to, props.code);
926
+ return vue.h("article", {
927
+ class: "oods-audit-event",
928
+ "data-oods-component": "AuditEvent",
929
+ "data-event-type": "audit"
930
+ }, content.length ? content : [
931
+ timestamp ? vue.h("time", { "data-event-time": "true", datetime: timestamp }, timestamp) : null,
932
+ vue.h("p", { "data-event-label": "true" }, label),
933
+ detail ? vue.h("p", { "data-event-detail": "true" }, detail) : null
934
+ ]);
935
+ };
936
+ }
937
+ });
938
+ function normalizeSelectOptions(raw) {
939
+ if (!Array.isArray(raw)) return [];
940
+ const choices = [];
941
+ for (const entry of raw) {
942
+ if (isRecord(entry)) {
943
+ const text = (key) => typeof entry[key] === "string" && entry[key].length > 0 ? entry[key] : void 0;
944
+ const value2 = text("value") ?? text("id") ?? text("label") ?? "";
945
+ const label = text("label") ?? text("name") ?? componentContracts.formatReadOnlyValue(value2, "string", true);
946
+ if (!value2 && !label) continue;
947
+ choices.push({ value: value2, label });
948
+ continue;
949
+ }
950
+ if (entry === void 0 || entry === null) continue;
951
+ const value = typeof entry === "object" ? JSON.stringify(entry) : String(entry);
952
+ if (!value) continue;
953
+ choices.push({ value, label: componentContracts.formatReadOnlyValue(value, "string", true) });
954
+ }
955
+ return choices;
956
+ }
957
+ function selectOptionNodes(choices, selected) {
958
+ return choices.length ? choices.map((choice, index) => vue.h("option", { key: index, value: choice.value, selected: choice.value === selected }, choice.label)) : [vue.h("option", { value: "" }, "Select...")];
959
+ }
960
+ var preventSubmit = (event) => {
961
+ event.preventDefault();
962
+ };
963
+ var FORM_SHELL_PROPS = {
964
+ title: String,
965
+ label: String,
966
+ heading: String,
967
+ name: String,
968
+ description: String,
969
+ subtitle: String,
970
+ hint: String
971
+ };
972
+ function formHeader(title, subtitle) {
973
+ return vue.h("header", { "data-form-header": "true" }, [
974
+ vue.h("h2", title),
975
+ subtitle ? vue.h("span", { "data-form-subtitle": "true" }, subtitle) : null
976
+ ]);
977
+ }
978
+ function fieldsetHeader(title, subtitle) {
979
+ return [vue.h("legend", title), subtitle ? vue.h("span", { "data-form-subtitle": "true" }, subtitle) : null];
980
+ }
981
+ function addressRecord(form) {
982
+ const read = (name) => {
983
+ const control = form?.elements.namedItem(name);
984
+ return control instanceof HTMLInputElement ? control.value : "";
985
+ };
986
+ return { street: read("street"), city: read("city"), region: read("region"), postalCode: read("postalCode") };
987
+ }
988
+ var AddressEditor = vue.defineComponent({
989
+ name: "OodsAddressEditor",
990
+ props: {
991
+ ...FORM_SHELL_PROPS,
992
+ street: String,
993
+ line1: String,
994
+ addressLine1: String,
995
+ city: String,
996
+ region: String,
997
+ state: String,
998
+ postalCode: String,
999
+ zip: String
1000
+ },
1001
+ emits: { change: (_address) => true },
1002
+ setup(props, { emit, slots }) {
1003
+ return () => {
1004
+ const content = authoredContent(slots.default?.());
1005
+ const title = firstText(props.title, props.label, props.heading, props.name) ?? "Address Editor";
1006
+ const subtitle = firstText(props.description, props.subtitle, props.hint);
1007
+ const publish = (event) => emit("change", addressRecord(event.target.form));
1008
+ const control = (fieldLabel, fieldName, initial) => vue.h("label", { "data-form-control": "input" }, [
1009
+ vue.h("span", fieldLabel),
1010
+ vue.h("input", { type: "text", name: fieldName, value: initial, onInput: publish })
1011
+ ]);
1012
+ return vue.h("form", {
1013
+ class: "oods-address-editor",
1014
+ "data-oods-component": "AddressEditor",
1015
+ "data-form-type": "address-editor",
1016
+ onSubmit: preventSubmit
1017
+ }, [
1018
+ formHeader(title, subtitle),
1019
+ vue.h("div", { "data-form-content": "true" }, content.length ? content : [
1020
+ control("Street", "street", firstText(props.street, props.line1, props.addressLine1)),
1021
+ control("City", "city", firstText(props.city)),
1022
+ control("Region", "region", firstText(props.region, props.state)),
1023
+ control("Postal Code", "postalCode", firstText(props.postalCode, props.zip))
1024
+ ])
1025
+ ]);
1026
+ };
1027
+ }
1028
+ });
1029
+ var PreferenceEditor = vue.defineComponent({
1030
+ name: "OodsPreferenceEditor",
1031
+ props: {
1032
+ ...FORM_SHELL_PROPS,
1033
+ namespaces: Array,
1034
+ namespace: String,
1035
+ document: String,
1036
+ json: String,
1037
+ value: String
1038
+ },
1039
+ setup(props, { slots }) {
1040
+ return () => {
1041
+ const content = authoredContent(slots.default?.());
1042
+ const title = firstText(props.title, props.label, props.heading, props.name) ?? "Preference Editor";
1043
+ const subtitle = firstText(props.description, props.subtitle, props.hint);
1044
+ const choices = normalizeSelectOptions(Array.isArray(props.namespaces) ? props.namespaces : ["default"]);
1045
+ const selected = firstText(props.namespace) ?? choices[0]?.value;
1046
+ return vue.h("form", {
1047
+ class: "oods-preference-editor",
1048
+ "data-oods-component": "PreferenceEditor",
1049
+ "data-form-type": "preference-editor",
1050
+ onSubmit: preventSubmit
1051
+ }, [
1052
+ formHeader(title, subtitle),
1053
+ vue.h("div", { "data-form-content": "true" }, content.length ? content : [
1054
+ vue.h("label", { "data-form-control": "select" }, [vue.h("span", "Namespace"), vue.h("select", { name: "namespace", value: selected }, selectOptionNodes(choices, selected))]),
1055
+ vue.h("label", { "data-form-control": "textarea" }, [vue.h("span", "Preference Document"), vue.h("textarea", { name: "preferenceDocument", value: firstScalar(props.document, props.json, props.value) ?? "" })])
1056
+ ])
1057
+ ]);
1058
+ };
1059
+ }
1060
+ });
1061
+ var RoleAssignmentForm = vue.defineComponent({
1062
+ name: "OodsRoleAssignmentForm",
1063
+ props: {
1064
+ ...FORM_SHELL_PROPS,
1065
+ roles: Array,
1066
+ availableRoles: Array,
1067
+ role: String,
1068
+ defaultRoleId: String,
1069
+ assignee: String,
1070
+ member: String
1071
+ },
1072
+ setup(props, { slots }) {
1073
+ return () => {
1074
+ const content = authoredContent(slots.default?.());
1075
+ const title = firstText(props.title, props.label, props.heading, props.name) ?? "Role Assignment";
1076
+ const subtitle = firstText(props.description, props.subtitle, props.hint);
1077
+ const choices = normalizeSelectOptions(props.roles ?? props.availableRoles ?? []);
1078
+ const selected = firstText(props.role, props.defaultRoleId) ?? choices[0]?.value;
1079
+ return vue.h("form", {
1080
+ class: "oods-role-assignment-form",
1081
+ "data-oods-component": "RoleAssignmentForm",
1082
+ "data-form-type": "role-assignment",
1083
+ onSubmit: preventSubmit
1084
+ }, [
1085
+ formHeader(title, subtitle),
1086
+ vue.h("div", { "data-form-content": "true" }, content.length ? content : [
1087
+ vue.h("label", { "data-form-control": "select" }, [vue.h("span", "Role"), vue.h("select", { name: "role", value: selected }, selectOptionNodes(choices, selected))]),
1088
+ vue.h("label", { "data-form-control": "input" }, [vue.h("span", "Assignee"), vue.h("input", { type: "text", name: "assignee", value: firstText(props.assignee, props.member) })])
1089
+ ])
1090
+ ]);
1091
+ };
1092
+ }
1093
+ });
1094
+ var StatusSelector = vue.defineComponent({
1095
+ name: "OodsStatusSelector",
1096
+ props: {
1097
+ label: String,
1098
+ title: String,
1099
+ help: String,
1100
+ options: Array,
1101
+ states: Array,
1102
+ value: String,
1103
+ status: String,
1104
+ modelValue: String
1105
+ },
1106
+ emits: { "update:modelValue": (_value) => true, change: (_value) => true },
1107
+ setup(props, { emit, slots }) {
1108
+ return () => {
1109
+ const content = authoredContent(slots.default?.());
1110
+ const label = firstText(props.label, props.title) ?? "Status";
1111
+ const choices = normalizeSelectOptions(props.options ?? props.states ?? ["draft", "active", "inactive"]);
1112
+ const selected = props.modelValue ?? props.value ?? firstText(props.status);
1113
+ const onChange = (event) => {
1114
+ const next = event.target.value;
1115
+ emit("update:modelValue", next);
1116
+ emit("change", next);
1117
+ };
1118
+ return vue.h("div", {
1119
+ class: "oods-status-selector",
1120
+ "data-oods-component": "StatusSelector",
1121
+ "data-summary-type": "status-selector"
1122
+ }, content.length ? content : [
1123
+ vue.h("label", { "data-form-control": "select" }, [
1124
+ vue.h("span", label),
1125
+ vue.h("select", { name: "status", value: selected, onChange }, selectOptionNodes(choices, selected))
1126
+ ]),
1127
+ props.help ? vue.h("p", { class: "oods-field-help" }, props.help) : null
1128
+ ]);
1129
+ };
1130
+ }
1131
+ });
1132
+ var TagInput = vue.defineComponent({
1133
+ name: "OodsTagInput",
1134
+ props: {
1135
+ ...FORM_SHELL_PROPS,
1136
+ tags: Array,
1137
+ value: String,
1138
+ placeholder: String,
1139
+ modelValue: String
1140
+ },
1141
+ emits: { "update:modelValue": (_value) => true, input: (_value) => true, change: (_value) => true },
1142
+ setup(props, { emit, slots }) {
1143
+ return () => {
1144
+ const content = authoredContent(slots.default?.());
1145
+ const title = firstText(props.title, props.label, props.heading, props.name) ?? "Tag Input";
1146
+ const subtitle = firstText(props.description, props.subtitle, props.hint);
1147
+ const items = normalizeTagItems(props.tags);
1148
+ const current = props.modelValue ?? props.value;
1149
+ const read = (event) => event.target.value;
1150
+ return vue.h("fieldset", {
1151
+ class: "oods-tag-input",
1152
+ "data-oods-component": "TagInput",
1153
+ "data-form-type": "tag-input"
1154
+ }, [
1155
+ ...fieldsetHeader(title, subtitle),
1156
+ vue.h("div", { "data-form-content": "true" }, content.length ? content : [
1157
+ vue.h("label", { "data-form-control": "input" }, [
1158
+ vue.h("span", "Tag"),
1159
+ vue.h("input", {
1160
+ type: "text",
1161
+ name: "tag",
1162
+ placeholder: firstText(props.placeholder),
1163
+ value: current,
1164
+ onInput: (event) => {
1165
+ emit("update:modelValue", read(event));
1166
+ emit("input", read(event));
1167
+ },
1168
+ onChange: (event) => emit("change", read(event))
1169
+ })
1170
+ ]),
1171
+ items.length ? vue.h("div", { "data-tag-list": "true" }, items.map((tag, index) => vue.h("span", { key: index, "data-tag-item": "true" }, tag))) : null
1172
+ ])
1173
+ ]);
1174
+ };
1175
+ }
1176
+ });
1177
+ var TemplatePicker = vue.defineComponent({
1178
+ name: "OodsTemplatePicker",
1179
+ props: {
1180
+ ...FORM_SHELL_PROPS,
1181
+ templates: Array,
1182
+ options: Array,
1183
+ templateId: String,
1184
+ value: String,
1185
+ channels: Array,
1186
+ channel: String
1187
+ },
1188
+ setup(props, { slots }) {
1189
+ return () => {
1190
+ const content = authoredContent(slots.default?.());
1191
+ const title = firstText(props.title, props.label, props.heading, props.name) ?? "Template Picker";
1192
+ const subtitle = firstText(props.description, props.subtitle, props.hint);
1193
+ const templateChoices = normalizeSelectOptions(props.templates ?? props.options ?? []);
1194
+ const channelChoices = normalizeSelectOptions(props.channels ?? ["email", "sms", "in_app"]);
1195
+ const selectedTemplate = firstText(props.templateId, props.value) ?? templateChoices[0]?.value;
1196
+ const selectedChannel = firstText(props.channel) ?? channelChoices[0]?.value;
1197
+ return vue.h("fieldset", {
1198
+ class: "oods-template-picker",
1199
+ "data-oods-component": "TemplatePicker",
1200
+ "data-form-type": "template-picker"
1201
+ }, [
1202
+ ...fieldsetHeader(title, subtitle),
1203
+ vue.h("div", { "data-form-content": "true" }, content.length ? content : [
1204
+ vue.h("label", { "data-form-control": "select" }, [vue.h("span", "Template"), vue.h("select", { name: "template", value: selectedTemplate }, selectOptionNodes(templateChoices, selectedTemplate))]),
1205
+ vue.h("label", { "data-form-control": "select" }, [vue.h("span", "Channel"), vue.h("select", { name: "channel", value: selectedChannel }, selectOptionNodes(channelChoices, selectedChannel))])
1206
+ ])
1207
+ ]);
1208
+ };
1209
+ }
1210
+ });
1211
+ var truncateLabel = (value, maxLength) => {
1212
+ const limit = typeof maxLength === "number" ? maxLength : Number(maxLength);
1213
+ return !Number.isFinite(limit) || limit <= 0 || value.length <= limit ? value : `${value.slice(0, Math.max(0, limit - 1)).trimEnd()}...`;
1214
+ };
1215
+ var InlineLabel = vue.defineComponent({
1216
+ name: "OodsInlineLabel",
1217
+ props: { label: String, text: String, value: String, maxLength: [Number, String] },
1218
+ setup(props, { slots }) {
1219
+ return () => {
1220
+ const content = authoredContent(slots.default?.());
1221
+ return vue.h(
1222
+ "span",
1223
+ { class: "oods-inline-label", "data-oods-component": "InlineLabel" },
1224
+ content.length ? content : truncateLabel(firstText(props.label, props.text, props.value) ?? "", props.maxLength)
1225
+ );
1226
+ };
1227
+ }
1228
+ });
1229
+ var LabelCell = vue.defineComponent({
1230
+ name: "OodsLabelCell",
1231
+ props: { label: String, text: String, value: String, description: String, subtitle: String, sublabel: String, supporting: String, truncate: Boolean, maxLength: [Number, String] },
1232
+ setup(props, { slots }) {
1233
+ return () => {
1234
+ const content = authoredContent(slots.default?.());
1235
+ const limit = props.truncate ? props.maxLength ?? 40 : props.maxLength;
1236
+ const description = notEcho(firstText(props.description, props.subtitle, props.sublabel, props.supporting), firstText(props.label, props.text, props.value));
1237
+ return vue.h(
1238
+ "span",
1239
+ { class: "oods-label-cell", "data-oods-component": "LabelCell" },
1240
+ content.length ? content : [
1241
+ vue.h("span", { "data-oods-label-cell-primary": "true" }, truncateLabel(firstText(props.label, props.text, props.value) ?? "", limit)),
1242
+ description ? vue.h("span", { "data-oods-label-cell-description": "true" }, truncateLabel(description, limit)) : null
1243
+ ]
1244
+ );
1245
+ };
1246
+ }
1247
+ });
1248
+ var FormLabelGroup = vue.defineComponent({
1249
+ name: "OodsFormLabelGroup",
1250
+ props: { label: String, text: String, title: String, placeholder: String, hint: String, description: String, htmlFor: String, for: String, inputId: String },
1251
+ setup(props, { slots }) {
1252
+ return () => {
1253
+ const hint = firstText(props.placeholder, props.hint, props.description);
1254
+ return vue.h("label", { class: "oods-form-label-group", "data-oods-component": "FormLabelGroup", for: props.htmlFor ?? props.for ?? props.inputId }, [
1255
+ vue.h("span", { "data-oods-form-label": "true" }, firstText(props.label, props.text, props.title) ?? "Label"),
1256
+ ...authoredContent(slots.default?.()),
1257
+ hint ? vue.h("span", { "data-oods-form-hint": "true" }, hint) : null
1258
+ ]);
1259
+ };
1260
+ }
1261
+ });
1262
+ var ClassificationBadge = createBadgeFamily({
1263
+ component: "ClassificationBadge",
1264
+ className: "oods-classification-badge",
1265
+ defaultLabel: "Classification",
1266
+ defaultVariant: "classification",
1267
+ labelKeys: ["label", "text", "category", "value"],
1268
+ statusKeys: ["status", "state", "mode"],
1269
+ extraProps: ["category", "mode"]
1270
+ });
1271
+ var ClassificationEditor = vue.defineComponent({
1272
+ name: "OodsClassificationEditor",
1273
+ props: {
1274
+ title: String,
1275
+ label: String,
1276
+ heading: String,
1277
+ name: String,
1278
+ description: String,
1279
+ subtitle: String,
1280
+ hint: String,
1281
+ category: String,
1282
+ primaryCategory: String,
1283
+ tags: [String, Array],
1284
+ modes: Array,
1285
+ mode: String,
1286
+ classificationMode: String
1287
+ },
1288
+ setup(props, { slots }) {
1289
+ return () => {
1290
+ const content = authoredContent(slots.default?.());
1291
+ const tagText = typeof props.tags === "string" ? props.tags : props.tags === void 0 ? "" : JSON.stringify(props.tags);
1292
+ const choices = normalizeSelectOptions(Array.isArray(props.modes) ? props.modes : ["strict", "flexible"]);
1293
+ const selected = firstText(props.mode, props.classificationMode) ?? choices[0]?.value;
1294
+ return vue.h("form", { class: "oods-classification-editor", "data-oods-component": "ClassificationEditor", "data-form-type": "classification-editor", onSubmit: (event) => event.preventDefault() }, [
1295
+ formHeader(firstText(props.title, props.label, props.heading, props.name) ?? "Classification Editor", firstText(props.description, props.subtitle, props.hint)),
1296
+ vue.h("div", { "data-form-content": "true" }, content.length ? content : [
1297
+ vue.h("label", { "data-form-control": "input" }, [vue.h("span", "Category"), vue.h("input", { type: "text", name: "category", value: firstText(props.category, props.primaryCategory) ?? "" })]),
1298
+ vue.h("label", { "data-form-control": "input" }, [vue.h("span", "Tags"), vue.h("input", { type: "text", name: "tags", placeholder: "tag-1, tag-2", value: tagText })]),
1299
+ vue.h("label", { "data-form-control": "select" }, [vue.h("span", "Mode"), vue.h("select", { name: "mode", value: selected }, selectOptionNodes(choices, selected))])
1300
+ ])
1301
+ ]);
1302
+ };
1303
+ }
1304
+ });
1305
+ var OwnerBadge = createBadgeFamily({
1306
+ component: "OwnerBadge",
1307
+ className: "oods-owner-badge",
1308
+ defaultLabel: "Owner",
1309
+ defaultVariant: "owner",
1310
+ labelKeys: ["label", "text", "owner", "ownerType", "value"],
1311
+ statusKeys: ["status", "state"],
1312
+ extraProps: ["owner", "ownerType"]
1313
+ });
1314
+ var OwnershipSummary = vue.defineComponent({
1315
+ name: "OodsOwnershipSummary",
1316
+ props: { title: String, label: String, heading: String, name: String, ownerLabel: String, ownerId: String, owner_id: String, ownerType: String, owner_type: String, role: String, ownershipRole: String, summary: String, text: String, description: String },
1317
+ setup(props, { slots }) {
1318
+ return () => {
1319
+ const content = authoredContent(slots.default?.());
1320
+ const terms = [["Owner", props.ownerId === void 0 && props.owner_id === void 0 && props.ownerLabel === void 0 ? void 0 : componentContracts.formatReferenceLabel(props.ownerId ?? props.owner_id, props.ownerLabel, "Owner")], ["Owner Type", firstScalar(props.ownerType, props.owner_type)], ["Role", firstScalar(props.role, props.ownershipRole)]].filter((entry) => entry[1] !== void 0);
1321
+ const fallback = firstText(props.summary, props.text, props.description);
1322
+ return vue.h("section", { class: "oods-ownership-summary", "data-oods-component": "OwnershipSummary", "data-summary-type": "ownership" }, [
1323
+ vue.h("h2", { "data-summary-title": "true" }, firstText(props.title, props.label, props.heading, props.name) ?? "Ownership Summary"),
1324
+ ...content.length ? content : terms.length ? [vue.h("dl", terms.map(([term, value]) => vue.h("div", { key: term, "data-summary-item": "true" }, [vue.h("dt", term), vue.h("dd", value)])))] : fallback ? [vue.h("p", { "data-summary-fallback": "true" }, fallback)] : [vue.h("dl")]
1325
+ ]);
1326
+ };
1327
+ }
1328
+ });
1329
+ var TagSummary = vue.defineComponent({
1330
+ name: "OodsTagSummary",
1331
+ props: { title: String, label: String, heading: String, name: String, tagCount: [Number, String], count: [Number, String], tags: [String, Array], summary: String, text: String, description: String },
1332
+ setup(props, { slots }) {
1333
+ return () => {
1334
+ const content = authoredContent(slots.default?.());
1335
+ const tagText = Array.isArray(props.tags) ? normalizeTagItems(props.tags).join(", ") || void 0 : firstText(props.tags);
1336
+ const terms = [["Tag Count", firstScalar(props.tagCount, props.count)], ["Tags", tagText]].filter((entry) => entry[1] !== void 0);
1337
+ const fallback = firstText(props.summary, props.text, props.description);
1338
+ return vue.h("section", { class: "oods-tags-summary", "data-oods-component": "TagSummary", "data-summary-type": "tags" }, [
1339
+ vue.h("h2", { "data-summary-title": "true" }, firstText(props.title, props.label, props.heading, props.name) ?? "Tag Summary"),
1340
+ ...content.length ? content : terms.length ? [vue.h("dl", terms.map(([term, value]) => vue.h("div", { key: term, "data-summary-item": "true" }, [vue.h("dt", term), vue.h("dd", value)])))] : fallback ? [vue.h("p", { "data-summary-fallback": "true" }, fallback)] : [vue.h("dl")]
1341
+ ]);
1342
+ };
1343
+ }
1344
+ });
1345
+ var OwnershipMeta = vue.defineComponent({
1346
+ name: "OodsOwnershipMeta",
1347
+ props: { title: String, label: String, heading: String, name: String, ownerType: String, owner_type: String, role: String, ownershipRole: String },
1348
+ setup(props, { slots }) {
1349
+ return () => {
1350
+ const content = authoredContent(slots.default?.());
1351
+ const terms = [["Owner Type", firstScalar(props.ownerType, props.owner_type)], ["Role", firstScalar(props.role, props.ownershipRole)]].filter((entry) => entry[1] !== void 0);
1352
+ return vue.h("div", { class: "oods-ownership-meta", "data-oods-component": "OwnershipMeta", "data-meta-type": "ownership" }, content.length ? content : [
1353
+ vue.h("span", { "data-meta-title": "true" }, firstText(props.title, props.label, props.heading, props.name) ?? "Ownership"),
1354
+ ...terms.map(([term, value]) => vue.h("span", { key: term, "data-meta-item": "true" }, [vue.h("strong", `${term}:`), ` ${value}`]))
1355
+ ]);
1356
+ };
1357
+ }
1358
+ });
1359
+ var ArchivePill = createBadgeFamily({
1360
+ component: "ArchivePill",
1361
+ className: "oods-archive-pill",
1362
+ defaultLabel: "Archive",
1363
+ defaultVariant: "archive",
1364
+ labelKeys: ["label", "text", "status", "state", "isArchived", "value"],
1365
+ statusKeys: ["status", "state", "isArchived", "value"],
1366
+ extraProps: ["isArchived"],
1367
+ booleanProps: ["isArchived", "value"]
1368
+ });
1369
+ var CancellationBadge = createBadgeFamily({
1370
+ component: "CancellationBadge",
1371
+ className: "oods-cancellation-badge",
1372
+ defaultLabel: "Cancellation",
1373
+ defaultVariant: "cancellation",
1374
+ labelKeys: ["label", "text", "status", "state", "cancelAtPeriodEnd", "value"],
1375
+ statusKeys: ["status", "state", "cancelAtPeriodEnd", "isCancelled", "value"],
1376
+ extraProps: ["cancelAtPeriodEnd", "isCancelled"],
1377
+ booleanProps: ["cancelAtPeriodEnd", "isCancelled", "value"]
1378
+ });
1379
+ var ArchiveSummary = vue.defineComponent({
1380
+ name: "OodsArchiveSummary",
1381
+ props: { title: String, label: String, heading: String, name: String, isArchived: { type: [String, Boolean], default: void 0 }, archived: { type: [String, Boolean], default: void 0 }, status: { type: [String, Boolean], default: void 0 }, archivedAt: String, reason: String, archiveReason: String, summary: String, text: String, description: String },
1382
+ setup(props, { slots }) {
1383
+ return () => {
1384
+ const content = authoredContent(slots.default?.());
1385
+ const terms = [["Archived", componentContracts.summaryValue(props.isArchived ?? props.archived ?? props.status)], ["Archived At", props.archivedAt ? componentContracts.formatDateTime(props.archivedAt) : void 0], ["Reason", firstText(props.reason, props.archiveReason)]].filter((entry) => entry[1] !== void 0);
1386
+ const fallback = firstText(props.summary, props.text, props.description);
1387
+ return vue.h("section", { class: "oods-archive-summary", "data-oods-component": "ArchiveSummary", "data-summary-type": "archive" }, [
1388
+ vue.h("h2", { "data-summary-title": "true" }, firstText(props.title, props.label, props.heading, props.name) ?? "Archive Summary"),
1389
+ ...content.length ? content : terms.length ? [vue.h("dl", terms.map(([term, value]) => vue.h("div", { key: term, "data-summary-item": "true" }, [vue.h("dt", term), vue.h("dd", value)])))] : fallback ? [vue.h("p", { "data-summary-fallback": "true" }, fallback)] : [vue.h("dl")]
1390
+ ]);
1391
+ };
1392
+ }
1393
+ });
1394
+ var PriceCardMeta = vue.defineComponent({
1395
+ name: "OodsPriceCardMeta",
1396
+ props: { title: String, label: String, heading: String, name: String, model: String, pricingModel: String, interval: String, billingInterval: String },
1397
+ setup(props, { slots }) {
1398
+ return () => {
1399
+ const content = authoredContent(slots.default?.());
1400
+ const terms = [["Model", firstText(props.model, props.pricingModel)], ["Interval", firstText(props.interval, props.billingInterval)]].filter((entry) => entry[1] !== void 0);
1401
+ return vue.h("div", { class: "oods-price-card-meta", "data-oods-component": "PriceCardMeta", "data-meta-type": "price" }, content.length ? content : [
1402
+ vue.h("span", { "data-meta-title": "true" }, firstText(props.title, props.label, props.heading, props.name) ?? "Price"),
1403
+ ...terms.map(([term, value]) => vue.h("span", { key: term, "data-meta-item": "true" }, [vue.h("strong", `${term}:`), ` ${value}`]))
1404
+ ]);
1405
+ };
1406
+ }
1407
+ });
1408
+ var CancellationForm = vue.defineComponent({
1409
+ name: "OodsCancellationForm",
1410
+ props: {
1411
+ title: String,
1412
+ label: String,
1413
+ heading: String,
1414
+ name: String,
1415
+ description: String,
1416
+ subtitle: String,
1417
+ hint: String,
1418
+ allowedReasons: Array,
1419
+ reasonCode: String,
1420
+ reason: String,
1421
+ cancellationReason: String,
1422
+ embedded: Boolean,
1423
+ reasonHelp: String,
1424
+ codeHelp: String
1425
+ },
1426
+ setup(props, { slots }) {
1427
+ return () => {
1428
+ const content = authoredContent(slots.default?.());
1429
+ const choices = normalizeSelectOptions(props.allowedReasons ?? ["no_longer_needed", "budget", "duplicate"]);
1430
+ if (props.reasonCode && !choices.some((choice) => choice.value === props.reasonCode)) choices.unshift({ value: props.reasonCode, label: props.reasonCode });
1431
+ return vue.h(props.embedded ? "fieldset" : "form", { class: "oods-cancellation-form", "data-oods-component": "CancellationForm", "data-form-type": "cancellation", onSubmit: props.embedded ? void 0 : (event) => event.preventDefault() }, [
1432
+ formHeader(firstText(props.title, props.label, props.heading, props.name) ?? "Cancellation Form", firstText(props.description, props.subtitle, props.hint)),
1433
+ vue.h("div", { "data-form-content": "true" }, content.length ? content : [
1434
+ vue.h("label", { "data-form-control": "select" }, [vue.h("span", "Reason Code"), props.allowedReasons?.length === 0 ? vue.h("input", { name: "reasonCode", value: props.reasonCode ?? "" }) : vue.h("select", { name: "reasonCode", value: props.reasonCode }, selectOptionNodes(choices, props.reasonCode))]),
1435
+ props.codeHelp ? vue.h("p", { class: "oods-field-help" }, props.codeHelp) : null,
1436
+ vue.h("label", { "data-form-control": "textarea" }, [vue.h("span", "Reason"), vue.h("textarea", { name: "reason", value: firstText(props.reason, props.cancellationReason) ?? "" })]),
1437
+ props.reasonHelp ? vue.h("p", { class: "oods-field-help" }, props.reasonHelp) : null
1438
+ ])
1439
+ ]);
1440
+ };
1441
+ }
1442
+ });
1443
+ function validationStyle(validation) {
1444
+ if (!validation) return void 0;
1445
+ const tone = validation.state === "error" ? "critical" : validation.state;
1446
+ return {
1447
+ "--cmp-input-message-border": `var(--sys-status-${tone}-border)`,
1448
+ "--cmp-input-message-text": `var(--sys-status-${tone}-text)`
1449
+ };
1450
+ }
1451
+ function useFieldMetadata(componentName, props, slots) {
1452
+ const generatedId = vue.useId();
1453
+ const controlId = vue.computed(() => props.id || `oods-${componentName.toLowerCase()}-${generatedId}`);
1454
+ const helpId = vue.computed(() => `${controlId.value}-help`);
1455
+ const validationId = vue.computed(() => `${controlId.value}-validation`);
1456
+ const describedBy = vue.computed(() => {
1457
+ const ids = [];
1458
+ if (props.help || slots.help) ids.push(helpId.value);
1459
+ if (props.validation?.message || slots.validation) ids.push(validationId.value);
1460
+ return ids.length > 0 ? ids.join(" ") : void 0;
1461
+ });
1462
+ return { controlId, helpId, validationId, describedBy };
1463
+ }
1464
+ function renderField(componentName, props, slots, metadata, control) {
1465
+ const label = slots.label?.() ?? props.label;
1466
+ const help = slots.help?.() ?? props.help;
1467
+ const validation = slots.validation?.() ?? props.validation?.message;
1468
+ return vue.h("div", {
1469
+ class: "oods-field",
1470
+ "data-oods-component": componentName,
1471
+ style: validationStyle(props.validation)
1472
+ }, [
1473
+ label ? vue.h("label", { class: "oods-field-label", for: metadata.controlId.value }, [label, props.required ? vue.h("span", { class: "oods-field-required", "aria-hidden": "true" }, "*") : null]) : null,
1474
+ control,
1475
+ help ? vue.h("p", { id: metadata.helpId.value, class: "oods-field-help" }, help) : null,
1476
+ validation ? vue.h("p", {
1477
+ id: metadata.validationId.value,
1478
+ class: "oods-field-error",
1479
+ role: props.validation?.state === "error" ? "alert" : void 0
1480
+ }, validation) : null
1481
+ ]);
1482
+ }
1483
+ var textFieldProps = {
1484
+ id: String,
1485
+ label: String,
1486
+ modelValue: String,
1487
+ value: String,
1488
+ defaultValue: { type: String, default: "" },
1489
+ placeholder: String,
1490
+ required: Boolean,
1491
+ disabled: Boolean,
1492
+ readOnly: Boolean,
1493
+ help: String,
1494
+ validation: Object,
1495
+ name: String,
1496
+ min: String,
1497
+ max: String,
1498
+ step: [String, Number]
1499
+ };
1500
+ function eventValue(event) {
1501
+ return event.target.value;
1502
+ }
1503
+ var Input = vue.defineComponent({
1504
+ name: "OodsInput",
1505
+ props: {
1506
+ ...textFieldProps,
1507
+ type: { type: String, default: "text" }
1508
+ },
1509
+ emits: {
1510
+ "update:modelValue": (_value) => true,
1511
+ input: (_value) => true,
1512
+ change: (_value) => true
1513
+ },
1514
+ setup(props, { emit, slots }) {
1515
+ const internalValue = vue.ref(props.defaultValue);
1516
+ const currentValue = vue.computed(() => props.modelValue ?? props.value ?? internalValue.value);
1517
+ const metadata = useFieldMetadata("Input", props, slots);
1518
+ const updateValue = (nextValue) => {
1519
+ if (props.modelValue === void 0 && props.value === void 0) internalValue.value = nextValue;
1520
+ emit("update:modelValue", nextValue);
1521
+ emit("input", nextValue);
1522
+ };
1523
+ return () => renderField("Input", props, slots, metadata, vue.h("input", {
1524
+ id: metadata.controlId.value,
1525
+ class: "oods-field-control",
1526
+ type: props.type,
1527
+ name: props.name,
1528
+ value: props.type === "datetime-local" ? componentContracts.dateTimeInputValue(currentValue.value) : currentValue.value,
1529
+ placeholder: props.placeholder,
1530
+ required: props.required,
1531
+ disabled: props.disabled,
1532
+ readonly: props.readOnly,
1533
+ min: props.min,
1534
+ max: props.max,
1535
+ step: props.step,
1536
+ "aria-invalid": props.validation?.state === "error" ? "true" : void 0,
1537
+ "aria-describedby": metadata.describedBy.value,
1538
+ "aria-errormessage": props.validation?.state === "error" ? metadata.validationId.value : void 0,
1539
+ onInput: (event) => updateValue(eventValue(event)),
1540
+ onChange: (event) => emit("change", eventValue(event))
1541
+ }));
1542
+ }
1543
+ });
1544
+ var DatePicker = vue.defineComponent({
1545
+ name: "OodsDatePicker",
1546
+ inheritAttrs: false,
1547
+ props: textFieldProps,
1548
+ emits: {
1549
+ "update:modelValue": (_value) => true,
1550
+ input: (_value) => true,
1551
+ change: (_value) => true
1552
+ },
1553
+ setup(props, { attrs, emit, slots }) {
1554
+ return () => vue.h("div", {
1555
+ ...attrs,
1556
+ class: ["oods-date-picker", attrs.class],
1557
+ "data-oods-component": "DatePicker"
1558
+ }, [vue.h(Input, {
1559
+ ...props,
1560
+ type: "date",
1561
+ "onUpdate:modelValue": (value) => emit("update:modelValue", value),
1562
+ onInput: (value) => emit("input", value),
1563
+ onChange: (value) => emit("change", value)
1564
+ }, slots)]);
1565
+ }
1566
+ });
1567
+ var Textarea = vue.defineComponent({
1568
+ name: "OodsTextarea",
1569
+ props: {
1570
+ ...textFieldProps,
1571
+ rows: { type: Number, default: 3 }
1572
+ },
1573
+ emits: {
1574
+ "update:modelValue": (_value) => true,
1575
+ input: (_value) => true,
1576
+ change: (_value) => true
1577
+ },
1578
+ setup(props, { emit, slots }) {
1579
+ const internalValue = vue.ref(props.defaultValue);
1580
+ const currentValue = vue.computed(() => props.modelValue ?? props.value ?? internalValue.value);
1581
+ const metadata = useFieldMetadata("Textarea", props, slots);
1582
+ const updateValue = (nextValue) => {
1583
+ if (props.modelValue === void 0 && props.value === void 0) internalValue.value = nextValue;
1584
+ emit("update:modelValue", nextValue);
1585
+ emit("input", nextValue);
1586
+ };
1587
+ return () => renderField("Textarea", props, slots, metadata, vue.h("textarea", {
1588
+ id: metadata.controlId.value,
1589
+ class: "oods-field-control",
1590
+ name: props.name,
1591
+ value: currentValue.value,
1592
+ rows: props.rows,
1593
+ placeholder: props.placeholder,
1594
+ required: props.required,
1595
+ disabled: props.disabled,
1596
+ readonly: props.readOnly,
1597
+ "aria-invalid": props.validation?.state === "error" ? "true" : void 0,
1598
+ "aria-describedby": metadata.describedBy.value,
1599
+ "aria-errormessage": props.validation?.state === "error" ? metadata.validationId.value : void 0,
1600
+ onInput: (event) => updateValue(eventValue(event)),
1601
+ onChange: (event) => emit("change", eventValue(event))
1602
+ }));
1603
+ }
1604
+ });
1605
+ var Select = vue.defineComponent({
1606
+ name: "OodsSelect",
1607
+ props: {
1608
+ id: String,
1609
+ label: String,
1610
+ modelValue: String,
1611
+ value: String,
1612
+ defaultValue: { type: String, default: "" },
1613
+ placeholder: String,
1614
+ required: Boolean,
1615
+ disabled: Boolean,
1616
+ help: String,
1617
+ validation: Object,
1618
+ name: String,
1619
+ options: { type: Array, default: () => [] }
1620
+ },
1621
+ emits: {
1622
+ "update:modelValue": (_value) => true,
1623
+ change: (_value) => true
1624
+ },
1625
+ setup(props, { emit, slots }) {
1626
+ const internalValue = vue.ref(props.defaultValue);
1627
+ const currentValue = vue.computed(() => props.modelValue ?? props.value ?? internalValue.value);
1628
+ const metadata = useFieldMetadata("Select", props, slots);
1629
+ const selectValue = (nextValue) => {
1630
+ if (props.modelValue === void 0 && props.value === void 0) internalValue.value = nextValue;
1631
+ emit("update:modelValue", nextValue);
1632
+ emit("change", nextValue);
1633
+ };
1634
+ return () => renderField("Select", props, slots, metadata, vue.h("select", {
1635
+ id: metadata.controlId.value,
1636
+ class: "oods-field-control",
1637
+ name: props.name,
1638
+ value: currentValue.value,
1639
+ required: props.required,
1640
+ disabled: props.disabled,
1641
+ "aria-invalid": props.validation?.state === "error" ? "true" : void 0,
1642
+ "aria-describedby": metadata.describedBy.value,
1643
+ "aria-errormessage": props.validation?.state === "error" ? metadata.validationId.value : void 0,
1644
+ onChange: (event) => selectValue(eventValue(event))
1645
+ }, [
1646
+ props.placeholder ? vue.h("option", {
1647
+ value: "",
1648
+ selected: currentValue.value === "",
1649
+ disabled: true
1650
+ }, props.placeholder) : null,
1651
+ ...props.options.map((option) => vue.h("option", {
1652
+ key: option.value,
1653
+ value: option.value,
1654
+ selected: option.value === currentValue.value,
1655
+ disabled: option.disabled
1656
+ }, slots.option?.({ option }) ?? option.label))
1657
+ ]));
1658
+ }
1659
+ });
1660
+ var Checkbox = vue.defineComponent({
1661
+ name: "OodsCheckbox",
1662
+ props: {
1663
+ id: String,
1664
+ label: String,
1665
+ modelValue: { type: Boolean, default: void 0 },
1666
+ checked: { type: Boolean, default: void 0 },
1667
+ defaultChecked: Boolean,
1668
+ required: Boolean,
1669
+ disabled: Boolean,
1670
+ help: String,
1671
+ validation: Object,
1672
+ name: String
1673
+ },
1674
+ emits: {
1675
+ "update:modelValue": (_value) => true,
1676
+ change: (_value) => true
1677
+ },
1678
+ setup(props, { emit, slots }) {
1679
+ const internalChecked = vue.ref(props.defaultChecked);
1680
+ const currentChecked = vue.computed(() => props.modelValue ?? props.checked ?? internalChecked.value);
1681
+ const metadata = useFieldMetadata("Checkbox", props, slots);
1682
+ const change = (event) => {
1683
+ const checked = event.target.checked;
1684
+ if (props.modelValue === void 0 && props.checked === void 0) internalChecked.value = checked;
1685
+ emit("update:modelValue", checked);
1686
+ emit("change", checked);
1687
+ };
1688
+ return () => {
1689
+ const label = slots.label?.() ?? props.label;
1690
+ const help = slots.help?.() ?? props.help;
1691
+ const validation = slots.validation?.() ?? props.validation?.message;
1692
+ return vue.h("div", {
1693
+ class: "oods-field",
1694
+ "data-oods-component": "Checkbox",
1695
+ style: validationStyle(props.validation)
1696
+ }, [
1697
+ vue.h("label", { class: "oods-checkbox", for: metadata.controlId.value }, [
1698
+ vue.h("input", {
1699
+ id: metadata.controlId.value,
1700
+ type: "checkbox",
1701
+ name: props.name,
1702
+ checked: currentChecked.value,
1703
+ required: props.required,
1704
+ disabled: props.disabled,
1705
+ "aria-invalid": props.validation?.state === "error" ? "true" : void 0,
1706
+ "aria-describedby": metadata.describedBy.value,
1707
+ "aria-errormessage": props.validation?.state === "error" ? metadata.validationId.value : void 0,
1708
+ onChange: change
1709
+ }),
1710
+ vue.h("span", { class: "oods-field-label" }, [
1711
+ label,
1712
+ props.required ? vue.h("span", { class: "oods-field-required", "aria-hidden": "true" }, "*") : null
1713
+ ])
1714
+ ]),
1715
+ help ? vue.h("p", { id: metadata.helpId.value, class: "oods-field-help" }, help) : null,
1716
+ validation ? vue.h("p", {
1717
+ id: metadata.validationId.value,
1718
+ class: "oods-field-error",
1719
+ role: props.validation?.state === "error" ? "alert" : void 0
1720
+ }, validation) : null
1721
+ ]);
1722
+ };
1723
+ }
1724
+ });
1725
+ var TableCaption = vue.defineComponent({
1726
+ name: "OodsTableCaption",
1727
+ setup(_props, { slots }) {
1728
+ return () => vue.h("caption", {}, slots.default?.());
1729
+ }
1730
+ });
1731
+ var TableHead = vue.defineComponent({
1732
+ name: "OodsTableHead",
1733
+ setup(_props, { slots }) {
1734
+ return () => vue.h("thead", {}, slots.default?.());
1735
+ }
1736
+ });
1737
+ var TableBody = vue.defineComponent({
1738
+ name: "OodsTableBody",
1739
+ setup(_props, { slots }) {
1740
+ return () => vue.h("tbody", {}, slots.default?.());
1741
+ }
1742
+ });
1743
+ var TableRow = vue.defineComponent({
1744
+ name: "OodsTableRow",
1745
+ setup(_props, { slots }) {
1746
+ return () => vue.h("tr", {}, slots.default?.());
1747
+ }
1748
+ });
1749
+ var TableHeaderCell = vue.defineComponent({
1750
+ name: "OodsTableHeaderCell",
1751
+ props: {
1752
+ scope: { type: String, default: "col" }
1753
+ },
1754
+ setup(props, { slots }) {
1755
+ return () => vue.h("th", { scope: props.scope }, slots.default?.());
1756
+ }
1757
+ });
1758
+ var TableCell = vue.defineComponent({
1759
+ name: "OodsTableCell",
1760
+ setup(_props, { slots }) {
1761
+ return () => vue.h("td", {}, slots.default?.());
1762
+ }
1763
+ });
1764
+ var Table = vue.defineComponent({
1765
+ name: "OodsTable",
1766
+ props: {
1767
+ caption: { type: String, default: "" },
1768
+ columns: { type: Array, default: () => [] },
1769
+ rows: { type: Array, default: () => [] },
1770
+ density: { type: String, default: "default" },
1771
+ selectable: Boolean
1772
+ },
1773
+ emits: {
1774
+ rowActivate: (_rowId) => true
1775
+ },
1776
+ setup(props, { emit, slots }) {
1777
+ const activate = (row) => emit("rowActivate", row.id);
1778
+ return () => vue.h("table", {
1779
+ class: "oods-table",
1780
+ "data-oods-component": "Table",
1781
+ "data-density": props.density
1782
+ }, [
1783
+ vue.h(TableCaption, {}, {
1784
+ default: () => slots.caption?.() ?? props.caption
1785
+ }),
1786
+ vue.h(TableHead, {}, {
1787
+ default: () => slots.head?.() ?? vue.h(TableRow, {}, {
1788
+ default: () => props.columns.map((column) => vue.h(TableHeaderCell, { key: column.key }, {
1789
+ default: () => slots.headerCell?.({ column }) ?? column.label
1790
+ }))
1791
+ })
1792
+ }),
1793
+ vue.h(TableBody, {}, {
1794
+ default: () => slots.body?.() ?? (props.rows.length === 0 ? vue.h(TableRow, {}, {
1795
+ default: () => vue.h(TableCell, {
1796
+ colspan: Math.max(props.columns.length, 1)
1797
+ }, {
1798
+ default: () => "No rows available."
1799
+ })
1800
+ }) : props.rows.map((row, rowIndex) => slots.row?.({ row, rowIndex, activate: () => activate(row) }) ?? vue.h(TableRow, { key: row.id }, {
1801
+ default: () => props.columns.map((column, columnIndex) => {
1802
+ const content = slots.cell?.({ row, column, value: row[column.key] }) ?? String(row[column.key] ?? "");
1803
+ return vue.h(TableCell, { key: column.key }, {
1804
+ default: () => props.selectable && columnIndex === 0 ? vue.h("button", {
1805
+ type: "button",
1806
+ class: "oods-table-row-action",
1807
+ onClick: () => activate(row)
1808
+ }, content) : content
1809
+ });
1810
+ })
1811
+ })))
1812
+ })
1813
+ ]);
1814
+ }
1815
+ });
1816
+ function itemDisabled(item) {
1817
+ return item.disabled === true || item.isDisabled === true;
1818
+ }
1819
+ function enabledItems(items) {
1820
+ return items.filter((item) => !itemDisabled(item));
1821
+ }
1822
+ function initialTabId(items, preferred) {
1823
+ if (preferred && items.some((item) => item.id === preferred && !itemDisabled(item))) return preferred;
1824
+ return enabledItems(items)[0]?.id ?? "";
1825
+ }
1826
+ function domId(value) {
1827
+ return value.replace(/[^A-Za-z0-9_-]/g, "-");
1828
+ }
1829
+ var Tabs = vue.defineComponent({
1830
+ name: "OodsTabs",
1831
+ props: {
1832
+ items: { type: Array, default: () => [] },
1833
+ selectedId: String,
1834
+ defaultSelectedId: String,
1835
+ size: { type: String, default: "md" },
1836
+ overflowLabel: { type: String, default: "More" },
1837
+ ariaLabel: { type: String, default: "Tabs" }
1838
+ },
1839
+ emits: {
1840
+ "update:selectedId": (_id) => true,
1841
+ change: (_id) => true
1842
+ },
1843
+ setup(props, { emit, slots }) {
1844
+ const baseId = domId(vue.useId());
1845
+ const internalSelectedId = vue.ref(initialTabId(props.items, props.defaultSelectedId));
1846
+ const activeId = vue.computed(() => props.selectedId !== void 0 ? initialTabId(props.items, props.selectedId) : initialTabId(props.items, internalSelectedId.value));
1847
+ const tabRefs = /* @__PURE__ */ new Map();
1848
+ const tabWidths = /* @__PURE__ */ new Map();
1849
+ const listRef = vue.ref();
1850
+ const visibleLimit = vue.ref(Number.POSITIVE_INFINITY);
1851
+ const overflowOpen = vue.ref(false);
1852
+ let resizeObserver;
1853
+ const visibleItems = vue.computed(() => {
1854
+ if (visibleLimit.value >= props.items.length) return [...props.items];
1855
+ const count = Math.max(1, visibleLimit.value);
1856
+ const visible = props.items.slice(0, count);
1857
+ const active = props.items.find((item) => item.id === activeId.value);
1858
+ if (active && !visible.some((item) => item.id === active.id)) {
1859
+ visible.splice(Math.max(visible.length - 1, 0), 1, active);
1860
+ visible.sort((left, right) => props.items.findIndex((item) => item.id === left.id) - props.items.findIndex((item) => item.id === right.id));
1861
+ }
1862
+ return visible;
1863
+ });
1864
+ const overflowItems = vue.computed(() => props.items.filter((item) => !visibleItems.value.some((visible) => visible.id === item.id)));
1865
+ const tabId = (id) => `oods-${baseId}-tab-${domId(id)}`;
1866
+ const panelId = (id) => `oods-${baseId}-panel-${domId(id)}`;
1867
+ const focusTab = (id) => vue.nextTick(() => tabRefs.get(id)?.focus());
1868
+ const selectTab = (id) => {
1869
+ const item = props.items.find((candidate) => candidate.id === id);
1870
+ if (!item || itemDisabled(item)) return;
1871
+ if (props.selectedId === void 0) internalSelectedId.value = id;
1872
+ emit("update:selectedId", id);
1873
+ emit("change", id);
1874
+ void focusTab(id);
1875
+ };
1876
+ const onTabKeydown = (event, currentId) => {
1877
+ const available = enabledItems(props.items);
1878
+ if (available.length === 0) return;
1879
+ const currentIndex = Math.max(available.findIndex((item) => item.id === currentId), 0);
1880
+ let nextIndex;
1881
+ if (event.key === "ArrowRight") nextIndex = (currentIndex + 1) % available.length;
1882
+ if (event.key === "ArrowLeft") nextIndex = (currentIndex - 1 + available.length) % available.length;
1883
+ if (event.key === "Home") nextIndex = 0;
1884
+ if (event.key === "End") nextIndex = available.length - 1;
1885
+ if (nextIndex === void 0) return;
1886
+ event.preventDefault();
1887
+ selectTab(available[nextIndex].id);
1888
+ };
1889
+ const recalculateOverflow = () => {
1890
+ const list = listRef.value;
1891
+ if (!list || list.clientWidth <= 0 || props.items.length <= 1) {
1892
+ visibleLimit.value = Number.POSITIVE_INFINITY;
1893
+ return;
1894
+ }
1895
+ for (const [id, element] of tabRefs) {
1896
+ if (element.offsetWidth > 0) tabWidths.set(id, element.offsetWidth);
1897
+ }
1898
+ const widths = props.items.map((item) => tabWidths.get(item.id) ?? 120);
1899
+ const total = widths.reduce((sum, width) => sum + width, 0);
1900
+ if (total <= list.clientWidth) {
1901
+ visibleLimit.value = Number.POSITIVE_INFINITY;
1902
+ return;
1903
+ }
1904
+ const availableWidth = Math.max(list.clientWidth - 96, 0);
1905
+ let usedWidth = 0;
1906
+ let count = 0;
1907
+ for (const width of widths) {
1908
+ if (count > 0 && usedWidth + width > availableWidth) break;
1909
+ usedWidth += width;
1910
+ count += 1;
1911
+ }
1912
+ visibleLimit.value = Math.max(1, count);
1913
+ };
1914
+ vue.watch(
1915
+ () => props.items,
1916
+ (items) => {
1917
+ if (!items.some((item) => item.id === internalSelectedId.value && !itemDisabled(item))) {
1918
+ internalSelectedId.value = initialTabId(items, props.defaultSelectedId);
1919
+ }
1920
+ void vue.nextTick(recalculateOverflow);
1921
+ }
1922
+ );
1923
+ vue.onMounted(() => {
1924
+ void vue.nextTick(recalculateOverflow);
1925
+ if (typeof ResizeObserver === "function" && listRef.value) {
1926
+ resizeObserver = new ResizeObserver(recalculateOverflow);
1927
+ resizeObserver.observe(listRef.value);
1928
+ } else if (typeof window !== "undefined") {
1929
+ window.addEventListener("resize", recalculateOverflow);
1930
+ }
1931
+ });
1932
+ vue.onBeforeUnmount(() => {
1933
+ resizeObserver?.disconnect();
1934
+ if (typeof window !== "undefined") window.removeEventListener("resize", recalculateOverflow);
1935
+ });
1936
+ return () => vue.h("div", {
1937
+ class: "oods-tabs",
1938
+ "data-oods-component": "Tabs",
1939
+ "data-size": props.size
1940
+ }, [
1941
+ vue.h("div", {
1942
+ ref: listRef,
1943
+ class: "oods-tab-list",
1944
+ role: "tablist",
1945
+ "aria-label": props.ariaLabel
1946
+ }, [
1947
+ ...visibleItems.value.map((item) => vue.h("button", {
1948
+ key: item.id,
1949
+ ref: (element) => {
1950
+ if (element instanceof HTMLButtonElement) tabRefs.set(item.id, element);
1951
+ else tabRefs.delete(item.id);
1952
+ },
1953
+ id: tabId(item.id),
1954
+ class: "oods-tab",
1955
+ type: "button",
1956
+ role: "tab",
1957
+ disabled: itemDisabled(item),
1958
+ tabindex: item.id === activeId.value ? 0 : -1,
1959
+ "aria-selected": item.id === activeId.value,
1960
+ "aria-controls": panelId(item.id),
1961
+ "data-tab-id": item.id,
1962
+ onClick: () => selectTab(item.id),
1963
+ onKeydown: (event) => onTabKeydown(event, item.id)
1964
+ }, slots.itemLabel?.({ item, selected: item.id === activeId.value }) ?? [item.label])),
1965
+ overflowItems.value.length > 0 ? vue.h("div", { class: "oods-tabs-overflow" }, [
1966
+ vue.h("button", {
1967
+ type: "button",
1968
+ class: "oods-tabs-overflow-trigger",
1969
+ "aria-label": `${props.overflowLabel} tabs`,
1970
+ "aria-haspopup": "menu",
1971
+ "aria-expanded": overflowOpen.value,
1972
+ onClick: () => {
1973
+ overflowOpen.value = !overflowOpen.value;
1974
+ }
1975
+ }, props.overflowLabel),
1976
+ overflowOpen.value ? vue.h("div", { class: "oods-tabs-overflow-menu", role: "menu" }, overflowItems.value.map((item) => vue.h("button", {
1977
+ key: item.id,
1978
+ type: "button",
1979
+ role: "menuitem",
1980
+ disabled: itemDisabled(item),
1981
+ "aria-current": item.id === activeId.value ? "page" : void 0,
1982
+ onClick: () => {
1983
+ selectTab(item.id);
1984
+ overflowOpen.value = false;
1985
+ }
1986
+ }, [item.label]))) : null
1987
+ ]) : null
1988
+ ]),
1989
+ ...props.items.map((item) => vue.h("div", {
1990
+ key: item.id,
1991
+ id: panelId(item.id),
1992
+ class: "oods-tab-panel",
1993
+ role: "tabpanel",
1994
+ tabindex: 0,
1995
+ hidden: item.id !== activeId.value,
1996
+ "aria-labelledby": tabId(item.id)
1997
+ }, slots.panel?.({ item, selected: item.id === activeId.value }) ?? [item.panel]))
1998
+ ]);
1999
+ }
2000
+ });
2001
+
2002
+ // src/ported-core.ts
2003
+ var STATUS_METADATA = {
2004
+ subscription: {
2005
+ future: {
2006
+ description: "Subscription is scheduled but not yet active.",
2007
+ tone: "info",
2008
+ icon: "\u23F2"
2009
+ },
2010
+ trialing: {
2011
+ description: "Subscription is in a trial period before billing begins.",
2012
+ tone: "accent",
2013
+ icon: "\u2605"
2014
+ },
2015
+ active: {
2016
+ description: "Subscription is paid and service is provisioned.",
2017
+ tone: "success",
2018
+ icon: "\u2714\uFE0E"
2019
+ },
2020
+ paused: {
2021
+ description: "Billing paused temporarily by finance or customer request.",
2022
+ tone: "neutral",
2023
+ icon: "\u23F8"
2024
+ },
2025
+ pending_cancellation: {
2026
+ description: "Subscription will cancel at period end but remains active now.",
2027
+ tone: "info",
2028
+ icon: "\u2026"
2029
+ },
2030
+ past_due: {
2031
+ description: "Renewal payment failed; smart retries ongoing and access retained during the grace window.",
2032
+ tone: "critical",
2033
+ icon: "\u26A0\uFE0E"
2034
+ },
2035
+ unpaid: {
2036
+ description: "Payment retries exhausted; service access revoked.",
2037
+ tone: "critical",
2038
+ icon: "\u2A2F"
2039
+ },
2040
+ terminated: {
2041
+ description: "Subscription ended; service access revoked and billing stopped.",
2042
+ tone: "neutral",
2043
+ icon: "\u2205"
2044
+ }
2045
+ },
2046
+ invoice: {
2047
+ draft: {
2048
+ description: "Invoice is editable and not yet finalized.",
2049
+ tone: "neutral",
2050
+ icon: "\u270E"
2051
+ },
2052
+ posted: {
2053
+ description: "Invoice finalized with terms before collection.",
2054
+ tone: "info",
2055
+ icon: "\u2026"
2056
+ },
2057
+ paid: {
2058
+ description: "Invoice collected successfully in full.",
2059
+ tone: "success",
2060
+ icon: "\u2714\uFE0E"
2061
+ },
2062
+ past_due: {
2063
+ description: "Invoice is unpaid past its due date.",
2064
+ tone: "critical",
2065
+ icon: "\u2A2F"
2066
+ },
2067
+ void: {
2068
+ description: "Invoice canceled and no longer payable.",
2069
+ tone: "neutral",
2070
+ icon: "\u2205"
2071
+ }
2072
+ }
2073
+ };
2074
+ var STATUS_ALIASES = {
2075
+ subscription: { delinquent: "unpaid" }
2076
+ };
2077
+ function isRecord2(value) {
2078
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2079
+ }
2080
+ function firstText2(record, keys) {
2081
+ for (const key of keys) {
2082
+ const value = record[key];
2083
+ if (value === void 0 || value === null || value === "") continue;
2084
+ if (typeof value === "string") return value;
2085
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
2086
+ }
2087
+ return void 0;
2088
+ }
2089
+ function statusLabel2(status) {
2090
+ return status.split(/[_-]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
2091
+ }
2092
+ function getPortedStatusPresentation(domain, status) {
2093
+ const normalizedDomain = domain.toLowerCase();
2094
+ const normalizedStatus = status.toLowerCase();
2095
+ const alias = STATUS_ALIASES[normalizedDomain]?.[normalizedStatus];
2096
+ const metadata = STATUS_METADATA[normalizedDomain]?.[alias ?? normalizedStatus];
2097
+ if (!metadata) {
2098
+ return {
2099
+ label: statusLabel2(status || "Unknown"),
2100
+ description: "Status not found in registry; falling back to the requested token set.",
2101
+ tone: "neutral"
2102
+ };
2103
+ }
2104
+ return { ...metadata, label: statusLabel2(status) };
2105
+ }
2106
+ function formatPrice(amountCents, amount, currency) {
2107
+ const normalizedAmount = amount;
2108
+ if (normalizedAmount === void 0 || !Number.isFinite(normalizedAmount)) return void 0;
2109
+ const formatted = Number.isInteger(normalizedAmount) ? String(normalizedAmount) : normalizedAmount.toFixed(2);
2110
+ return currency ? `${currency.toUpperCase()} ${formatted}` : formatted;
2111
+ }
2112
+ function normalizeTimelineEvents(raw) {
2113
+ if (!Array.isArray(raw)) return [];
2114
+ const events = [];
2115
+ for (const entry of raw) {
2116
+ if (entry === void 0 || entry === null) continue;
2117
+ if (!isRecord2(entry)) {
2118
+ events.push({ label: String(entry) });
2119
+ continue;
2120
+ }
2121
+ const from = firstText2(entry, ["from"]);
2122
+ const to = firstText2(entry, ["to"]);
2123
+ events.push({
2124
+ label: firstText2(entry, ["label", "title"]) ?? (firstText2(entry, ["event", "status", "state"]) ? statusLabel2(firstText2(entry, ["event", "status", "state"])) : void 0) ?? firstText2(entry, ["text", "name"]) ?? (to ? from ? `${statusLabel2(from)} \u2192 ${statusLabel2(to)}` : statusLabel2(to) : void 0) ?? "Event",
2125
+ timestamp: firstText2(entry, ["timestamp", "datetime", "time", "at", "createdAt", "updatedAt"]),
2126
+ detail: firstText2(entry, ["detail", "description", "message"]) ?? (to ? from ? `${statusLabel2(from)} \u2192 ${statusLabel2(to)}` : statusLabel2(to) : void 0),
2127
+ actorId: firstText2(entry, ["actorId", "actor_id", "actor"]),
2128
+ reason: firstText2(entry, ["reason"])
2129
+ });
2130
+ }
2131
+ return events;
2132
+ }
2133
+ function integerAtLeast(value, minimum, fallback) {
2134
+ return Number.isFinite(value) ? Math.max(minimum, Math.floor(value)) : fallback;
2135
+ }
2136
+ function buildPaginationItems(options) {
2137
+ const count = integerAtLeast(options.count, 0, 0);
2138
+ const rawPage = integerAtLeast(options.page, 1, 1);
2139
+ const page = count > 0 ? Math.min(rawPage, count) : 1;
2140
+ const items = [{
2141
+ type: "previous",
2142
+ page: page > 1 ? page - 1 : page,
2143
+ selected: false,
2144
+ disabled: count === 0 || page <= 1
2145
+ }];
2146
+ if (count === 0) {
2147
+ items.push({ type: "next", page: 1, selected: false, disabled: true });
2148
+ return { page, count, items };
2149
+ }
2150
+ const siblingCount = integerAtLeast(options.siblingCount ?? 1, 0, 1);
2151
+ const boundaryCount = integerAtLeast(options.boundaryCount ?? 1, 1, 1);
2152
+ const candidates = count <= boundaryCount * 2 + siblingCount * 2 + 3 ? Array.from({ length: count }, (_, index) => index + 1) : [.../* @__PURE__ */ new Set([
2153
+ ...Array.from({ length: boundaryCount }, (_, index) => index + 1),
2154
+ ...Array.from({ length: siblingCount * 2 + 1 }, (_, index) => page - siblingCount + index),
2155
+ ...Array.from({ length: boundaryCount }, (_, index) => count - boundaryCount + index + 1)
2156
+ ])].filter((candidate) => candidate >= 1 && candidate <= count).sort((left, right) => left - right);
2157
+ let priorPage;
2158
+ let ellipsisIndex = 0;
2159
+ for (const pageNumber of candidates) {
2160
+ if (priorPage !== void 0 && pageNumber - priorPage > 1) {
2161
+ items.push({
2162
+ type: "ellipsis",
2163
+ selected: false,
2164
+ disabled: true,
2165
+ index: ellipsisIndex++
2166
+ });
2167
+ }
2168
+ items.push({
2169
+ type: "page",
2170
+ page: pageNumber,
2171
+ selected: pageNumber === page,
2172
+ disabled: false
2173
+ });
2174
+ priorPage = pageNumber;
2175
+ }
2176
+ items.push({
2177
+ type: "next",
2178
+ page: page < count ? page + 1 : page,
2179
+ selected: false,
2180
+ disabled: page >= count
2181
+ });
2182
+ return { page, count, items };
2183
+ }
2184
+ function relativeTimestampLabel(datetime, now) {
2185
+ if (now === void 0) return datetime;
2186
+ const timestamp = Date.parse(datetime);
2187
+ const reference = now instanceof Date ? now.getTime() : typeof now === "number" ? now : Date.parse(now);
2188
+ if (!Number.isFinite(timestamp) || !Number.isFinite(reference)) return datetime;
2189
+ const seconds = Math.round((timestamp - reference) / 1e3);
2190
+ const absoluteSeconds = Math.abs(seconds);
2191
+ const formatter = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
2192
+ if (absoluteSeconds < 60) return formatter.format(seconds, "second");
2193
+ const minutes = Math.round(seconds / 60);
2194
+ if (Math.abs(minutes) < 60) return formatter.format(minutes, "minute");
2195
+ const hours = Math.round(minutes / 60);
2196
+ if (Math.abs(hours) < 24) return formatter.format(hours, "hour");
2197
+ return formatter.format(Math.round(hours / 24), "day");
2198
+ }
2199
+
2200
+ // src/ported.ts
2201
+ function stringValue(value) {
2202
+ if (typeof value === "string") return value;
2203
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
2204
+ return void 0;
2205
+ }
2206
+ function firstDefined(...values) {
2207
+ return values.find((value) => value !== void 0);
2208
+ }
2209
+ function firstArray(...values) {
2210
+ return values.find((value) => Array.isArray(value)) ?? [];
2211
+ }
2212
+ function mergedClass(componentClass, value) {
2213
+ return value ? [componentClass, value] : [componentClass];
2214
+ }
2215
+ var StatusBadge = vue.defineComponent({
2216
+ name: "OodsStatusBadge",
2217
+ inheritAttrs: false,
2218
+ props: {
2219
+ content: { type: [String, Number], default: void 0 },
2220
+ status: String,
2221
+ value: String,
2222
+ label: String,
2223
+ domain: { type: String, default: "subscription" },
2224
+ tone: String,
2225
+ emphasis: { type: String, default: "subtle" },
2226
+ showIcon: { type: Boolean, default: true },
2227
+ field: String,
2228
+ statusField: String,
2229
+ domainField: String,
2230
+ readOnly: Boolean,
2231
+ compact: Boolean,
2232
+ variant: String
2233
+ },
2234
+ setup(props, { attrs, slots }) {
2235
+ return () => {
2236
+ const status = props.status ?? props.value ?? "unknown";
2237
+ const presentation = getPortedStatusPresentation(props.domain, status);
2238
+ const tone = props.tone === "lifecycle" ? presentation.tone : props.tone ?? presentation.tone;
2239
+ const ariaLabel = stringValue(attrs["aria-label"]) ?? props.label ?? `Status: ${presentation.label}`;
2240
+ return vue.h(Badge, {
2241
+ ...attrs,
2242
+ class: mergedClass("oods-status-badge", attrs.class),
2243
+ content: slots.default ? void 0 : props.content ?? presentation.label,
2244
+ status,
2245
+ domain: props.domain,
2246
+ tone,
2247
+ emphasis: props.variant === "solid" ? "solid" : props.emphasis,
2248
+ icon: props.showIcon ? presentation.icon : void 0,
2249
+ title: stringValue(attrs.title) ?? presentation.description,
2250
+ "aria-label": ariaLabel,
2251
+ "data-oods-component": "StatusBadge"
2252
+ }, slots);
2253
+ };
2254
+ }
2255
+ });
2256
+ function currencyLabel(amountCents, amount, currency, minorUnits) {
2257
+ const normalizedAmount = amountCents !== void 0 ? amountCents / componentContracts.currencyMinorUnits(currency, minorUnits) : amount;
2258
+ if (normalizedAmount === void 0 || !Number.isFinite(normalizedAmount)) return void 0;
2259
+ if (!currency) return formatPrice(void 0, normalizedAmount, void 0);
2260
+ try {
2261
+ return new Intl.NumberFormat("en-US", {
2262
+ style: "currency",
2263
+ currency: currency.toUpperCase()
2264
+ }).format(normalizedAmount);
2265
+ } catch {
2266
+ return formatPrice(void 0, normalizedAmount, currency);
2267
+ }
2268
+ }
2269
+ var PriceBadge = vue.defineComponent({
2270
+ name: "OodsPriceBadge",
2271
+ inheritAttrs: false,
2272
+ props: {
2273
+ amountCents: Number,
2274
+ unitAmountCents: Number,
2275
+ minorUnits: Number,
2276
+ amount: Number,
2277
+ unitAmount: Number,
2278
+ currency: String,
2279
+ currencyCode: String,
2280
+ label: String,
2281
+ value: [String, Number],
2282
+ emphasis: { type: String, default: "subtle" },
2283
+ field: String,
2284
+ amountField: String,
2285
+ currencyField: String,
2286
+ intervalField: String,
2287
+ minorUnitsParameter: String
2288
+ },
2289
+ setup(props, { attrs, slots }) {
2290
+ return () => {
2291
+ const currency = props.currency ?? props.currencyCode;
2292
+ const cents = props.amountCents ?? props.unitAmountCents;
2293
+ const amount = props.amount ?? props.unitAmount;
2294
+ const content = props.label ?? props.value ?? currencyLabel(cents, amount, currency, props.minorUnits) ?? "Price";
2295
+ return vue.h(Badge, {
2296
+ ...attrs,
2297
+ class: mergedClass("oods-price-badge", attrs.class),
2298
+ content,
2299
+ emphasis: props.emphasis,
2300
+ title: stringValue(attrs.title) ?? props.label,
2301
+ "data-oods-component": "PriceBadge",
2302
+ "data-price": "true",
2303
+ "data-badge-variant": "price",
2304
+ "data-currency": currency?.toUpperCase()
2305
+ }, slots);
2306
+ };
2307
+ }
2308
+ });
2309
+ var timelineProps = {
2310
+ title: String,
2311
+ source: Array,
2312
+ events: Array,
2313
+ history: Array,
2314
+ entries: Array,
2315
+ stateHistory: Array,
2316
+ auditLog: Array,
2317
+ status: String,
2318
+ allowedTransitions: Array,
2319
+ showActorId: { type: Boolean, default: true },
2320
+ showReason: { type: Boolean, default: true },
2321
+ maxVisible: Number,
2322
+ field: String,
2323
+ historyField: String,
2324
+ statesParameter: String,
2325
+ auditLogField: String,
2326
+ createdField: String,
2327
+ updatedField: String,
2328
+ eventField: String,
2329
+ eventTimestampField: String,
2330
+ eventOptionsParameter: String,
2331
+ showFromState: Boolean
2332
+ };
2333
+ function renderTimelineEvent(event, index, showActorId, showReason) {
2334
+ return vue.h("li", { key: `${event.timestamp ?? "event"}-${index}` }, [
2335
+ vue.h("article", { "data-timeline-event": "true" }, [
2336
+ vue.h("p", { "data-timeline-label": "true" }, event.label),
2337
+ event.timestamp ? vue.h("time", { "data-timeline-time": "true", datetime: event.timestamp }, componentContracts.formatDateTime(event.timestamp)) : null,
2338
+ event.detail ? vue.h("p", { "data-timeline-detail": "true" }, event.detail) : null,
2339
+ showActorId && event.actorId ? vue.h("p", { "data-timeline-actor": "true" }, `Actor: ${event.actorId}`) : null,
2340
+ showReason && event.reason ? vue.h("p", { "data-timeline-reason": "true" }, `Reason: ${event.reason}`) : null
2341
+ ])
2342
+ ]);
2343
+ }
2344
+ function timelineComponent(configuration) {
2345
+ return vue.defineComponent({
2346
+ name: `Oods${configuration.componentId}`,
2347
+ inheritAttrs: false,
2348
+ props: timelineProps,
2349
+ setup(props, { attrs, slots }) {
2350
+ return () => {
2351
+ const rawEvents = configuration.componentId === "StatusTimeline" ? firstArray(props.source, props.events, props.history, props.entries, props.stateHistory) : firstArray(props.source, props.events, props.history, props.entries, props.auditLog);
2352
+ const normalized = normalizeTimelineEvents(rawEvents);
2353
+ const events = props.maxVisible === void 0 ? normalized : normalized.slice(0, Math.max(0, Math.floor(props.maxVisible)));
2354
+ const title = props.title ?? configuration.defaultTitle;
2355
+ const slottedEvents = slots.default?.();
2356
+ return vue.h("div", {
2357
+ ...attrs,
2358
+ class: mergedClass("oods-timeline", attrs.class),
2359
+ role: "log",
2360
+ "aria-label": stringValue(attrs["aria-label"]) ?? title,
2361
+ "data-oods-component": configuration.componentId,
2362
+ "data-timeline-type": configuration.timelineType
2363
+ }, [
2364
+ vue.h("h2", { "data-timeline-title": "true" }, title),
2365
+ props.status ? vue.h("p", { "data-timeline-current": "true" }, `Current status: ${statusLabel2(props.status)}`) : null,
2366
+ props.allowedTransitions?.length ? vue.h("p", { "data-timeline-transitions": "true" }, `Allowed transitions: ${props.allowedTransitions.join(", ")}`) : null,
2367
+ vue.h("ol", { "data-timeline-events": "true" }, slottedEvents?.length ? slottedEvents : events.length ? events.map((event, index) => renderTimelineEvent(
2368
+ event,
2369
+ index,
2370
+ props.showActorId,
2371
+ props.showReason
2372
+ )) : [vue.h("li", { "data-timeline-empty": "true" }, "No events")])
2373
+ ]);
2374
+ };
2375
+ }
2376
+ });
2377
+ }
2378
+ var StatusTimeline = timelineComponent({
2379
+ componentId: "StatusTimeline",
2380
+ defaultTitle: "Status Timeline",
2381
+ timelineType: "status"
2382
+ });
2383
+ var AuditTimeline = timelineComponent({
2384
+ componentId: "AuditTimeline",
2385
+ defaultTitle: "Audit Timeline",
2386
+ timelineType: "audit"
2387
+ });
2388
+ function slotSummaryValue(slots) {
2389
+ const nodes = slots.default?.() ?? [];
2390
+ for (const node of nodes) {
2391
+ if (typeof node.children === "string") {
2392
+ const text = node.children.trim().toLowerCase();
2393
+ if (text === "true") return true;
2394
+ if (text === "false") return false;
2395
+ if (node.children.length > 0) return node.children;
2396
+ }
2397
+ }
2398
+ return void 0;
2399
+ }
2400
+ function summaryEntry(label, value, key) {
2401
+ return vue.h("div", { key, "data-summary-item": "true" }, [
2402
+ vue.h("dt", label),
2403
+ vue.h("dd", [value])
2404
+ ]);
2405
+ }
2406
+ var CancellationSummary = vue.defineComponent({
2407
+ name: "OodsCancellationSummary",
2408
+ inheritAttrs: false,
2409
+ props: {
2410
+ title: String,
2411
+ label: String,
2412
+ cancelAtPeriodEnd: { type: Boolean, default: void 0 },
2413
+ requestedAt: String,
2414
+ reason: String,
2415
+ code: String,
2416
+ field: String,
2417
+ cancelAtPeriodEndField: String,
2418
+ requestedAtField: String,
2419
+ reasonField: String,
2420
+ codeField: String
2421
+ },
2422
+ setup(props, { attrs, slots }) {
2423
+ return () => {
2424
+ const slotValue = slotSummaryValue(slots);
2425
+ const cancellationValue = props.cancelAtPeriodEnd ?? slotValue;
2426
+ const rows = [];
2427
+ if (cancellationValue !== void 0) {
2428
+ rows.push(summaryEntry(
2429
+ "Cancel at period end",
2430
+ typeof cancellationValue === "boolean" ? cancellationValue ? "Yes" : "No" : cancellationValue,
2431
+ "cancel-at-period-end"
2432
+ ));
2433
+ }
2434
+ if (props.requestedAt) {
2435
+ rows.push(summaryEntry(
2436
+ "Requested at",
2437
+ vue.h("time", { datetime: props.requestedAt }, componentContracts.formatDateTime(props.requestedAt)),
2438
+ "requested-at"
2439
+ ));
2440
+ }
2441
+ if (props.reason) rows.push(summaryEntry("Reason", props.reason, "reason"));
2442
+ if (props.code) rows.push(summaryEntry("Code", componentContracts.formatReadOnlyValue(props.code, "string", true), "code"));
2443
+ return vue.h("section", {
2444
+ ...attrs,
2445
+ class: mergedClass("oods-cancellation-summary", attrs.class),
2446
+ "data-oods-component": "CancellationSummary",
2447
+ "data-summary-type": "cancellation",
2448
+ "data-cancellation-state": props.cancelAtPeriodEnd === void 0 ? void 0 : String(props.cancelAtPeriodEnd)
2449
+ }, [
2450
+ vue.h("h2", { "data-summary-title": "true" }, props.title ?? props.label ?? "Cancellation Summary"),
2451
+ vue.h("dl", rows)
2452
+ ]);
2453
+ };
2454
+ }
2455
+ });
2456
+ function inputValue(event) {
2457
+ return event.target.value;
2458
+ }
2459
+ var SearchInput = vue.defineComponent({
2460
+ name: "OodsSearchInput",
2461
+ inheritAttrs: false,
2462
+ props: {
2463
+ id: String,
2464
+ label: String,
2465
+ modelValue: String,
2466
+ value: String,
2467
+ defaultValue: { type: String, default: "" },
2468
+ placeholder: { type: String, default: "Search\u2026" },
2469
+ clearable: { type: Boolean, default: true },
2470
+ debounceMs: { type: Number, default: 0 },
2471
+ debounce: Number,
2472
+ minQueryLength: { type: Number, default: 0 },
2473
+ disabled: Boolean,
2474
+ field: String,
2475
+ placeholderParameter: String,
2476
+ debounceParameter: String,
2477
+ minQueryLengthParameter: String,
2478
+ clearableParameter: String
2479
+ },
2480
+ emits: {
2481
+ "update:modelValue": (_value) => true,
2482
+ valueChange: (_value) => true,
2483
+ update: (_value) => true,
2484
+ input: (_value) => true,
2485
+ search: (_value) => true,
2486
+ clear: () => true
2487
+ },
2488
+ setup(props, { attrs, emit }) {
2489
+ const generatedId = vue.useId();
2490
+ const internalValue = vue.ref(props.defaultValue);
2491
+ const currentValue = vue.computed(() => props.modelValue ?? props.value ?? internalValue.value);
2492
+ let timer;
2493
+ vue.onBeforeUnmount(() => {
2494
+ if (timer !== void 0) clearTimeout(timer);
2495
+ });
2496
+ const emitSearch = (value) => {
2497
+ if (value.length < Math.max(0, props.minQueryLength) && value.length !== 0) return;
2498
+ emit("valueChange", value);
2499
+ emit("update", value);
2500
+ emit("search", value);
2501
+ };
2502
+ const scheduleSearch = (value) => {
2503
+ if (timer !== void 0) clearTimeout(timer);
2504
+ const debounceMs = Math.max(0, props.debounceMs || props.debounce || 0);
2505
+ if (debounceMs > 0) {
2506
+ timer = setTimeout(() => emitSearch(value), debounceMs);
2507
+ } else {
2508
+ emitSearch(value);
2509
+ }
2510
+ };
2511
+ const updateValue = (value) => {
2512
+ if (props.modelValue === void 0 && props.value === void 0) internalValue.value = value;
2513
+ emit("update:modelValue", value);
2514
+ emit("input", value);
2515
+ scheduleSearch(value);
2516
+ };
2517
+ const clear = () => {
2518
+ if (props.disabled || currentValue.value.length === 0) return;
2519
+ if (timer !== void 0) clearTimeout(timer);
2520
+ updateValue("");
2521
+ emit("clear");
2522
+ };
2523
+ const submit = (event) => {
2524
+ event.preventDefault();
2525
+ if (!props.disabled) {
2526
+ if (timer !== void 0) clearTimeout(timer);
2527
+ emitSearch(currentValue.value);
2528
+ }
2529
+ };
2530
+ return () => {
2531
+ const id = props.id ?? `oods-search-${generatedId.replace(/:/g, "")}`;
2532
+ const ariaLabel = stringValue(attrs["aria-label"]) ?? props.label ?? props.placeholder;
2533
+ return vue.h("form", {
2534
+ ...attrs,
2535
+ class: mergedClass("oods-search-input", attrs.class),
2536
+ role: "search",
2537
+ "aria-label": ariaLabel,
2538
+ "data-oods-component": "SearchInput",
2539
+ "data-behavioral": "search",
2540
+ onSubmit: submit
2541
+ }, [
2542
+ vue.h(Input, {
2543
+ id,
2544
+ label: props.label,
2545
+ type: "search",
2546
+ value: currentValue.value,
2547
+ placeholder: props.placeholder,
2548
+ disabled: props.disabled,
2549
+ "aria-label": props.label ? void 0 : ariaLabel,
2550
+ onInput: (value) => updateValue(value),
2551
+ onKeydown: (event) => {
2552
+ if (event.key === "Escape") clear();
2553
+ }
2554
+ }),
2555
+ props.clearable && currentValue.value ? vue.h("button", {
2556
+ type: "button",
2557
+ "data-search-clear": "true",
2558
+ "aria-label": "Clear search",
2559
+ disabled: props.disabled,
2560
+ onClick: clear
2561
+ }, "\xD7") : null
2562
+ ]);
2563
+ };
2564
+ }
2565
+ });
2566
+ var PaginationBar = vue.defineComponent({
2567
+ name: "OodsPaginationBar",
2568
+ inheritAttrs: false,
2569
+ props: {
2570
+ page: { type: Number, default: 1 },
2571
+ pageSize: { type: Number, default: 25 },
2572
+ totalItems: { type: Number, default: 0 },
2573
+ totalPages: Number,
2574
+ pageSizeOptions: { type: Array, default: () => [10, 25, 50, 100] },
2575
+ showPageSizeSelector: Boolean,
2576
+ showGotoPage: Boolean,
2577
+ showItemRange: { type: Boolean, default: true },
2578
+ pageField: String,
2579
+ pageSizeField: String,
2580
+ totalItemsField: String,
2581
+ totalPagesField: String,
2582
+ pageSizeOptionsParameter: String,
2583
+ showPageSizeSelectorParameter: String,
2584
+ showGotoPageParameter: String,
2585
+ showItemRangeParameter: String
2586
+ },
2587
+ emits: {
2588
+ "update:page": (_page) => true,
2589
+ pageChange: (_page) => true,
2590
+ change: (_page) => true,
2591
+ update: (_page) => true,
2592
+ pageSizeChange: (_pageSize) => true
2593
+ },
2594
+ setup(props, { attrs, emit }) {
2595
+ const generatedId = vue.useId().replace(/:/g, "");
2596
+ const gotoPage = vue.ref("");
2597
+ const count = vue.computed(() => props.totalPages ?? (props.pageSize > 0 ? Math.ceil(props.totalItems / props.pageSize) : 0));
2598
+ const pagination = vue.computed(() => buildPaginationItems({
2599
+ page: props.page,
2600
+ count: count.value
2601
+ }));
2602
+ const setPage = (page) => {
2603
+ if (pagination.value.count === 0) return;
2604
+ const nextPage = Math.max(1, Math.min(Math.floor(page), pagination.value.count));
2605
+ if (nextPage === pagination.value.page) return;
2606
+ emit("update:page", nextPage);
2607
+ emit("pageChange", nextPage);
2608
+ emit("change", nextPage);
2609
+ emit("update", nextPage);
2610
+ };
2611
+ const submitGoto = (event) => {
2612
+ event.preventDefault();
2613
+ const nextPage = Number(gotoPage.value);
2614
+ if (Number.isFinite(nextPage)) setPage(nextPage);
2615
+ };
2616
+ return () => {
2617
+ const { page, count: totalPages, items } = pagination.value;
2618
+ return vue.h("nav", {
2619
+ ...attrs,
2620
+ class: mergedClass("oods-pagination-bar", attrs.class),
2621
+ "aria-label": stringValue(attrs["aria-label"]) ?? "Pagination",
2622
+ "data-oods-component": "PaginationBar",
2623
+ "data-behavioral": "pagination"
2624
+ }, [
2625
+ props.showItemRange ? vue.h("span", { "data-pagination-count": "true" }, `${props.totalItems} ${props.totalItems === 1 ? "record" : "records"}`) : null,
2626
+ // s211-m02: on one page the range only repeats the count ("15 records Showing 1–15 of 15").
2627
+ props.showItemRange && props.totalItems > 0 && totalPages > 1 ? vue.h("span", { "data-pagination-range": "true" }, `Showing ${(page - 1) * Math.max(1, props.pageSize) + 1}\u2013${Math.min(page * Math.max(1, props.pageSize), props.totalItems)} of ${props.totalItems}`) : null,
2628
+ // s210-m01: one page needs no page controls; the count and range above already say what is shown.
2629
+ totalPages > 1 ? vue.h("ul", { class: "oods-pagination-bar__pages" }, items.map((item, index) => {
2630
+ if (item.type === "ellipsis") {
2631
+ return vue.h("li", { key: `ellipsis-${item.index ?? index}`, "aria-hidden": "true" }, "\u2026");
2632
+ }
2633
+ const label = item.type === "page" ? `Page ${item.page}` : item.type === "previous" ? "Previous page" : "Next page";
2634
+ const content = item.type === "page" ? String(item.page) : item.type === "previous" ? "\u2039" : "\u203A";
2635
+ return vue.h("li", { key: `${item.type}-${item.page}` }, [
2636
+ vue.h("button", {
2637
+ type: "button",
2638
+ "data-pagination-page": item.type === "page" ? item.page : void 0,
2639
+ "data-pagination-prev": item.type === "previous" ? "true" : void 0,
2640
+ "data-pagination-next": item.type === "next" ? "true" : void 0,
2641
+ "aria-label": label,
2642
+ "aria-current": item.selected ? "page" : void 0,
2643
+ disabled: item.disabled,
2644
+ onClick: () => item.page !== void 0 && setPage(item.page)
2645
+ }, content)
2646
+ ]);
2647
+ })) : null,
2648
+ totalPages > 1 ? vue.h("span", { "data-pagination-current": "true" }, `Page ${page} of ${Math.max(1, totalPages)}`) : null,
2649
+ props.showPageSizeSelector ? vue.h("label", { class: "oods-pagination-size" }, [
2650
+ vue.h("span", "Items per page"),
2651
+ vue.h("select", {
2652
+ value: props.pageSize,
2653
+ onChange: (event) => {
2654
+ const nextSize = Number(event.target.value);
2655
+ if (Number.isFinite(nextSize) && nextSize > 0 && nextSize !== props.pageSize) {
2656
+ emit("pageSizeChange", nextSize);
2657
+ }
2658
+ }
2659
+ }, props.pageSizeOptions.map((option) => vue.h("option", { value: option }, String(option))))
2660
+ ]) : null,
2661
+ props.showGotoPage ? vue.h("form", { class: "oods-pagination-goto", onSubmit: submitGoto }, [
2662
+ vue.h("label", { for: `oods-pagination-goto-${generatedId}` }, "Go to page"),
2663
+ vue.h("input", {
2664
+ id: `oods-pagination-goto-${generatedId}`,
2665
+ type: "number",
2666
+ min: 1,
2667
+ max: totalPages,
2668
+ value: gotoPage.value,
2669
+ onInput: (event) => {
2670
+ gotoPage.value = inputValue(event);
2671
+ }
2672
+ }),
2673
+ vue.h("button", { type: "submit" }, "Go")
2674
+ ]) : null
2675
+ ]);
2676
+ };
2677
+ }
2678
+ });
2679
+ var RelativeTimestamp = vue.defineComponent({
2680
+ name: "OodsRelativeTimestamp",
2681
+ inheritAttrs: false,
2682
+ props: {
2683
+ datetime: String,
2684
+ timestamp: String,
2685
+ value: String,
2686
+ updatedAt: String,
2687
+ createdAt: String,
2688
+ relative: String,
2689
+ label: String,
2690
+ text: String,
2691
+ timezone: String,
2692
+ hideWhenEmpty: Boolean,
2693
+ now: [String, Number, Date],
2694
+ field: String,
2695
+ fallbackField: String,
2696
+ timezoneParameter: String
2697
+ },
2698
+ setup(props, { attrs }) {
2699
+ return () => {
2700
+ const datetime = firstDefined(
2701
+ props.datetime,
2702
+ props.timestamp,
2703
+ props.value,
2704
+ props.updatedAt,
2705
+ props.createdAt
2706
+ ) ?? "";
2707
+ if (props.hideWhenEmpty && (!datetime || Number.isNaN(Date.parse(datetime)))) return null;
2708
+ const content = firstDefined(
2709
+ props.relative,
2710
+ props.label,
2711
+ props.text,
2712
+ props.now === void 0 ? componentContracts.formatDateTime(datetime, { timeZone: props.timezone }) : relativeTimestampLabel(datetime, props.now)
2713
+ ) ?? "";
2714
+ return vue.h("time", {
2715
+ ...attrs,
2716
+ class: mergedClass("oods-relative-timestamp", attrs.class),
2717
+ datetime: datetime || void 0,
2718
+ title: stringValue(attrs.title) ?? componentContracts.formatDateTime(datetime, { timeZone: props.timezone }),
2719
+ "data-oods-component": "RelativeTimestamp"
2720
+ }, content);
2721
+ };
2722
+ }
2723
+ });
2724
+ var amountProps = { id: String, amount: Number, currency: { type: String, default: "usd" }, minorUnits: { type: Number, default: componentContracts.BILLING_MINOR_UNITS } };
2725
+ var BillingSummaryBadge = vue.defineComponent({
2726
+ name: "BillingSummaryBadge",
2727
+ props: { ...amountProps, interval: String, showInterval: { type: Boolean, default: true } },
2728
+ setup: (props) => () => vue.h("span", { id: props.id, class: "oods-billing-summary", "data-oods-component": "BillingSummaryBadge" }, props.showInterval ? componentContracts.billingSummary(props.amount, props.currency, props.minorUnits, props.interval) : componentContracts.formatBillingAmount(props.amount, props.currency, props.minorUnits))
2729
+ });
2730
+ var BillingAmountInput = vue.defineComponent({
2731
+ name: "BillingAmountInput",
2732
+ props: { ...amountProps, label: { type: String, default: "Billing amount" }, name: String, disabled: Boolean, help: String },
2733
+ emits: { change: (value) => value === void 0 || Number.isSafeInteger(value) },
2734
+ setup(props, { emit }) {
2735
+ const generatedId = vue.useId();
2736
+ const text = vue.ref(componentContracts.billingAmountText(props.amount, props.minorUnits));
2737
+ const error = vue.ref(componentContracts.billingAmountMessage(props.amount, props.minorUnits));
2738
+ vue.watch(() => [props.amount, props.minorUnits], () => {
2739
+ text.value = componentContracts.billingAmountText(props.amount, props.minorUnits);
2740
+ error.value = componentContracts.billingAmountMessage(props.amount, props.minorUnits);
2741
+ });
2742
+ return () => {
2743
+ const id = props.id ?? `billing-amount-${generatedId}`;
2744
+ return vue.h("div", { class: "oods-billing-field", "data-oods-component": "BillingAmountInput", "data-state": error.value ? "invalid" : "editing" }, [
2745
+ vue.h("label", { for: id }, props.label),
2746
+ vue.h("span", { id: `${id}-currency` }, props.currency.toUpperCase()),
2747
+ vue.h("input", {
2748
+ id,
2749
+ name: props.name,
2750
+ type: "text",
2751
+ inputmode: "decimal",
2752
+ value: text.value,
2753
+ disabled: props.disabled,
2754
+ "data-billing-minor-units": props.minorUnits,
2755
+ "aria-invalid": error.value ? true : void 0,
2756
+ "aria-describedby": [`${id}-currency`, error.value && `${id}-error`].filter(Boolean).join(" "),
2757
+ onInput(event) {
2758
+ const target = event.currentTarget;
2759
+ const result = componentContracts.parseBillingAmount(target.value, props.minorUnits);
2760
+ target.setCustomValidity(result.valid ? "" : result.message);
2761
+ text.value = target.value;
2762
+ error.value = result.valid ? void 0 : result.message;
2763
+ if (result.valid) emit("change", result.value);
2764
+ }
2765
+ }),
2766
+ props.help ? vue.h("p", { class: "oods-field-help" }, props.help) : null,
2767
+ error.value ? vue.h("p", { id: `${id}-error`, role: "alert" }, error.value) : null
2768
+ ]);
2769
+ };
2770
+ }
2771
+ });
2772
+ var BillingIntervalSelector = vue.defineComponent({
2773
+ name: "BillingIntervalSelector",
2774
+ props: { id: String, interval: String, intervals: { type: Array, default: () => [...componentContracts.BILLING_INTERVALS] }, label: { type: String, default: "Billing interval" }, name: String, disabled: Boolean, help: String },
2775
+ emits: { change: (value) => typeof value === "string" },
2776
+ setup(props, { emit }) {
2777
+ const generatedId = vue.useId();
2778
+ const value = vue.ref(props.interval ?? "");
2779
+ vue.watch(() => props.interval, () => {
2780
+ value.value = props.interval ?? "";
2781
+ });
2782
+ return () => {
2783
+ const id = props.id ?? `billing-interval-${generatedId}`;
2784
+ const error = componentContracts.billingIntervalMessage(value.value || void 0, props.intervals);
2785
+ return vue.h("div", { class: "oods-billing-field", "data-oods-component": "BillingIntervalSelector", "data-state": error ? "invalid" : "editing" }, [
2786
+ vue.h("label", { for: id }, props.label),
2787
+ vue.h("select", {
2788
+ id,
2789
+ name: props.name,
2790
+ value: value.value,
2791
+ disabled: props.disabled,
2792
+ "aria-invalid": error ? true : void 0,
2793
+ "aria-describedby": error ? `${id}-error` : void 0,
2794
+ onChange(event) {
2795
+ const next = event.currentTarget.value;
2796
+ if (props.intervals.includes(next)) {
2797
+ value.value = next;
2798
+ emit("change", next);
2799
+ }
2800
+ }
2801
+ }, [
2802
+ !props.intervals.includes(value.value) ? vue.h("option", { value: value.value, disabled: true, selected: true }, value.value || "Choose interval") : null,
2803
+ ...props.intervals.map((option) => vue.h("option", { value: option, selected: option === value.value }, componentContracts.formatReadOnlyValue(option, "string", true)))
2804
+ ]),
2805
+ props.help ? vue.h("p", { class: "oods-field-help" }, props.help) : null,
2806
+ error ? vue.h("p", { id: `${id}-error`, role: "alert" }, error) : null
2807
+ ]);
2808
+ };
2809
+ }
2810
+ });
2811
+ var CycleProgressCard = vue.defineComponent({
2812
+ name: "CycleProgressCard",
2813
+ props: { id: String, title: { type: String, default: "Billing cycle" }, progress: Number, periodStart: String, periodEnd: String, interval: String, now: String },
2814
+ setup: (props) => () => {
2815
+ const cycle = componentContracts.billingCycle(props);
2816
+ return vue.h("section", { id: props.id, class: "oods-billing-cycle", "data-oods-component": "CycleProgressCard", "aria-label": props.title }, [
2817
+ vue.h("h2", props.title),
2818
+ vue.h("p", cycle.announcement),
2819
+ cycle.percent !== void 0 ? vue.h("progress", { max: 100, value: cycle.percent, "aria-label": cycle.announcement }) : null,
2820
+ props.interval ? vue.h("p", { class: "oods-billing-muted" }, props.interval) : null
2821
+ ]);
2822
+ }
2823
+ });
2824
+ var paymentProps = { id: String, title: String, lastPayment: String, nextPayment: String, paymentStatus: String, amount: Number, currency: String, minorUnits: Number };
2825
+ function renderBillingTimeline(props, component, includeMethod) {
2826
+ const heading = props.title ?? (includeMethod ? "Payments" : "Payment events");
2827
+ return vue.h("section", { id: props.id, class: "oods-payment-timeline", "data-oods-component": component, role: "log", "aria-label": heading }, [
2828
+ vue.h("h2", heading),
2829
+ vue.h("p", componentContracts.billingPaymentSummary(props)),
2830
+ includeMethod ? vue.h("p", { class: "oods-billing-muted" }, `Payment method: ${props.paymentMethod ?? "Not provided"}`) : null,
2831
+ vue.h("ol", componentContracts.billingPaymentRows(props).map((row) => vue.h("li", { key: row.kind, "data-payment-kind": row.kind }, [vue.h("strong", row.label), row.at ? vue.h("time", { datetime: row.at }, row.text) : vue.h("span", row.text)])))
2832
+ ]);
2833
+ }
2834
+ var PaymentTimeline = vue.defineComponent({ name: "PaymentTimeline", props: { ...paymentProps, paymentMethod: String }, setup: (props) => () => renderBillingTimeline(props, "PaymentTimeline", true) });
2835
+ var PaymentEventTimeline = vue.defineComponent({ name: "PaymentEventTimeline", props: { ...paymentProps, event: Object }, setup: (props) => () => props.event ? vue.h("section", { id: props.id, "data-oods-component": "PaymentEventTimeline", "aria-label": props.event.title ?? "Payment event" }, [vue.h("strong", props.event.title), vue.h("time", { datetime: props.event.at }, componentContracts.formatDateTime(props.event.at)), vue.h("p", props.event.description)]) : renderBillingTimeline(props, "PaymentEventTimeline", false) });
2836
+ var BillingCardMeta = vue.defineComponent({
2837
+ name: "BillingCardMeta",
2838
+ props: { id: String, amount: Number, currency: String, minorUnits: Number, interval: String },
2839
+ setup: (props) => () => vue.h("span", { id: props.id, class: "oods-billing-card-meta", "data-oods-component": "BillingCardMeta" }, componentContracts.billingSummary(props.amount, props.currency, props.minorUnits, props.interval))
2840
+ });
2841
+ var ArchivedRowOverlay = vue.defineComponent({
2842
+ name: "ArchivedRowOverlay",
2843
+ props: { id: String, isArchived: Boolean, showBadge: { type: Boolean, default: true }, separateTab: { type: Boolean, default: true }, tabLabel: { type: String, default: "Archived" }, label: String },
2844
+ setup: (props, { slots }) => () => vue.h("span", {
2845
+ id: props.id,
2846
+ class: "oods-archived-row",
2847
+ "data-oods-component": "ArchivedRowOverlay",
2848
+ "data-archived": props.isArchived ? "true" : void 0,
2849
+ "data-archive-tab": props.separateTab ? props.tabLabel : void 0,
2850
+ role: props.isArchived ? "group" : void 0,
2851
+ "aria-hidden": props.isArchived ? false : void 0,
2852
+ "aria-label": props.isArchived ? `${props.tabLabel}${props.label ? `: ${props.label}` : ""}` : void 0
2853
+ }, [slots.default?.(), props.isArchived && props.showBadge ? vue.h("span", { class: "oods-archive-badge" }, props.tabLabel) : null])
2854
+ });
2855
+ var AuditSummaryCard = vue.defineComponent({
2856
+ name: "AuditSummaryCard",
2857
+ props: { id: String, title: { type: String, default: "Audit summary" }, auditLog: Array, lastN: { type: Number, default: 5 }, showTransitionCount: { type: Boolean, default: true }, showLastTransitionTime: { type: Boolean, default: true }, showLastActor: { type: Boolean, default: true } },
2858
+ setup: (props) => () => {
2859
+ const summary2 = componentContracts.auditSummary(props);
2860
+ return vue.h("section", { id: props.id, class: "oods-audit-summary", "data-oods-component": "AuditSummaryCard", "aria-label": props.title }, [vue.h("h2", props.title), vue.h("dl", [
2861
+ ...props.showTransitionCount ? [vue.h("dt", "Transitions"), vue.h("dd", summary2.count)] : [],
2862
+ ...props.showLastActor ? [vue.h("dt", "Last actor"), vue.h("dd", summary2.actor)] : [],
2863
+ ...props.showLastTransitionTime ? [vue.h("dt", "Last transition"), vue.h("dd", summary2.at ? vue.h("time", { datetime: summary2.at }, summary2.timestamp) : summary2.timestamp)] : []
2864
+ ]), summary2.recent.length ? vue.h("ol", { "aria-label": "Recent transitions" }, summary2.recent.map((entry, index) => vue.h("li", { key: index }, String(entry.to_state ?? "Transition")))) : null]);
2865
+ }
2866
+ });
2867
+ var SortIndicator = vue.defineComponent({
2868
+ name: "SortIndicator",
2869
+ inheritAttrs: false,
2870
+ props: { id: String, label: { type: String, default: "Sort" }, sortField: String, sortDirection: String, sortActive: { type: Boolean, default: false }, triStateSort: { type: Boolean, default: true }, sortableFields: Array, defaultSortField: String, defaultSortDirection: String },
2871
+ emits: { change: (_state) => true },
2872
+ setup(props, { emit, attrs }) {
2873
+ const state = vue.ref(componentContracts.initialSort(props));
2874
+ vue.watch(() => [props.sortField, props.sortDirection, props.sortActive, props.defaultSortField, props.defaultSortDirection], () => {
2875
+ state.value = componentContracts.initialSort(props);
2876
+ });
2877
+ return () => vue.h("table", { ...attrs, "data-oods-action": void 0, id: props.id, class: "oods-sort-indicator", "data-oods-component": "SortIndicator", "aria-label": props.label }, [vue.h("thead", [vue.h("tr", [vue.h("th", { scope: "col", "aria-sort": componentContracts.ariaSort(state.value) }, [
2878
+ vue.h("button", { type: "button", "data-oods-action": attrs["data-oods-action"], "aria-label": `${props.label} ${state.value.field}`, onClick: () => {
2879
+ const next = componentContracts.nextSort(state.value, props.triStateSort);
2880
+ state.value = next;
2881
+ emit("change", next);
2882
+ } }, `${state.value.field}: ${componentContracts.ariaSort(state.value)}`)
2883
+ ])])])]);
2884
+ }
2885
+ });
2886
+ var TimelineEntryLabel = vue.defineComponent({
2887
+ name: "TimelineEntryLabel",
2888
+ props: { label: String, text: String, value: String, maxLength: [Number, String], compact: { type: Boolean, default: true } },
2889
+ setup: (props, { slots }) => () => vue.h(InlineLabel, { label: props.label, text: props.text, value: props.value, maxLength: props.maxLength ?? (props.compact ? 40 : void 0), "data-oods-component": "TimelineEntryLabel", "data-timeline-label": "true", "data-compact": props.compact }, slots)
2890
+ });
2891
+ var array = Array;
2892
+ function eventRecipe(id, kind, title) {
2893
+ return vue.defineComponent({ name: id, props: { id: String, title: { type: String, default: title }, archivedAt: String, restoredAt: String, archivedBy: String, reason: String, timestamp: String, code: String, history: array, status: String, showActor: { type: Boolean, default: true }, showReason: { type: Boolean, default: true } }, setup: (props) => () => {
2894
+ const rows = componentContracts.traitEventRows(kind, props);
2895
+ return vue.h("article", { id: props.id, class: "oods-trait-recipe", "data-oods-component": id, "aria-label": props.title }, [vue.h("h2", props.title), rows.length ? vue.h("ol", rows.map((row, index) => vue.h("li", { key: index }, [vue.h("strong", row.title), vue.h("p", row.at ? vue.h("time", { datetime: row.at }, row.time) : row.time), row.actor && vue.h("p", `Actor: ${row.actor}`), row.reason && vue.h("p", `Reason: ${row.reason}`), row.code && vue.h("p", `Code: ${row.code}`)]))) : vue.h("p", "No events recorded.")]);
2896
+ } });
2897
+ }
2898
+ var ArchiveEvent = eventRecipe("ArchiveEvent", "archive", "Archive events");
2899
+ var CancellationEvent = eventRecipe("CancellationEvent", "cancellation", "Cancellation event");
2900
+ var StateTransitionEvent = eventRecipe("StateTransitionEvent", "transition", "State transitions");
2901
+ var CommunicationDetailPanel = vue.defineComponent({ name: "CommunicationDetailPanel", props: { id: String, title: { type: String, default: "Communication" }, channels: array, templates: array, policies: array, conversations: array }, setup: (props) => () => vue.h("section", { id: props.id, class: "oods-trait-recipe", "data-oods-component": "CommunicationDetailPanel", "aria-label": props.title }, [vue.h("h2", props.title), vue.h("div", { role: "status", "aria-live": "polite" }, `${props.conversations?.length ?? 0} conversations`), vue.h("dl", [["Channels", props.channels], ["Templates", props.templates], ["Delivery policies", props.policies], ["Conversations", props.conversations]].map(([label, rows]) => vue.h("div", { key: label }, [vue.h("dt", label), vue.h("dd", rows?.length ? vue.h("ul", componentContracts.recipeItemLabels(rows).map((text, index) => vue.h("li", { key: index }, text))) : "None recorded")])))]) });
2902
+ var ColorStatePicker = vue.defineComponent({ name: "ColorStatePicker", props: { id: String, title: { type: String, default: "Color state" }, colorStates: { type: Array, default: () => [...componentContracts.DEFAULT_COLOR_STATES] }, value: String, disabled: Boolean }, emits: { change: (value) => typeof value === "string" }, setup(props, { emit }) {
2903
+ const generatedId = vue.useId();
2904
+ const selected = vue.ref(props.value ?? props.colorStates[0] ?? "");
2905
+ vue.watch(() => [props.value, props.colorStates], () => {
2906
+ selected.value = props.value ?? props.colorStates[0] ?? "";
2907
+ });
2908
+ return () => vue.h("fieldset", { id: props.id, class: "oods-trait-recipe", "data-oods-component": "ColorStatePicker" }, [vue.h("legend", props.title), vue.h(Select, { id: `${props.id ?? generatedId}-state`, label: "Color state", name: "color_state", modelValue: selected.value, disabled: props.disabled, options: [...!props.colorStates.includes(selected.value) ? [{ value: selected.value, label: selected.value || "Choose state", disabled: true }] : [], ...props.colorStates.map((value) => ({ value, label: componentContracts.formatReadOnlyValue(value, "string", true) }))], "onUpdate:modelValue": (next) => {
2909
+ selected.value = next;
2910
+ emit("change", next);
2911
+ } })]);
2912
+ } });
2913
+ var StatusColorLegend = vue.defineComponent({ name: "StatusColorLegend", props: { id: String, title: { type: String, default: "Status colors" }, colorStates: { type: Array, default: () => [...componentContracts.DEFAULT_COLOR_STATES] }, value: String, showTokenReferences: Boolean }, setup: (props) => () => vue.h("section", { id: props.id, class: "oods-trait-recipe", "data-oods-component": "StatusColorLegend", "aria-label": props.title }, [vue.h("h2", props.title), vue.h("dl", props.colorStates.map((state) => vue.h("div", { key: state }, [vue.h("dt", [vue.h(ColorSwatch, { label: state, color: `var(--sys-status-${state}-surface)` }), state === props.value ? " (current)" : ""]), vue.h("dd", props.showTokenReferences ? `--sys-status-${state}-surface` : state)])))]) });
2914
+ var GeoFieldMappingForm = vue.defineComponent({ name: "GeoFieldMappingForm", props: { id: String, title: { type: String, default: "Geographic fields" }, embedded: Boolean, disabled: Boolean, latitude: String, longitude: String, identifier: String, autoDetect: { type: Boolean, default: true } }, emits: { change: (_value) => true }, setup(props, { emit }) {
2915
+ const generatedId = vue.useId();
2916
+ const value = vue.ref(componentContracts.geoFieldMapping(props));
2917
+ vue.watch(() => [props.latitude, props.longitude, props.identifier, props.autoDetect], () => {
2918
+ value.value = componentContracts.geoFieldMapping(props);
2919
+ });
2920
+ const update = (patch) => {
2921
+ value.value = { ...value.value, ...patch };
2922
+ emit("change", value.value);
2923
+ };
2924
+ return () => vue.h(props.embedded ? "fieldset" : "form", { id: props.id, class: "oods-trait-recipe", "data-oods-component": "GeoFieldMappingForm", "aria-label": props.title, onSubmit: (event) => event.preventDefault() }, [vue.h(props.embedded ? "legend" : "h2", props.title), ...["latitude", "longitude", "identifier"].map((field) => vue.h(Input, { key: field, id: `${props.id ?? generatedId}-${field}`, name: `geo_${field}_field`, label: `${field[0].toUpperCase()}${field.slice(1)} field`, modelValue: value.value[field], disabled: props.disabled, "onUpdate:modelValue": (next) => update({ [field]: next }) })), vue.h(Checkbox, { id: `${props.id ?? generatedId}-auto`, name: "geo_auto_detect_enabled", label: "Auto detect fields", modelValue: value.value.autoDetect, disabled: props.disabled, "onUpdate:modelValue": (autoDetect) => update({ autoDetect }) })]);
2925
+ } });
2926
+ var GeoResolutionBadge = vue.defineComponent({ name: "GeoResolutionBadge", props: { id: String, resolution: String }, setup: (props) => () => vue.h("span", { id: props.id, class: "oods-geo-resolution", "data-oods-component": "GeoResolutionBadge" }, `Resolution: ${props.resolution || "Not determined"}`) });
2927
+ var GeocodablePreview = vue.defineComponent({ name: "GeocodablePreview", props: { id: String, title: { type: String, default: "Geographic data" }, resolution: String, requiresLookup: { type: Boolean, default: void 0 }, detectedFields: array }, setup: (props) => () => vue.h("section", { id: props.id, class: "oods-trait-recipe", "data-oods-component": "GeocodablePreview", "aria-label": props.title }, [vue.h("h2", props.title), vue.h("dl", [vue.h("dt", "Resolution"), vue.h("dd", props.resolution || "Not determined"), vue.h("dt", "Requires lookup"), vue.h("dd", props.requiresLookup === void 0 ? "Not determined" : props.requiresLookup ? "Yes" : "No"), vue.h("dt", "Detected fields"), vue.h("dd", componentContracts.recipeItemLabels(props.detectedFields).join(", ") || "None detected")])]) });
2928
+ function controls(id) {
2929
+ return vue.defineComponent({ name: id, props: { id: String, title: String, value: Object, channel: { type: String, default: "x" }, disabled: Boolean }, emits: { change: (_value) => true }, setup(props, { emit }) {
2930
+ const generatedId = vue.useId();
2931
+ const current = vue.ref(props.value ?? {});
2932
+ const drafts = vue.ref({});
2933
+ const errors = vue.ref({});
2934
+ vue.watch(() => props.value, (value) => {
2935
+ current.value = value ?? {};
2936
+ drafts.value = {};
2937
+ errors.value = {};
2938
+ }, { deep: true });
2939
+ const update = (field, text) => {
2940
+ const result = componentContracts.editVizIntent(current.value, field, text);
2941
+ drafts.value = { ...drafts.value, [field.key]: text };
2942
+ errors.value = { ...errors.value, [field.key]: result.error };
2943
+ if (result.value) {
2944
+ current.value = result.value;
2945
+ emit("change", result.value);
2946
+ }
2947
+ };
2948
+ return () => vue.h("fieldset", { id: props.id, class: "oods-viz-controls oods-trait-recipe", "data-oods-component": id, disabled: props.disabled }, [vue.h("legend", props.title ?? componentContracts.VIZ_CONTROL_TITLES[id]), ...componentContracts.vizControlFields(id, props.channel).map((field) => {
2949
+ const text = drafts.value[field.key] ?? componentContracts.vizFieldValue(current.value, field.key);
2950
+ const common = { key: field.key, id: `${props.id ?? generatedId}-${field.key.replace(".", "-")}`, name: field.key, label: field.label, modelValue: text, validation: errors.value[field.key] ? { state: "error", message: errors.value[field.key] } : void 0, "onUpdate:modelValue": (value) => update(field, value) };
2951
+ return field.type === "select" ? vue.h(Select, { ...common, options: [...!field.options?.includes(text) ? [{ value: text, label: text || "Choose option", disabled: true }] : [], ...field.options.map((value) => ({ value, label: value }))] }) : vue.h(Input, { ...common, type: field.type === "number" ? "number" : "text", min: field.min === void 0 ? void 0 : String(field.min), max: field.max === void 0 ? void 0 : String(field.max), step: field.step === void 0 ? void 0 : String(field.step) });
2952
+ }), ...id === "VizColorControls" || id === "VizColorLegendConfig" ? [vue.h("div", { class: "oods-viz-swatches" }, current.value.encodings?.color?.range?.map((color, index) => vue.h(ColorSwatch, { key: index, color, label: `Category color ${index + 1}: ${color}` })))] : [], ...id === "VizShapeLegend" ? [vue.h(Text, { content: `Shape field: ${current.value.encodings?.shape?.field || "Not bound"}` })] : []]);
2953
+ } });
2954
+ }
2955
+ function summary(id) {
2956
+ return vue.defineComponent({ name: id, props: { id: String, title: String, value: Object, channel: { type: String, default: "x" } }, setup(props) {
2957
+ return () => {
2958
+ const rows = componentContracts.vizSummaryRows(id, props.value ?? {}, props.channel);
2959
+ if (id === "VizEncodingBadge" || id === "VizRoleBadge") return vue.h("span", { id: props.id, "data-oods-component": id }, [vue.h(Badge, { content: rows.map(([label, text]) => `${label}: ${text}`).join(" \xB7 ") })]);
2960
+ const title = props.title ?? id.replace(/^Viz/, "").replace(/Summary$/, "") + " summary";
2961
+ return vue.h("section", { id: props.id, class: "oods-trait-recipe", "data-oods-component": id, "aria-label": title }, [vue.h("h2", title), vue.h("dl", rows.map(([label, text]) => vue.h("div", { key: label }, [vue.h("dt", label), vue.h("dd", [vue.h(Text, { content: text })])])))]);
2962
+ };
2963
+ } });
2964
+ }
2965
+ function preview(id, type) {
2966
+ return vue.defineComponent({ name: id, props: { svg: String, title: String, description: String, width: Number, height: Number }, setup(props, { slots }) {
2967
+ return () => vue.h(VizAreaPreview, { ...props, title: props.title ?? `${type[0].toUpperCase()}${type.slice(1)} chart`, "data-oods-component": id, "data-viz-preview-type": type }, { default: slots.default ?? (() => vue.h(Text, { content: "No rendered chart supplied" })) });
2968
+ } });
2969
+ }
2970
+ var VizAreaControls = controls("VizAreaControls");
2971
+ var VizAxisControls = controls("VizAxisControls");
2972
+ var VizColorControls = controls("VizColorControls");
2973
+ var VizHeatmapControls = controls("VizHeatmapControls");
2974
+ var VizLineControls = controls("VizLineControls");
2975
+ var VizMarkControls = controls("VizMarkControls");
2976
+ var VizOpacityControls = controls("VizOpacityControls");
2977
+ var VizPointControls = controls("VizPointControls");
2978
+ var VizScaleControls = controls("VizScaleControls");
2979
+ var VizScatterControls = controls("VizScatterControls");
2980
+ var VizShapeControls = controls("VizShapeControls");
2981
+ var VizSizeControls = controls("VizSizeControls");
2982
+ var VizColorLegendConfig = controls("VizColorLegendConfig");
2983
+ var VizShapeLegend = controls("VizShapeLegend");
2984
+ var VizAxisSummary = summary("VizAxisSummary");
2985
+ var VizOpacitySummary = summary("VizOpacitySummary");
2986
+ var VizScaleSummary = summary("VizScaleSummary");
2987
+ var VizSizeSummary = summary("VizSizeSummary");
2988
+ var VizEncodingBadge = summary("VizEncodingBadge");
2989
+ var VizRoleBadge = summary("VizRoleBadge");
2990
+ var VizHeatmapPreview = preview("VizHeatmapPreview", "heatmap");
2991
+ var VizGraphPreview = preview("VizGraphPreview", "force_graph");
2992
+ var VizLinePreview = preview("VizLinePreview", "line");
2993
+ var VizMarkPreview = preview("VizMarkPreview", "bar");
2994
+ var VizPointPreview = preview("VizPointPreview", "scatter");
2995
+ var VizScatterPreview = preview("VizScatterPreview", "scatter");
2996
+
2997
+ exports.AddressCollectionPanel = AddressCollectionPanel;
2998
+ exports.AddressEditor = AddressEditor;
2999
+ exports.AddressSummaryBadge = AddressSummaryBadge;
3000
+ exports.AddressValidationTimeline = AddressValidationTimeline;
3001
+ exports.ArchiveEvent = ArchiveEvent;
3002
+ exports.ArchivePill = ArchivePill;
3003
+ exports.ArchiveSummary = ArchiveSummary;
3004
+ exports.ArchivedRowOverlay = ArchivedRowOverlay;
3005
+ exports.AuditEvent = AuditEvent;
3006
+ exports.AuditSummaryCard = AuditSummaryCard;
3007
+ exports.AuditTimeline = AuditTimeline;
3008
+ exports.Badge = Badge;
3009
+ exports.Banner = Banner;
3010
+ exports.BillingAmountInput = BillingAmountInput;
3011
+ exports.BillingCardMeta = BillingCardMeta;
3012
+ exports.BillingIntervalSelector = BillingIntervalSelector;
3013
+ exports.BillingSummaryBadge = BillingSummaryBadge;
3014
+ exports.Button = Button;
3015
+ exports.CancellationBadge = CancellationBadge;
3016
+ exports.CancellationEvent = CancellationEvent;
3017
+ exports.CancellationForm = CancellationForm;
3018
+ exports.CancellationSummary = CancellationSummary;
3019
+ exports.Card = Card;
3020
+ exports.CardHeader = CardHeader;
3021
+ exports.Checkbox = Checkbox;
3022
+ exports.ClassificationBadge = ClassificationBadge;
3023
+ exports.ClassificationEditor = ClassificationEditor;
3024
+ exports.ClassificationPanel = ClassificationPanel;
3025
+ exports.ColorStatePicker = ColorStatePicker;
3026
+ exports.ColorSwatch = ColorSwatch;
3027
+ exports.ColorizedBadge = ColorizedBadge;
3028
+ exports.CommunicationDetailPanel = CommunicationDetailPanel;
3029
+ exports.CycleProgressCard = CycleProgressCard;
3030
+ exports.DatePicker = DatePicker;
3031
+ exports.DetailHeader = DetailHeader;
3032
+ exports.FilterPanel = FilterPanel;
3033
+ exports.FormLabelGroup = FormLabelGroup;
3034
+ exports.GeoFieldMappingForm = GeoFieldMappingForm;
3035
+ exports.GeoResolutionBadge = GeoResolutionBadge;
3036
+ exports.GeocodablePreview = GeocodablePreview;
3037
+ exports.Grid = Grid;
3038
+ exports.InlineLabel = InlineLabel;
3039
+ exports.Input = Input;
3040
+ exports.LabelCell = LabelCell;
3041
+ exports.MembershipAuditTimeline = MembershipAuditTimeline;
3042
+ exports.MembershipPanel = MembershipPanel;
3043
+ exports.MessageEventTimeline = MessageEventTimeline;
3044
+ exports.MessageStatusBadge = MessageStatusBadge;
3045
+ exports.OwnerBadge = OwnerBadge;
3046
+ exports.OwnershipMeta = OwnershipMeta;
3047
+ exports.OwnershipSummary = OwnershipSummary;
3048
+ exports.PaginationBar = PaginationBar;
3049
+ exports.PaymentEventTimeline = PaymentEventTimeline;
3050
+ exports.PaymentTimeline = PaymentTimeline;
3051
+ exports.PreferenceEditor = PreferenceEditor;
3052
+ exports.PreferencePanel = PreferencePanel;
3053
+ exports.PreferenceSummaryBadge = PreferenceSummaryBadge;
3054
+ exports.PreferenceTimeline = PreferenceTimeline;
3055
+ exports.PriceBadge = PriceBadge;
3056
+ exports.PriceCardMeta = PriceCardMeta;
3057
+ exports.PriceSummary = PriceSummary;
3058
+ exports.RelativeTimestamp = RelativeTimestamp;
3059
+ exports.RoleAssignmentForm = RoleAssignmentForm;
3060
+ exports.RoleBadgeList = RoleBadgeList;
3061
+ exports.SearchInput = SearchInput;
3062
+ exports.Select = Select;
3063
+ exports.SortIndicator = SortIndicator;
3064
+ exports.Stack = Stack;
3065
+ exports.StateTransitionEvent = StateTransitionEvent;
3066
+ exports.StatusBadge = StatusBadge;
3067
+ exports.StatusColorLegend = StatusColorLegend;
3068
+ exports.StatusSelector = StatusSelector;
3069
+ exports.StatusTimeline = StatusTimeline;
3070
+ exports.Table = Table;
3071
+ exports.TableBody = TableBody;
3072
+ exports.TableCaption = TableCaption;
3073
+ exports.TableCell = TableCell;
3074
+ exports.TableHead = TableHead;
3075
+ exports.TableHeaderCell = TableHeaderCell;
3076
+ exports.TableRow = TableRow;
3077
+ exports.Tabs = Tabs;
3078
+ exports.TagInput = TagInput;
3079
+ exports.TagManager = TagManager;
3080
+ exports.TagPills = TagPills;
3081
+ exports.TagSummary = TagSummary;
3082
+ exports.TemplatePicker = TemplatePicker;
3083
+ exports.Text = Text;
3084
+ exports.Textarea = Textarea;
3085
+ exports.TimelineEntryLabel = TimelineEntryLabel;
3086
+ exports.VizAreaControls = VizAreaControls;
3087
+ exports.VizAreaPreview = VizAreaPreview;
3088
+ exports.VizAxisControls = VizAxisControls;
3089
+ exports.VizAxisSummary = VizAxisSummary;
3090
+ exports.VizColorControls = VizColorControls;
3091
+ exports.VizColorLegendConfig = VizColorLegendConfig;
3092
+ exports.VizEncodingBadge = VizEncodingBadge;
3093
+ exports.VizGraphPreview = VizGraphPreview;
3094
+ exports.VizHeatmapControls = VizHeatmapControls;
3095
+ exports.VizHeatmapPreview = VizHeatmapPreview;
3096
+ exports.VizLineControls = VizLineControls;
3097
+ exports.VizLinePreview = VizLinePreview;
3098
+ exports.VizMarkControls = VizMarkControls;
3099
+ exports.VizMarkPreview = VizMarkPreview;
3100
+ exports.VizOpacityControls = VizOpacityControls;
3101
+ exports.VizOpacitySummary = VizOpacitySummary;
3102
+ exports.VizPointControls = VizPointControls;
3103
+ exports.VizPointPreview = VizPointPreview;
3104
+ exports.VizRoleBadge = VizRoleBadge;
3105
+ exports.VizScaleControls = VizScaleControls;
3106
+ exports.VizScaleSummary = VizScaleSummary;
3107
+ exports.VizScatterControls = VizScatterControls;
3108
+ exports.VizScatterPreview = VizScatterPreview;
3109
+ exports.VizShapeControls = VizShapeControls;
3110
+ exports.VizShapeLegend = VizShapeLegend;
3111
+ exports.VizSizeControls = VizSizeControls;
3112
+ exports.VizSizeSummary = VizSizeSummary;