@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/ported.js ADDED
@@ -0,0 +1,1298 @@
1
+ import { dateTimeInputValue, formatDateTime, formatReadOnlyValue, currencyMinorUnits } from '@oods/component-contracts';
2
+ import { defineComponent, h, ref, computed, useId, onBeforeUnmount } from 'vue';
3
+
4
+ // src/ported.ts
5
+ var toneProp = String;
6
+ var emphasisProp = String;
7
+ var sizeProp = String;
8
+ var STATUS_TONES = {
9
+ active: "success",
10
+ paid: "success",
11
+ success: "success",
12
+ successful: "success",
13
+ future: "info",
14
+ pending: "info",
15
+ pending_cancellation: "info",
16
+ posted: "info",
17
+ processing: "info",
18
+ trialing: "accent",
19
+ caution: "warning",
20
+ warning: "warning",
21
+ delinquent: "critical",
22
+ error: "critical",
23
+ failed: "critical",
24
+ past_due: "critical",
25
+ unpaid: "critical"
26
+ };
27
+ var DOMAIN_STATUS_TONES = {
28
+ subscription: {
29
+ future: "info",
30
+ trialing: "accent",
31
+ active: "success",
32
+ paused: "neutral",
33
+ pending_cancellation: "info",
34
+ past_due: "critical",
35
+ unpaid: "critical",
36
+ terminated: "neutral"
37
+ },
38
+ invoice: {
39
+ draft: "neutral",
40
+ posted: "info",
41
+ paid: "success",
42
+ past_due: "critical",
43
+ void: "neutral"
44
+ }
45
+ };
46
+ function normalizeStatus(status) {
47
+ return status?.trim().toLowerCase().replace(/[\s-]+/g, "_") ?? "";
48
+ }
49
+ function resolveTone(tone, status, domain) {
50
+ const normalizedStatus = normalizeStatus(status);
51
+ const normalizedDomain = domain?.trim().toLowerCase() ?? "";
52
+ return tone ?? DOMAIN_STATUS_TONES[normalizedDomain]?.[normalizedStatus] ?? STATUS_TONES[normalizedStatus] ?? "neutral";
53
+ }
54
+ function tokenTone(tone) {
55
+ if (tone === "positive") return "success";
56
+ if (tone === "danger") return "critical";
57
+ return tone;
58
+ }
59
+ function statusLabel(status) {
60
+ return normalizeStatus(status).split("_").filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join(" ");
61
+ }
62
+ function slotOrValue(slot, value) {
63
+ return slot ? slot() : value;
64
+ }
65
+ var Badge = defineComponent({
66
+ name: "OodsBadge",
67
+ props: {
68
+ content: { type: [String, Number], default: void 0 },
69
+ status: String,
70
+ domain: { type: String, default: "subscription" },
71
+ tone: toneProp,
72
+ emphasis: { type: emphasisProp, default: "subtle" },
73
+ icon: { type: [String, Number, Object], default: void 0 }
74
+ },
75
+ setup(props, { slots }) {
76
+ return () => {
77
+ const tone = resolveTone(props.tone, props.status, props.domain);
78
+ const palette = tokenTone(tone);
79
+ return h("span", {
80
+ class: "oods-badge",
81
+ "data-oods-component": "Badge",
82
+ "data-status": props.status,
83
+ "data-domain": props.domain,
84
+ "data-tone": tone,
85
+ "data-emphasis": props.emphasis,
86
+ style: {
87
+ "--cmp-badge-background": `var(--sys-status-${palette}-surface)`,
88
+ "--cmp-badge-border": `var(--sys-status-${palette}-border)`,
89
+ "--cmp-badge-text": `var(--sys-status-${palette}-text)`
90
+ }
91
+ }, [
92
+ props.icon || slots.icon ? h("span", { class: "oods-badge-icon", "aria-hidden": "true" }, [slotOrValue(slots.icon, props.icon)]) : null,
93
+ slotOrValue(slots.default, props.content ?? statusLabel(props.status))
94
+ ]);
95
+ };
96
+ }
97
+ });
98
+ defineComponent({
99
+ name: "OodsBanner",
100
+ props: {
101
+ title: { type: String, default: "" },
102
+ detail: { type: String, default: "" },
103
+ content: { type: [String, Number], default: "" },
104
+ status: String,
105
+ domain: { type: String, default: "subscription" },
106
+ tone: toneProp,
107
+ emphasis: { type: emphasisProp, default: "subtle" },
108
+ dismissLabel: String
109
+ },
110
+ emits: {
111
+ dismiss: () => true
112
+ },
113
+ setup(props, { emit, slots }) {
114
+ return () => {
115
+ const tone = resolveTone(props.tone, props.status, props.domain);
116
+ const palette = tokenTone(tone);
117
+ return h("section", {
118
+ class: "oods-banner",
119
+ "data-oods-component": "Banner",
120
+ "data-status": props.status,
121
+ "data-domain": props.domain,
122
+ "data-tone": tone,
123
+ "data-emphasis": props.emphasis,
124
+ role: tone === "critical" || tone === "danger" ? "alert" : "status",
125
+ "aria-live": tone === "critical" || tone === "danger" ? "assertive" : "polite",
126
+ style: {
127
+ "--cmp-banner-background": `var(--sys-status-${palette}-surface)`,
128
+ "--cmp-banner-border": `var(--sys-status-${palette}-border)`,
129
+ "--cmp-banner-text": `var(--sys-status-${palette}-text)`
130
+ }
131
+ }, [
132
+ h("div", { class: "oods-banner-content" }, [
133
+ props.title || props.status || slots.title ? h("strong", { class: "oods-banner-title" }, [
134
+ slotOrValue(slots.title, props.title || statusLabel(props.status))
135
+ ]) : null,
136
+ props.detail ? h("p", { class: "oods-banner-detail" }, props.detail) : null,
137
+ slots.default || props.content ? h(slots.default ? "div" : "p", { class: "oods-banner-body" }, [slotOrValue(slots.default, props.content)]) : null,
138
+ slots.actions ? h("div", { class: "oods-banner-actions" }, slots.actions()) : null
139
+ ]),
140
+ props.dismissLabel ? h("button", {
141
+ type: "button",
142
+ class: "oods-banner-dismiss",
143
+ "aria-label": props.dismissLabel,
144
+ onClick: () => emit("dismiss")
145
+ }, [h("span", { "aria-hidden": "true" }, "\xD7")]) : null
146
+ ]);
147
+ };
148
+ }
149
+ });
150
+ defineComponent({
151
+ name: "OodsButton",
152
+ props: {
153
+ content: { type: [String, Number], default: "" },
154
+ intent: { type: String, default: "neutral" },
155
+ size: { type: sizeProp, default: "md" },
156
+ disabled: Boolean,
157
+ type: { type: String, default: "button" }
158
+ },
159
+ emits: {
160
+ activate: (_event) => true
161
+ },
162
+ setup(props, { emit, slots }) {
163
+ return () => h("button", {
164
+ type: props.type,
165
+ disabled: props.disabled,
166
+ class: "oods-button",
167
+ "data-oods-component": "Button",
168
+ "data-intent": props.intent,
169
+ "data-size": props.size,
170
+ style: {
171
+ "--cmp-button-background": "var(--sys-surface-interactive-primary-default)",
172
+ "--cmp-button-background-hover": "var(--sys-surface-interactive-primary-hover)",
173
+ "--cmp-button-background-disabled": "var(--sys-surface-disabled)",
174
+ "--cmp-button-border": "var(--sys-border-subtle)",
175
+ "--cmp-button-text": "var(--sys-text-on-interactive)",
176
+ "--cmp-button-text-disabled": "var(--sys-text-disabled)"
177
+ },
178
+ onClick: (event) => emit("activate", event)
179
+ }, [slotOrValue(slots.default, props.content)]);
180
+ }
181
+ });
182
+ var SAFE_CARD_ELEMENTS = /* @__PURE__ */ new Set(["div", "article", "section", "aside"]);
183
+ defineComponent({
184
+ name: "OodsCard",
185
+ props: {
186
+ elevated: Boolean,
187
+ as: { type: String, default: "div" }
188
+ },
189
+ setup(props, { slots }) {
190
+ return () => h(SAFE_CARD_ELEMENTS.has(props.as) ? props.as : "div", {
191
+ class: "oods-card",
192
+ "data-oods-component": "Card",
193
+ "data-elevated": String(props.elevated)
194
+ }, slots.default?.());
195
+ }
196
+ });
197
+ var GAP_VALUES = {
198
+ xs: "var(--cmp-spacing-inline-xs, 0.5rem)",
199
+ sm: "var(--cmp-spacing-inline-sm, 0.75rem)",
200
+ md: "var(--cmp-spacing-stack-default, 1rem)",
201
+ lg: "var(--cmp-spacing-stack-lg, 1.5rem)",
202
+ xl: "var(--cmp-spacing-stack-xl, 2rem)"
203
+ };
204
+ function resolveGap(gap) {
205
+ return GAP_VALUES[gap] ?? gap;
206
+ }
207
+ function maxColumns(columns) {
208
+ const count = Number(columns);
209
+ return Number.isFinite(count) && count > 0 ? count : void 0;
210
+ }
211
+ defineComponent({
212
+ name: "OodsGrid",
213
+ props: {
214
+ columns: { type: [Number, String], default: "auto-fit" },
215
+ minColumnWidth: { type: String, default: "16rem" },
216
+ gap: { type: String, default: "md" },
217
+ align: { type: String, default: "stretch" },
218
+ justify: { type: String, default: "normal" }
219
+ },
220
+ setup(props, { slots }) {
221
+ return () => h("div", {
222
+ class: "oods-grid",
223
+ "data-oods-component": "Grid",
224
+ "data-max-columns": maxColumns(props.columns),
225
+ style: {
226
+ "--oods-grid-columns": String(props.columns),
227
+ "--oods-grid-min-column": props.minColumnWidth,
228
+ "--oods-layout-gap": resolveGap(props.gap),
229
+ "--oods-layout-align": props.align,
230
+ "--oods-layout-justify": props.justify
231
+ }
232
+ }, slots.default?.());
233
+ }
234
+ });
235
+ defineComponent({
236
+ name: "OodsStack",
237
+ props: {
238
+ direction: { type: String, default: "column" },
239
+ gap: { type: String, default: "md" },
240
+ align: { type: String, default: "stretch" },
241
+ justify: { type: String, default: "flex-start" },
242
+ wrap: Boolean
243
+ },
244
+ setup(props, { slots }) {
245
+ return () => h("div", {
246
+ class: "oods-stack",
247
+ "data-oods-component": "Stack",
248
+ style: {
249
+ "--oods-stack-direction": props.direction,
250
+ "--oods-stack-wrap": props.wrap ? "wrap" : "nowrap",
251
+ "--oods-layout-gap": resolveGap(props.gap),
252
+ "--oods-layout-align": props.align,
253
+ "--oods-layout-justify": props.justify
254
+ }
255
+ }, slots.default?.());
256
+ }
257
+ });
258
+ var SAFE_TEXT_ELEMENTS = /* @__PURE__ */ new Set([
259
+ "span",
260
+ "p",
261
+ "strong",
262
+ "em",
263
+ "small",
264
+ "div",
265
+ "label",
266
+ "h1",
267
+ "h2",
268
+ "h3",
269
+ "h4",
270
+ "h5",
271
+ "h6"
272
+ ]);
273
+ defineComponent({
274
+ name: "OodsText",
275
+ props: {
276
+ content: { type: [String, Number], default: "" },
277
+ label: String,
278
+ as: { type: String, default: "span" },
279
+ size: { type: sizeProp, default: "md" },
280
+ weight: { type: String, default: "normal" }
281
+ },
282
+ setup(props, { slots }) {
283
+ return () => h(SAFE_TEXT_ELEMENTS.has(props.as) ? props.as : "span", {
284
+ class: "oods-text",
285
+ "data-oods-component": "Text",
286
+ "data-size": props.size,
287
+ "data-weight": props.weight,
288
+ "aria-description": props.label
289
+ }, [slotOrValue(slots.default, props.content)]);
290
+ }
291
+ });
292
+ function validationStyle(validation) {
293
+ if (!validation) return void 0;
294
+ const tone = validation.state === "error" ? "critical" : validation.state;
295
+ return {
296
+ "--cmp-input-message-border": `var(--sys-status-${tone}-border)`,
297
+ "--cmp-input-message-text": `var(--sys-status-${tone}-text)`
298
+ };
299
+ }
300
+ function useFieldMetadata(componentName, props, slots) {
301
+ const generatedId = useId();
302
+ const controlId = computed(() => props.id || `oods-${componentName.toLowerCase()}-${generatedId}`);
303
+ const helpId = computed(() => `${controlId.value}-help`);
304
+ const validationId = computed(() => `${controlId.value}-validation`);
305
+ const describedBy = computed(() => {
306
+ const ids = [];
307
+ if (props.help || slots.help) ids.push(helpId.value);
308
+ if (props.validation?.message || slots.validation) ids.push(validationId.value);
309
+ return ids.length > 0 ? ids.join(" ") : void 0;
310
+ });
311
+ return { controlId, helpId, validationId, describedBy };
312
+ }
313
+ function renderField(componentName, props, slots, metadata, control) {
314
+ const label = slots.label?.() ?? props.label;
315
+ const help = slots.help?.() ?? props.help;
316
+ const validation = slots.validation?.() ?? props.validation?.message;
317
+ return h("div", {
318
+ class: "oods-field",
319
+ "data-oods-component": componentName,
320
+ style: validationStyle(props.validation)
321
+ }, [
322
+ label ? h("label", { class: "oods-field-label", for: metadata.controlId.value }, [label, props.required ? h("span", { class: "oods-field-required", "aria-hidden": "true" }, "*") : null]) : null,
323
+ control,
324
+ help ? h("p", { id: metadata.helpId.value, class: "oods-field-help" }, help) : null,
325
+ validation ? h("p", {
326
+ id: metadata.validationId.value,
327
+ class: "oods-field-error",
328
+ role: props.validation?.state === "error" ? "alert" : void 0
329
+ }, validation) : null
330
+ ]);
331
+ }
332
+ var textFieldProps = {
333
+ id: String,
334
+ label: String,
335
+ modelValue: String,
336
+ value: String,
337
+ defaultValue: { type: String, default: "" },
338
+ placeholder: String,
339
+ required: Boolean,
340
+ disabled: Boolean,
341
+ readOnly: Boolean,
342
+ help: String,
343
+ validation: Object,
344
+ name: String,
345
+ min: String,
346
+ max: String,
347
+ step: [String, Number]
348
+ };
349
+ function eventValue(event) {
350
+ return event.target.value;
351
+ }
352
+ var Input = defineComponent({
353
+ name: "OodsInput",
354
+ props: {
355
+ ...textFieldProps,
356
+ type: { type: String, default: "text" }
357
+ },
358
+ emits: {
359
+ "update:modelValue": (_value) => true,
360
+ input: (_value) => true,
361
+ change: (_value) => true
362
+ },
363
+ setup(props, { emit, slots }) {
364
+ const internalValue = ref(props.defaultValue);
365
+ const currentValue = computed(() => props.modelValue ?? props.value ?? internalValue.value);
366
+ const metadata = useFieldMetadata("Input", props, slots);
367
+ const updateValue = (nextValue) => {
368
+ if (props.modelValue === void 0 && props.value === void 0) internalValue.value = nextValue;
369
+ emit("update:modelValue", nextValue);
370
+ emit("input", nextValue);
371
+ };
372
+ return () => renderField("Input", props, slots, metadata, h("input", {
373
+ id: metadata.controlId.value,
374
+ class: "oods-field-control",
375
+ type: props.type,
376
+ name: props.name,
377
+ value: props.type === "datetime-local" ? dateTimeInputValue(currentValue.value) : currentValue.value,
378
+ placeholder: props.placeholder,
379
+ required: props.required,
380
+ disabled: props.disabled,
381
+ readonly: props.readOnly,
382
+ min: props.min,
383
+ max: props.max,
384
+ step: props.step,
385
+ "aria-invalid": props.validation?.state === "error" ? "true" : void 0,
386
+ "aria-describedby": metadata.describedBy.value,
387
+ "aria-errormessage": props.validation?.state === "error" ? metadata.validationId.value : void 0,
388
+ onInput: (event) => updateValue(eventValue(event)),
389
+ onChange: (event) => emit("change", eventValue(event))
390
+ }));
391
+ }
392
+ });
393
+ defineComponent({
394
+ name: "OodsDatePicker",
395
+ inheritAttrs: false,
396
+ props: textFieldProps,
397
+ emits: {
398
+ "update:modelValue": (_value) => true,
399
+ input: (_value) => true,
400
+ change: (_value) => true
401
+ },
402
+ setup(props, { attrs, emit, slots }) {
403
+ return () => h("div", {
404
+ ...attrs,
405
+ class: ["oods-date-picker", attrs.class],
406
+ "data-oods-component": "DatePicker"
407
+ }, [h(Input, {
408
+ ...props,
409
+ type: "date",
410
+ "onUpdate:modelValue": (value) => emit("update:modelValue", value),
411
+ onInput: (value) => emit("input", value),
412
+ onChange: (value) => emit("change", value)
413
+ }, slots)]);
414
+ }
415
+ });
416
+ defineComponent({
417
+ name: "OodsTextarea",
418
+ props: {
419
+ ...textFieldProps,
420
+ rows: { type: Number, default: 3 }
421
+ },
422
+ emits: {
423
+ "update:modelValue": (_value) => true,
424
+ input: (_value) => true,
425
+ change: (_value) => true
426
+ },
427
+ setup(props, { emit, slots }) {
428
+ const internalValue = ref(props.defaultValue);
429
+ const currentValue = computed(() => props.modelValue ?? props.value ?? internalValue.value);
430
+ const metadata = useFieldMetadata("Textarea", props, slots);
431
+ const updateValue = (nextValue) => {
432
+ if (props.modelValue === void 0 && props.value === void 0) internalValue.value = nextValue;
433
+ emit("update:modelValue", nextValue);
434
+ emit("input", nextValue);
435
+ };
436
+ return () => renderField("Textarea", props, slots, metadata, h("textarea", {
437
+ id: metadata.controlId.value,
438
+ class: "oods-field-control",
439
+ name: props.name,
440
+ value: currentValue.value,
441
+ rows: props.rows,
442
+ placeholder: props.placeholder,
443
+ required: props.required,
444
+ disabled: props.disabled,
445
+ readonly: props.readOnly,
446
+ "aria-invalid": props.validation?.state === "error" ? "true" : void 0,
447
+ "aria-describedby": metadata.describedBy.value,
448
+ "aria-errormessage": props.validation?.state === "error" ? metadata.validationId.value : void 0,
449
+ onInput: (event) => updateValue(eventValue(event)),
450
+ onChange: (event) => emit("change", eventValue(event))
451
+ }));
452
+ }
453
+ });
454
+ defineComponent({
455
+ name: "OodsSelect",
456
+ props: {
457
+ id: String,
458
+ label: String,
459
+ modelValue: String,
460
+ value: String,
461
+ defaultValue: { type: String, default: "" },
462
+ placeholder: String,
463
+ required: Boolean,
464
+ disabled: Boolean,
465
+ help: String,
466
+ validation: Object,
467
+ name: String,
468
+ options: { type: Array, default: () => [] }
469
+ },
470
+ emits: {
471
+ "update:modelValue": (_value) => true,
472
+ change: (_value) => true
473
+ },
474
+ setup(props, { emit, slots }) {
475
+ const internalValue = ref(props.defaultValue);
476
+ const currentValue = computed(() => props.modelValue ?? props.value ?? internalValue.value);
477
+ const metadata = useFieldMetadata("Select", props, slots);
478
+ const selectValue = (nextValue) => {
479
+ if (props.modelValue === void 0 && props.value === void 0) internalValue.value = nextValue;
480
+ emit("update:modelValue", nextValue);
481
+ emit("change", nextValue);
482
+ };
483
+ return () => renderField("Select", props, slots, metadata, h("select", {
484
+ id: metadata.controlId.value,
485
+ class: "oods-field-control",
486
+ name: props.name,
487
+ value: currentValue.value,
488
+ required: props.required,
489
+ disabled: props.disabled,
490
+ "aria-invalid": props.validation?.state === "error" ? "true" : void 0,
491
+ "aria-describedby": metadata.describedBy.value,
492
+ "aria-errormessage": props.validation?.state === "error" ? metadata.validationId.value : void 0,
493
+ onChange: (event) => selectValue(eventValue(event))
494
+ }, [
495
+ props.placeholder ? h("option", {
496
+ value: "",
497
+ selected: currentValue.value === "",
498
+ disabled: true
499
+ }, props.placeholder) : null,
500
+ ...props.options.map((option) => h("option", {
501
+ key: option.value,
502
+ value: option.value,
503
+ selected: option.value === currentValue.value,
504
+ disabled: option.disabled
505
+ }, slots.option?.({ option }) ?? option.label))
506
+ ]));
507
+ }
508
+ });
509
+ defineComponent({
510
+ name: "OodsCheckbox",
511
+ props: {
512
+ id: String,
513
+ label: String,
514
+ modelValue: { type: Boolean, default: void 0 },
515
+ checked: { type: Boolean, default: void 0 },
516
+ defaultChecked: Boolean,
517
+ required: Boolean,
518
+ disabled: Boolean,
519
+ help: String,
520
+ validation: Object,
521
+ name: String
522
+ },
523
+ emits: {
524
+ "update:modelValue": (_value) => true,
525
+ change: (_value) => true
526
+ },
527
+ setup(props, { emit, slots }) {
528
+ const internalChecked = ref(props.defaultChecked);
529
+ const currentChecked = computed(() => props.modelValue ?? props.checked ?? internalChecked.value);
530
+ const metadata = useFieldMetadata("Checkbox", props, slots);
531
+ const change = (event) => {
532
+ const checked = event.target.checked;
533
+ if (props.modelValue === void 0 && props.checked === void 0) internalChecked.value = checked;
534
+ emit("update:modelValue", checked);
535
+ emit("change", checked);
536
+ };
537
+ return () => {
538
+ const label = slots.label?.() ?? props.label;
539
+ const help = slots.help?.() ?? props.help;
540
+ const validation = slots.validation?.() ?? props.validation?.message;
541
+ return h("div", {
542
+ class: "oods-field",
543
+ "data-oods-component": "Checkbox",
544
+ style: validationStyle(props.validation)
545
+ }, [
546
+ h("label", { class: "oods-checkbox", for: metadata.controlId.value }, [
547
+ h("input", {
548
+ id: metadata.controlId.value,
549
+ type: "checkbox",
550
+ name: props.name,
551
+ checked: currentChecked.value,
552
+ required: props.required,
553
+ disabled: props.disabled,
554
+ "aria-invalid": props.validation?.state === "error" ? "true" : void 0,
555
+ "aria-describedby": metadata.describedBy.value,
556
+ "aria-errormessage": props.validation?.state === "error" ? metadata.validationId.value : void 0,
557
+ onChange: change
558
+ }),
559
+ h("span", { class: "oods-field-label" }, [
560
+ label,
561
+ props.required ? h("span", { class: "oods-field-required", "aria-hidden": "true" }, "*") : null
562
+ ])
563
+ ]),
564
+ help ? h("p", { id: metadata.helpId.value, class: "oods-field-help" }, help) : null,
565
+ validation ? h("p", {
566
+ id: metadata.validationId.value,
567
+ class: "oods-field-error",
568
+ role: props.validation?.state === "error" ? "alert" : void 0
569
+ }, validation) : null
570
+ ]);
571
+ };
572
+ }
573
+ });
574
+
575
+ // src/ported-core.ts
576
+ var STATUS_METADATA = {
577
+ subscription: {
578
+ future: {
579
+ description: "Subscription is scheduled but not yet active.",
580
+ tone: "info",
581
+ icon: "\u23F2"
582
+ },
583
+ trialing: {
584
+ description: "Subscription is in a trial period before billing begins.",
585
+ tone: "accent",
586
+ icon: "\u2605"
587
+ },
588
+ active: {
589
+ description: "Subscription is paid and service is provisioned.",
590
+ tone: "success",
591
+ icon: "\u2714\uFE0E"
592
+ },
593
+ paused: {
594
+ description: "Billing paused temporarily by finance or customer request.",
595
+ tone: "neutral",
596
+ icon: "\u23F8"
597
+ },
598
+ pending_cancellation: {
599
+ description: "Subscription will cancel at period end but remains active now.",
600
+ tone: "info",
601
+ icon: "\u2026"
602
+ },
603
+ past_due: {
604
+ description: "Renewal payment failed; smart retries ongoing and access retained during the grace window.",
605
+ tone: "critical",
606
+ icon: "\u26A0\uFE0E"
607
+ },
608
+ unpaid: {
609
+ description: "Payment retries exhausted; service access revoked.",
610
+ tone: "critical",
611
+ icon: "\u2A2F"
612
+ },
613
+ terminated: {
614
+ description: "Subscription ended; service access revoked and billing stopped.",
615
+ tone: "neutral",
616
+ icon: "\u2205"
617
+ }
618
+ },
619
+ invoice: {
620
+ draft: {
621
+ description: "Invoice is editable and not yet finalized.",
622
+ tone: "neutral",
623
+ icon: "\u270E"
624
+ },
625
+ posted: {
626
+ description: "Invoice finalized with terms before collection.",
627
+ tone: "info",
628
+ icon: "\u2026"
629
+ },
630
+ paid: {
631
+ description: "Invoice collected successfully in full.",
632
+ tone: "success",
633
+ icon: "\u2714\uFE0E"
634
+ },
635
+ past_due: {
636
+ description: "Invoice is unpaid past its due date.",
637
+ tone: "critical",
638
+ icon: "\u2A2F"
639
+ },
640
+ void: {
641
+ description: "Invoice canceled and no longer payable.",
642
+ tone: "neutral",
643
+ icon: "\u2205"
644
+ }
645
+ }
646
+ };
647
+ var STATUS_ALIASES = {
648
+ subscription: { delinquent: "unpaid" }
649
+ };
650
+ function isRecord(value) {
651
+ return typeof value === "object" && value !== null && !Array.isArray(value);
652
+ }
653
+ function firstText(record, keys) {
654
+ for (const key of keys) {
655
+ const value = record[key];
656
+ if (value === void 0 || value === null || value === "") continue;
657
+ if (typeof value === "string") return value;
658
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
659
+ }
660
+ return void 0;
661
+ }
662
+ function statusLabel2(status) {
663
+ return status.split(/[_-]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
664
+ }
665
+ function getPortedStatusPresentation(domain, status) {
666
+ const normalizedDomain = domain.toLowerCase();
667
+ const normalizedStatus = status.toLowerCase();
668
+ const alias = STATUS_ALIASES[normalizedDomain]?.[normalizedStatus];
669
+ const metadata = STATUS_METADATA[normalizedDomain]?.[alias ?? normalizedStatus];
670
+ if (!metadata) {
671
+ return {
672
+ label: statusLabel2(status || "Unknown"),
673
+ description: "Status not found in registry; falling back to the requested token set.",
674
+ tone: "neutral"
675
+ };
676
+ }
677
+ return { ...metadata, label: statusLabel2(status) };
678
+ }
679
+ function formatPrice(amountCents, amount, currency) {
680
+ const normalizedAmount = amount;
681
+ if (normalizedAmount === void 0 || !Number.isFinite(normalizedAmount)) return void 0;
682
+ const formatted = Number.isInteger(normalizedAmount) ? String(normalizedAmount) : normalizedAmount.toFixed(2);
683
+ return currency ? `${currency.toUpperCase()} ${formatted}` : formatted;
684
+ }
685
+ function normalizeTimelineEvents(raw) {
686
+ if (!Array.isArray(raw)) return [];
687
+ const events = [];
688
+ for (const entry of raw) {
689
+ if (entry === void 0 || entry === null) continue;
690
+ if (!isRecord(entry)) {
691
+ events.push({ label: String(entry) });
692
+ continue;
693
+ }
694
+ const from = firstText(entry, ["from"]);
695
+ const to = firstText(entry, ["to"]);
696
+ events.push({
697
+ label: firstText(entry, ["label", "title"]) ?? (firstText(entry, ["event", "status", "state"]) ? statusLabel2(firstText(entry, ["event", "status", "state"])) : void 0) ?? firstText(entry, ["text", "name"]) ?? (to ? from ? `${statusLabel2(from)} \u2192 ${statusLabel2(to)}` : statusLabel2(to) : void 0) ?? "Event",
698
+ timestamp: firstText(entry, ["timestamp", "datetime", "time", "at", "createdAt", "updatedAt"]),
699
+ detail: firstText(entry, ["detail", "description", "message"]) ?? (to ? from ? `${statusLabel2(from)} \u2192 ${statusLabel2(to)}` : statusLabel2(to) : void 0),
700
+ actorId: firstText(entry, ["actorId", "actor_id", "actor"]),
701
+ reason: firstText(entry, ["reason"])
702
+ });
703
+ }
704
+ return events;
705
+ }
706
+ function integerAtLeast(value, minimum, fallback) {
707
+ return Number.isFinite(value) ? Math.max(minimum, Math.floor(value)) : fallback;
708
+ }
709
+ function buildPaginationItems(options) {
710
+ const count = integerAtLeast(options.count, 0, 0);
711
+ const rawPage = integerAtLeast(options.page, 1, 1);
712
+ const page = count > 0 ? Math.min(rawPage, count) : 1;
713
+ const items = [{
714
+ type: "previous",
715
+ page: page > 1 ? page - 1 : page,
716
+ selected: false,
717
+ disabled: count === 0 || page <= 1
718
+ }];
719
+ if (count === 0) {
720
+ items.push({ type: "next", page: 1, selected: false, disabled: true });
721
+ return { page, count, items };
722
+ }
723
+ const siblingCount = integerAtLeast(options.siblingCount ?? 1, 0, 1);
724
+ const boundaryCount = integerAtLeast(options.boundaryCount ?? 1, 1, 1);
725
+ const candidates = count <= boundaryCount * 2 + siblingCount * 2 + 3 ? Array.from({ length: count }, (_, index) => index + 1) : [.../* @__PURE__ */ new Set([
726
+ ...Array.from({ length: boundaryCount }, (_, index) => index + 1),
727
+ ...Array.from({ length: siblingCount * 2 + 1 }, (_, index) => page - siblingCount + index),
728
+ ...Array.from({ length: boundaryCount }, (_, index) => count - boundaryCount + index + 1)
729
+ ])].filter((candidate) => candidate >= 1 && candidate <= count).sort((left, right) => left - right);
730
+ let priorPage;
731
+ let ellipsisIndex = 0;
732
+ for (const pageNumber of candidates) {
733
+ if (priorPage !== void 0 && pageNumber - priorPage > 1) {
734
+ items.push({
735
+ type: "ellipsis",
736
+ selected: false,
737
+ disabled: true,
738
+ index: ellipsisIndex++
739
+ });
740
+ }
741
+ items.push({
742
+ type: "page",
743
+ page: pageNumber,
744
+ selected: pageNumber === page,
745
+ disabled: false
746
+ });
747
+ priorPage = pageNumber;
748
+ }
749
+ items.push({
750
+ type: "next",
751
+ page: page < count ? page + 1 : page,
752
+ selected: false,
753
+ disabled: page >= count
754
+ });
755
+ return { page, count, items };
756
+ }
757
+ function relativeTimestampLabel(datetime, now) {
758
+ if (now === void 0) return datetime;
759
+ const timestamp = Date.parse(datetime);
760
+ const reference = now instanceof Date ? now.getTime() : typeof now === "number" ? now : Date.parse(now);
761
+ if (!Number.isFinite(timestamp) || !Number.isFinite(reference)) return datetime;
762
+ const seconds = Math.round((timestamp - reference) / 1e3);
763
+ const absoluteSeconds = Math.abs(seconds);
764
+ const formatter = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
765
+ if (absoluteSeconds < 60) return formatter.format(seconds, "second");
766
+ const minutes = Math.round(seconds / 60);
767
+ if (Math.abs(minutes) < 60) return formatter.format(minutes, "minute");
768
+ const hours = Math.round(minutes / 60);
769
+ if (Math.abs(hours) < 24) return formatter.format(hours, "hour");
770
+ return formatter.format(Math.round(hours / 24), "day");
771
+ }
772
+
773
+ // src/ported.ts
774
+ function stringValue(value) {
775
+ if (typeof value === "string") return value;
776
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
777
+ return void 0;
778
+ }
779
+ function firstDefined(...values) {
780
+ return values.find((value) => value !== void 0);
781
+ }
782
+ function firstArray(...values) {
783
+ return values.find((value) => Array.isArray(value)) ?? [];
784
+ }
785
+ function mergedClass(componentClass, value) {
786
+ return value ? [componentClass, value] : [componentClass];
787
+ }
788
+ var StatusBadge = defineComponent({
789
+ name: "OodsStatusBadge",
790
+ inheritAttrs: false,
791
+ props: {
792
+ content: { type: [String, Number], default: void 0 },
793
+ status: String,
794
+ value: String,
795
+ label: String,
796
+ domain: { type: String, default: "subscription" },
797
+ tone: String,
798
+ emphasis: { type: String, default: "subtle" },
799
+ showIcon: { type: Boolean, default: true },
800
+ field: String,
801
+ statusField: String,
802
+ domainField: String,
803
+ readOnly: Boolean,
804
+ compact: Boolean,
805
+ variant: String
806
+ },
807
+ setup(props, { attrs, slots }) {
808
+ return () => {
809
+ const status = props.status ?? props.value ?? "unknown";
810
+ const presentation = getPortedStatusPresentation(props.domain, status);
811
+ const tone = props.tone === "lifecycle" ? presentation.tone : props.tone ?? presentation.tone;
812
+ const ariaLabel = stringValue(attrs["aria-label"]) ?? props.label ?? `Status: ${presentation.label}`;
813
+ return h(Badge, {
814
+ ...attrs,
815
+ class: mergedClass("oods-status-badge", attrs.class),
816
+ content: slots.default ? void 0 : props.content ?? presentation.label,
817
+ status,
818
+ domain: props.domain,
819
+ tone,
820
+ emphasis: props.variant === "solid" ? "solid" : props.emphasis,
821
+ icon: props.showIcon ? presentation.icon : void 0,
822
+ title: stringValue(attrs.title) ?? presentation.description,
823
+ "aria-label": ariaLabel,
824
+ "data-oods-component": "StatusBadge"
825
+ }, slots);
826
+ };
827
+ }
828
+ });
829
+ function currencyLabel(amountCents, amount, currency, minorUnits) {
830
+ const normalizedAmount = amountCents !== void 0 ? amountCents / currencyMinorUnits(currency, minorUnits) : amount;
831
+ if (normalizedAmount === void 0 || !Number.isFinite(normalizedAmount)) return void 0;
832
+ if (!currency) return formatPrice(void 0, normalizedAmount, void 0);
833
+ try {
834
+ return new Intl.NumberFormat("en-US", {
835
+ style: "currency",
836
+ currency: currency.toUpperCase()
837
+ }).format(normalizedAmount);
838
+ } catch {
839
+ return formatPrice(void 0, normalizedAmount, currency);
840
+ }
841
+ }
842
+ var PriceBadge = defineComponent({
843
+ name: "OodsPriceBadge",
844
+ inheritAttrs: false,
845
+ props: {
846
+ amountCents: Number,
847
+ unitAmountCents: Number,
848
+ minorUnits: Number,
849
+ amount: Number,
850
+ unitAmount: Number,
851
+ currency: String,
852
+ currencyCode: String,
853
+ label: String,
854
+ value: [String, Number],
855
+ emphasis: { type: String, default: "subtle" },
856
+ field: String,
857
+ amountField: String,
858
+ currencyField: String,
859
+ intervalField: String,
860
+ minorUnitsParameter: String
861
+ },
862
+ setup(props, { attrs, slots }) {
863
+ return () => {
864
+ const currency = props.currency ?? props.currencyCode;
865
+ const cents = props.amountCents ?? props.unitAmountCents;
866
+ const amount = props.amount ?? props.unitAmount;
867
+ const content = props.label ?? props.value ?? currencyLabel(cents, amount, currency, props.minorUnits) ?? "Price";
868
+ return h(Badge, {
869
+ ...attrs,
870
+ class: mergedClass("oods-price-badge", attrs.class),
871
+ content,
872
+ emphasis: props.emphasis,
873
+ title: stringValue(attrs.title) ?? props.label,
874
+ "data-oods-component": "PriceBadge",
875
+ "data-price": "true",
876
+ "data-badge-variant": "price",
877
+ "data-currency": currency?.toUpperCase()
878
+ }, slots);
879
+ };
880
+ }
881
+ });
882
+ var timelineProps = {
883
+ title: String,
884
+ source: Array,
885
+ events: Array,
886
+ history: Array,
887
+ entries: Array,
888
+ stateHistory: Array,
889
+ auditLog: Array,
890
+ status: String,
891
+ allowedTransitions: Array,
892
+ showActorId: { type: Boolean, default: true },
893
+ showReason: { type: Boolean, default: true },
894
+ maxVisible: Number,
895
+ field: String,
896
+ historyField: String,
897
+ statesParameter: String,
898
+ auditLogField: String,
899
+ createdField: String,
900
+ updatedField: String,
901
+ eventField: String,
902
+ eventTimestampField: String,
903
+ eventOptionsParameter: String,
904
+ showFromState: Boolean
905
+ };
906
+ function renderTimelineEvent(event, index, showActorId, showReason) {
907
+ return h("li", { key: `${event.timestamp ?? "event"}-${index}` }, [
908
+ h("article", { "data-timeline-event": "true" }, [
909
+ h("p", { "data-timeline-label": "true" }, event.label),
910
+ event.timestamp ? h("time", { "data-timeline-time": "true", datetime: event.timestamp }, formatDateTime(event.timestamp)) : null,
911
+ event.detail ? h("p", { "data-timeline-detail": "true" }, event.detail) : null,
912
+ showActorId && event.actorId ? h("p", { "data-timeline-actor": "true" }, `Actor: ${event.actorId}`) : null,
913
+ showReason && event.reason ? h("p", { "data-timeline-reason": "true" }, `Reason: ${event.reason}`) : null
914
+ ])
915
+ ]);
916
+ }
917
+ function timelineComponent(configuration) {
918
+ return defineComponent({
919
+ name: `Oods${configuration.componentId}`,
920
+ inheritAttrs: false,
921
+ props: timelineProps,
922
+ setup(props, { attrs, slots }) {
923
+ return () => {
924
+ 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);
925
+ const normalized = normalizeTimelineEvents(rawEvents);
926
+ const events = props.maxVisible === void 0 ? normalized : normalized.slice(0, Math.max(0, Math.floor(props.maxVisible)));
927
+ const title = props.title ?? configuration.defaultTitle;
928
+ const slottedEvents = slots.default?.();
929
+ return h("div", {
930
+ ...attrs,
931
+ class: mergedClass("oods-timeline", attrs.class),
932
+ role: "log",
933
+ "aria-label": stringValue(attrs["aria-label"]) ?? title,
934
+ "data-oods-component": configuration.componentId,
935
+ "data-timeline-type": configuration.timelineType
936
+ }, [
937
+ h("h2", { "data-timeline-title": "true" }, title),
938
+ props.status ? h("p", { "data-timeline-current": "true" }, `Current status: ${statusLabel2(props.status)}`) : null,
939
+ props.allowedTransitions?.length ? h("p", { "data-timeline-transitions": "true" }, `Allowed transitions: ${props.allowedTransitions.join(", ")}`) : null,
940
+ h("ol", { "data-timeline-events": "true" }, slottedEvents?.length ? slottedEvents : events.length ? events.map((event, index) => renderTimelineEvent(
941
+ event,
942
+ index,
943
+ props.showActorId,
944
+ props.showReason
945
+ )) : [h("li", { "data-timeline-empty": "true" }, "No events")])
946
+ ]);
947
+ };
948
+ }
949
+ });
950
+ }
951
+ var StatusTimeline = timelineComponent({
952
+ componentId: "StatusTimeline",
953
+ defaultTitle: "Status Timeline",
954
+ timelineType: "status"
955
+ });
956
+ var AuditTimeline = timelineComponent({
957
+ componentId: "AuditTimeline",
958
+ defaultTitle: "Audit Timeline",
959
+ timelineType: "audit"
960
+ });
961
+ function slotSummaryValue(slots) {
962
+ const nodes = slots.default?.() ?? [];
963
+ for (const node of nodes) {
964
+ if (typeof node.children === "string") {
965
+ const text = node.children.trim().toLowerCase();
966
+ if (text === "true") return true;
967
+ if (text === "false") return false;
968
+ if (node.children.length > 0) return node.children;
969
+ }
970
+ }
971
+ return void 0;
972
+ }
973
+ function summaryEntry(label, value, key) {
974
+ return h("div", { key, "data-summary-item": "true" }, [
975
+ h("dt", label),
976
+ h("dd", [value])
977
+ ]);
978
+ }
979
+ var CancellationSummary = defineComponent({
980
+ name: "OodsCancellationSummary",
981
+ inheritAttrs: false,
982
+ props: {
983
+ title: String,
984
+ label: String,
985
+ cancelAtPeriodEnd: { type: Boolean, default: void 0 },
986
+ requestedAt: String,
987
+ reason: String,
988
+ code: String,
989
+ field: String,
990
+ cancelAtPeriodEndField: String,
991
+ requestedAtField: String,
992
+ reasonField: String,
993
+ codeField: String
994
+ },
995
+ setup(props, { attrs, slots }) {
996
+ return () => {
997
+ const slotValue = slotSummaryValue(slots);
998
+ const cancellationValue = props.cancelAtPeriodEnd ?? slotValue;
999
+ const rows = [];
1000
+ if (cancellationValue !== void 0) {
1001
+ rows.push(summaryEntry(
1002
+ "Cancel at period end",
1003
+ typeof cancellationValue === "boolean" ? cancellationValue ? "Yes" : "No" : cancellationValue,
1004
+ "cancel-at-period-end"
1005
+ ));
1006
+ }
1007
+ if (props.requestedAt) {
1008
+ rows.push(summaryEntry(
1009
+ "Requested at",
1010
+ h("time", { datetime: props.requestedAt }, formatDateTime(props.requestedAt)),
1011
+ "requested-at"
1012
+ ));
1013
+ }
1014
+ if (props.reason) rows.push(summaryEntry("Reason", props.reason, "reason"));
1015
+ if (props.code) rows.push(summaryEntry("Code", formatReadOnlyValue(props.code, "string", true), "code"));
1016
+ return h("section", {
1017
+ ...attrs,
1018
+ class: mergedClass("oods-cancellation-summary", attrs.class),
1019
+ "data-oods-component": "CancellationSummary",
1020
+ "data-summary-type": "cancellation",
1021
+ "data-cancellation-state": props.cancelAtPeriodEnd === void 0 ? void 0 : String(props.cancelAtPeriodEnd)
1022
+ }, [
1023
+ h("h2", { "data-summary-title": "true" }, props.title ?? props.label ?? "Cancellation Summary"),
1024
+ h("dl", rows)
1025
+ ]);
1026
+ };
1027
+ }
1028
+ });
1029
+ function inputValue(event) {
1030
+ return event.target.value;
1031
+ }
1032
+ var SearchInput = defineComponent({
1033
+ name: "OodsSearchInput",
1034
+ inheritAttrs: false,
1035
+ props: {
1036
+ id: String,
1037
+ label: String,
1038
+ modelValue: String,
1039
+ value: String,
1040
+ defaultValue: { type: String, default: "" },
1041
+ placeholder: { type: String, default: "Search\u2026" },
1042
+ clearable: { type: Boolean, default: true },
1043
+ debounceMs: { type: Number, default: 0 },
1044
+ debounce: Number,
1045
+ minQueryLength: { type: Number, default: 0 },
1046
+ disabled: Boolean,
1047
+ field: String,
1048
+ placeholderParameter: String,
1049
+ debounceParameter: String,
1050
+ minQueryLengthParameter: String,
1051
+ clearableParameter: String
1052
+ },
1053
+ emits: {
1054
+ "update:modelValue": (_value) => true,
1055
+ valueChange: (_value) => true,
1056
+ update: (_value) => true,
1057
+ input: (_value) => true,
1058
+ search: (_value) => true,
1059
+ clear: () => true
1060
+ },
1061
+ setup(props, { attrs, emit }) {
1062
+ const generatedId = useId();
1063
+ const internalValue = ref(props.defaultValue);
1064
+ const currentValue = computed(() => props.modelValue ?? props.value ?? internalValue.value);
1065
+ let timer;
1066
+ onBeforeUnmount(() => {
1067
+ if (timer !== void 0) clearTimeout(timer);
1068
+ });
1069
+ const emitSearch = (value) => {
1070
+ if (value.length < Math.max(0, props.minQueryLength) && value.length !== 0) return;
1071
+ emit("valueChange", value);
1072
+ emit("update", value);
1073
+ emit("search", value);
1074
+ };
1075
+ const scheduleSearch = (value) => {
1076
+ if (timer !== void 0) clearTimeout(timer);
1077
+ const debounceMs = Math.max(0, props.debounceMs || props.debounce || 0);
1078
+ if (debounceMs > 0) {
1079
+ timer = setTimeout(() => emitSearch(value), debounceMs);
1080
+ } else {
1081
+ emitSearch(value);
1082
+ }
1083
+ };
1084
+ const updateValue = (value) => {
1085
+ if (props.modelValue === void 0 && props.value === void 0) internalValue.value = value;
1086
+ emit("update:modelValue", value);
1087
+ emit("input", value);
1088
+ scheduleSearch(value);
1089
+ };
1090
+ const clear = () => {
1091
+ if (props.disabled || currentValue.value.length === 0) return;
1092
+ if (timer !== void 0) clearTimeout(timer);
1093
+ updateValue("");
1094
+ emit("clear");
1095
+ };
1096
+ const submit = (event) => {
1097
+ event.preventDefault();
1098
+ if (!props.disabled) {
1099
+ if (timer !== void 0) clearTimeout(timer);
1100
+ emitSearch(currentValue.value);
1101
+ }
1102
+ };
1103
+ return () => {
1104
+ const id = props.id ?? `oods-search-${generatedId.replace(/:/g, "")}`;
1105
+ const ariaLabel = stringValue(attrs["aria-label"]) ?? props.label ?? props.placeholder;
1106
+ return h("form", {
1107
+ ...attrs,
1108
+ class: mergedClass("oods-search-input", attrs.class),
1109
+ role: "search",
1110
+ "aria-label": ariaLabel,
1111
+ "data-oods-component": "SearchInput",
1112
+ "data-behavioral": "search",
1113
+ onSubmit: submit
1114
+ }, [
1115
+ h(Input, {
1116
+ id,
1117
+ label: props.label,
1118
+ type: "search",
1119
+ value: currentValue.value,
1120
+ placeholder: props.placeholder,
1121
+ disabled: props.disabled,
1122
+ "aria-label": props.label ? void 0 : ariaLabel,
1123
+ onInput: (value) => updateValue(value),
1124
+ onKeydown: (event) => {
1125
+ if (event.key === "Escape") clear();
1126
+ }
1127
+ }),
1128
+ props.clearable && currentValue.value ? h("button", {
1129
+ type: "button",
1130
+ "data-search-clear": "true",
1131
+ "aria-label": "Clear search",
1132
+ disabled: props.disabled,
1133
+ onClick: clear
1134
+ }, "\xD7") : null
1135
+ ]);
1136
+ };
1137
+ }
1138
+ });
1139
+ var PaginationBar = defineComponent({
1140
+ name: "OodsPaginationBar",
1141
+ inheritAttrs: false,
1142
+ props: {
1143
+ page: { type: Number, default: 1 },
1144
+ pageSize: { type: Number, default: 25 },
1145
+ totalItems: { type: Number, default: 0 },
1146
+ totalPages: Number,
1147
+ pageSizeOptions: { type: Array, default: () => [10, 25, 50, 100] },
1148
+ showPageSizeSelector: Boolean,
1149
+ showGotoPage: Boolean,
1150
+ showItemRange: { type: Boolean, default: true },
1151
+ pageField: String,
1152
+ pageSizeField: String,
1153
+ totalItemsField: String,
1154
+ totalPagesField: String,
1155
+ pageSizeOptionsParameter: String,
1156
+ showPageSizeSelectorParameter: String,
1157
+ showGotoPageParameter: String,
1158
+ showItemRangeParameter: String
1159
+ },
1160
+ emits: {
1161
+ "update:page": (_page) => true,
1162
+ pageChange: (_page) => true,
1163
+ change: (_page) => true,
1164
+ update: (_page) => true,
1165
+ pageSizeChange: (_pageSize) => true
1166
+ },
1167
+ setup(props, { attrs, emit }) {
1168
+ const generatedId = useId().replace(/:/g, "");
1169
+ const gotoPage = ref("");
1170
+ const count = computed(() => props.totalPages ?? (props.pageSize > 0 ? Math.ceil(props.totalItems / props.pageSize) : 0));
1171
+ const pagination = computed(() => buildPaginationItems({
1172
+ page: props.page,
1173
+ count: count.value
1174
+ }));
1175
+ const setPage = (page) => {
1176
+ if (pagination.value.count === 0) return;
1177
+ const nextPage = Math.max(1, Math.min(Math.floor(page), pagination.value.count));
1178
+ if (nextPage === pagination.value.page) return;
1179
+ emit("update:page", nextPage);
1180
+ emit("pageChange", nextPage);
1181
+ emit("change", nextPage);
1182
+ emit("update", nextPage);
1183
+ };
1184
+ const submitGoto = (event) => {
1185
+ event.preventDefault();
1186
+ const nextPage = Number(gotoPage.value);
1187
+ if (Number.isFinite(nextPage)) setPage(nextPage);
1188
+ };
1189
+ return () => {
1190
+ const { page, count: totalPages, items } = pagination.value;
1191
+ return h("nav", {
1192
+ ...attrs,
1193
+ class: mergedClass("oods-pagination-bar", attrs.class),
1194
+ "aria-label": stringValue(attrs["aria-label"]) ?? "Pagination",
1195
+ "data-oods-component": "PaginationBar",
1196
+ "data-behavioral": "pagination"
1197
+ }, [
1198
+ props.showItemRange ? h("span", { "data-pagination-count": "true" }, `${props.totalItems} ${props.totalItems === 1 ? "record" : "records"}`) : null,
1199
+ // s211-m02: on one page the range only repeats the count ("15 records Showing 1–15 of 15").
1200
+ props.showItemRange && props.totalItems > 0 && totalPages > 1 ? 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,
1201
+ // s210-m01: one page needs no page controls; the count and range above already say what is shown.
1202
+ totalPages > 1 ? h("ul", { class: "oods-pagination-bar__pages" }, items.map((item, index) => {
1203
+ if (item.type === "ellipsis") {
1204
+ return h("li", { key: `ellipsis-${item.index ?? index}`, "aria-hidden": "true" }, "\u2026");
1205
+ }
1206
+ const label = item.type === "page" ? `Page ${item.page}` : item.type === "previous" ? "Previous page" : "Next page";
1207
+ const content = item.type === "page" ? String(item.page) : item.type === "previous" ? "\u2039" : "\u203A";
1208
+ return h("li", { key: `${item.type}-${item.page}` }, [
1209
+ h("button", {
1210
+ type: "button",
1211
+ "data-pagination-page": item.type === "page" ? item.page : void 0,
1212
+ "data-pagination-prev": item.type === "previous" ? "true" : void 0,
1213
+ "data-pagination-next": item.type === "next" ? "true" : void 0,
1214
+ "aria-label": label,
1215
+ "aria-current": item.selected ? "page" : void 0,
1216
+ disabled: item.disabled,
1217
+ onClick: () => item.page !== void 0 && setPage(item.page)
1218
+ }, content)
1219
+ ]);
1220
+ })) : null,
1221
+ totalPages > 1 ? h("span", { "data-pagination-current": "true" }, `Page ${page} of ${Math.max(1, totalPages)}`) : null,
1222
+ props.showPageSizeSelector ? h("label", { class: "oods-pagination-size" }, [
1223
+ h("span", "Items per page"),
1224
+ h("select", {
1225
+ value: props.pageSize,
1226
+ onChange: (event) => {
1227
+ const nextSize = Number(event.target.value);
1228
+ if (Number.isFinite(nextSize) && nextSize > 0 && nextSize !== props.pageSize) {
1229
+ emit("pageSizeChange", nextSize);
1230
+ }
1231
+ }
1232
+ }, props.pageSizeOptions.map((option) => h("option", { value: option }, String(option))))
1233
+ ]) : null,
1234
+ props.showGotoPage ? h("form", { class: "oods-pagination-goto", onSubmit: submitGoto }, [
1235
+ h("label", { for: `oods-pagination-goto-${generatedId}` }, "Go to page"),
1236
+ h("input", {
1237
+ id: `oods-pagination-goto-${generatedId}`,
1238
+ type: "number",
1239
+ min: 1,
1240
+ max: totalPages,
1241
+ value: gotoPage.value,
1242
+ onInput: (event) => {
1243
+ gotoPage.value = inputValue(event);
1244
+ }
1245
+ }),
1246
+ h("button", { type: "submit" }, "Go")
1247
+ ]) : null
1248
+ ]);
1249
+ };
1250
+ }
1251
+ });
1252
+ var RelativeTimestamp = defineComponent({
1253
+ name: "OodsRelativeTimestamp",
1254
+ inheritAttrs: false,
1255
+ props: {
1256
+ datetime: String,
1257
+ timestamp: String,
1258
+ value: String,
1259
+ updatedAt: String,
1260
+ createdAt: String,
1261
+ relative: String,
1262
+ label: String,
1263
+ text: String,
1264
+ timezone: String,
1265
+ hideWhenEmpty: Boolean,
1266
+ now: [String, Number, Date],
1267
+ field: String,
1268
+ fallbackField: String,
1269
+ timezoneParameter: String
1270
+ },
1271
+ setup(props, { attrs }) {
1272
+ return () => {
1273
+ const datetime = firstDefined(
1274
+ props.datetime,
1275
+ props.timestamp,
1276
+ props.value,
1277
+ props.updatedAt,
1278
+ props.createdAt
1279
+ ) ?? "";
1280
+ if (props.hideWhenEmpty && (!datetime || Number.isNaN(Date.parse(datetime)))) return null;
1281
+ const content = firstDefined(
1282
+ props.relative,
1283
+ props.label,
1284
+ props.text,
1285
+ props.now === void 0 ? formatDateTime(datetime, { timeZone: props.timezone }) : relativeTimestampLabel(datetime, props.now)
1286
+ ) ?? "";
1287
+ return h("time", {
1288
+ ...attrs,
1289
+ class: mergedClass("oods-relative-timestamp", attrs.class),
1290
+ datetime: datetime || void 0,
1291
+ title: stringValue(attrs.title) ?? formatDateTime(datetime, { timeZone: props.timezone }),
1292
+ "data-oods-component": "RelativeTimestamp"
1293
+ }, content);
1294
+ };
1295
+ }
1296
+ });
1297
+
1298
+ export { AuditTimeline, CancellationSummary, PaginationBar, PriceBadge, RelativeTimestamp, SearchInput, StatusBadge, StatusTimeline };