@lostgradient/cinder 0.9.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/components.json +82 -4
  3. package/dist/components/access-gate/index.js +381 -239
  4. package/dist/components/chat/builders.d.ts +17 -8
  5. package/dist/components/chat/conversation-model.d.ts +1 -1
  6. package/dist/components/chat/index.d.ts +1 -1
  7. package/dist/components/chat/index.js +300 -17
  8. package/dist/components/chat/utilities/conversation.d.ts +6 -6
  9. package/dist/components/chat-conversation-header/index.js +19 -6
  10. package/dist/components/chat-conversation-list/index.js +19 -6
  11. package/dist/components/code-block/code-block.css +15 -9
  12. package/dist/components/code-block/code-block.variables.js +8 -2
  13. package/dist/components/confirm-dialog/confirm-dialog.css +2 -0
  14. package/dist/components/confirm-dialog/confirm-dialog.schema.js +10 -1
  15. package/dist/components/confirm-dialog/confirm-dialog.types.d.ts +7 -0
  16. package/dist/components/confirm-dialog/index.js +478 -106
  17. package/dist/components/connection-indicator/connection-indicator.css +136 -0
  18. package/dist/components/connection-indicator/connection-indicator.css.d.ts +1 -0
  19. package/dist/components/connection-indicator/connection-indicator.schema.d.ts +3 -0
  20. package/dist/components/connection-indicator/connection-indicator.schema.js +89 -0
  21. package/dist/components/connection-indicator/connection-indicator.svelte.d.ts +21 -0
  22. package/dist/components/connection-indicator/connection-indicator.types.d.ts +46 -0
  23. package/dist/components/connection-indicator/connection-indicator.variables.d.ts +2 -0
  24. package/dist/components/connection-indicator/connection-indicator.variables.js +61 -0
  25. package/dist/components/connection-indicator/index.d.ts +5 -0
  26. package/dist/components/connection-indicator/index.js +409 -0
  27. package/dist/components/container/container.schema.js +4 -4
  28. package/dist/components/faceted-filter-bar/faceted-filter-bar.schema.js +5 -1
  29. package/dist/components/faceted-filter-bar/faceted-filter-bar.types.d.ts +2 -0
  30. package/dist/components/faceted-filter-bar/index.js +40 -31
  31. package/dist/components/invocation-rule-builder/index.d.ts +1 -1
  32. package/dist/components/invocation-rule-builder/index.js +305 -150
  33. package/dist/components/invocation-rule-builder/invocation-rule-builder.css +28 -0
  34. package/dist/components/invocation-rule-builder/invocation-rule-builder.schema.js +89 -5
  35. package/dist/components/invocation-rule-builder/invocation-rule-builder.svelte.d.ts +2 -1
  36. package/dist/components/invocation-rule-builder/invocation-rule-builder.types.d.ts +114 -16
  37. package/dist/components/navigation-bar/index.js +11 -17
  38. package/dist/components/run-step-timeline/index.d.ts +1 -1
  39. package/dist/components/run-step-timeline/index.js +1052 -560
  40. package/dist/components/run-step-timeline/run-step-branch-disclosure.svelte.d.ts +17 -0
  41. package/dist/components/run-step-timeline/run-step-timeline.css +124 -0
  42. package/dist/components/run-step-timeline/run-step-timeline.schema.js +873 -350
  43. package/dist/components/run-step-timeline/run-step-timeline.svelte.d.ts +1 -15
  44. package/dist/components/run-step-timeline/run-step-timeline.types.d.ts +182 -5
  45. package/dist/components/run-step-timeline/run-step-timeline.utilities.d.ts +60 -0
  46. package/dist/components/schedule-builder/index.d.ts +5 -0
  47. package/dist/components/schedule-builder/index.js +3176 -0
  48. package/dist/components/schedule-builder/schedule-builder.css +121 -0
  49. package/dist/components/schedule-builder/schedule-builder.css.d.ts +1 -0
  50. package/dist/components/schedule-builder/schedule-builder.schema.d.ts +3 -0
  51. package/dist/components/schedule-builder/schedule-builder.schema.js +141 -0
  52. package/dist/components/schedule-builder/schedule-builder.svelte.d.ts +22 -0
  53. package/dist/components/schedule-builder/schedule-builder.types.d.ts +130 -0
  54. package/dist/components/schedule-builder/schedule-builder.utilities.d.ts +78 -0
  55. package/dist/components/schedule-builder/schedule-builder.variables.d.ts +2 -0
  56. package/dist/components/schedule-builder/schedule-builder.variables.js +61 -0
  57. package/dist/components/tag-input/index.js +22 -4
  58. package/dist/components/tag-input/tag-input.schema.js +5 -1
  59. package/dist/components/tag-input/tag-input.types.d.ts +4 -0
  60. package/dist/index.d.ts +6 -2
  61. package/dist/index.js +23069 -20833
  62. package/dist/server/components/access-gate/index.js +2 -1
  63. package/dist/server/components/chat/index.js +4 -4
  64. package/dist/server/components/chat-conversation-header/index.js +5 -5
  65. package/dist/server/components/chat-conversation-list/index.js +3 -3
  66. package/dist/server/components/color-field/index.js +2 -2
  67. package/dist/server/components/confirm-dialog/index.js +7 -2
  68. package/dist/server/components/connection-indicator/index.js +16 -0
  69. package/dist/server/components/data-table/index.js +3 -3
  70. package/dist/server/components/diff-viewer/index.js +4 -3
  71. package/dist/server/components/faceted-filter-bar/index.js +1 -1
  72. package/dist/server/components/invocation-rule-builder/index.js +1 -1
  73. package/dist/server/components/json-schema-editor/index.js +4 -3
  74. package/dist/server/components/navigation-bar/index.js +1 -1
  75. package/dist/server/components/review-editor/index.js +4 -3
  76. package/dist/server/components/run-step-timeline/index.js +1 -1
  77. package/dist/server/components/schedule-builder/index.js +30 -0
  78. package/dist/server/components/side-navigation/index.js +2 -2
  79. package/dist/server/components/table/index.js +3 -3
  80. package/dist/server/components/tag-input/index.js +1 -1
  81. package/dist/server/components/time-field/index.js +5 -3
  82. package/dist/server/index.js +105 -92
  83. package/dist/server/index.server-0sx92swg.js +9 -0
  84. package/dist/server/{index.server-mzzb7dnm.js → index.server-2bbwajb2.js} +1 -1
  85. package/dist/server/{index.server-q7w4jw89.js → index.server-2s5h1d05.js} +2 -2
  86. package/dist/server/{index.server-dg83ke3k.js → index.server-2zdbz3rj.js} +1 -1
  87. package/dist/server/{index.server-0jhra85x.js → index.server-3w72dxz7.js} +4 -4
  88. package/dist/server/{index.server-5tf22d6e.js → index.server-4wx0qven.js} +3 -3
  89. package/dist/server/{index.server-qh9qzd5t.js → index.server-4x4hgvqe.js} +283 -13
  90. package/dist/server/{index.server-c6pe4kaf.js → index.server-5gzyqv78.js} +1 -1
  91. package/dist/server/{index.server-mqt1ngqw.js → index.server-6hnfzc7f.js} +19 -6
  92. package/dist/server/{index.server-894t97kv.js → index.server-7yvpb6z7.js} +4 -4
  93. package/dist/server/index.server-be9bpqeh.js +93 -0
  94. package/dist/server/{index.server-f3z7tpxr.js → index.server-c42b462p.js} +2 -4
  95. package/dist/server/{index.server-7vyzen5x.js → index.server-c5qv3p61.js} +171 -63
  96. package/dist/server/index.server-cch0eed5.js +144 -0
  97. package/dist/server/{index.server-btgb3d56.js → index.server-d5a5ccz7.js} +18 -12
  98. package/dist/server/index.server-g5jgq3jp.js +793 -0
  99. package/dist/server/index.server-gmh5ar16.js +2 -0
  100. package/dist/server/{index.server-0atyznp7.js → index.server-h89zk5ev.js} +7 -7
  101. package/dist/server/{index.server-jxg42jpz.js → index.server-jdcq5zza.js} +7 -7
  102. package/dist/server/{index.server-2dga5yzr.js → index.server-jfrssta6.js} +1 -1
  103. package/dist/server/{index.server-fy068yne.js → index.server-jj76374p.js} +1 -1
  104. package/dist/server/{index.server-39ne5cd1.js → index.server-pxq277t5.js} +32 -1
  105. package/dist/server/index.server-qbkxz7r2.js +750 -0
  106. package/dist/server/{index.server-marjz8ps.js → index.server-tcay903b.js} +3 -2
  107. package/dist/server/{index.server-yz4k36cw.js → index.server-z7hxtqs1.js} +1 -1
  108. package/package.json +87 -97
  109. package/src/components/chat/builders.ts +445 -14
  110. package/src/components/chat/conversation-model.ts +1 -0
  111. package/src/components/chat/index.ts +1 -0
  112. package/src/components/chat/utilities/conversation.ts +33 -15
  113. package/src/components/code-block/code-block.css +15 -9
  114. package/src/components/code-block/code-block.variables.json +7 -1
  115. package/src/components/code-block/code-block.variables.ts +7 -1
  116. package/src/components/confirm-dialog/confirm-dialog.css +2 -0
  117. package/src/components/confirm-dialog/confirm-dialog.schema.json +8 -0
  118. package/src/components/confirm-dialog/confirm-dialog.schema.ts +10 -0
  119. package/src/components/confirm-dialog/confirm-dialog.svelte +31 -3
  120. package/src/components/confirm-dialog/confirm-dialog.types.ts +7 -0
  121. package/src/components/connection-indicator/connection-indicator.css +136 -0
  122. package/src/components/connection-indicator/connection-indicator.examples.json +31 -0
  123. package/src/components/connection-indicator/connection-indicator.schema.json +29 -0
  124. package/src/components/connection-indicator/connection-indicator.schema.ts +36 -0
  125. package/src/components/connection-indicator/connection-indicator.svelte +63 -0
  126. package/src/components/connection-indicator/connection-indicator.types.ts +58 -0
  127. package/src/components/connection-indicator/connection-indicator.variables.json +1 -0
  128. package/src/components/connection-indicator/connection-indicator.variables.ts +3 -0
  129. package/src/components/connection-indicator/index.ts +10 -0
  130. package/src/components/container/container.schema.json +3 -3
  131. package/src/components/container/container.schema.ts +3 -3
  132. package/src/components/faceted-filter-bar/faceted-filter-bar.schema.json +4 -0
  133. package/src/components/faceted-filter-bar/faceted-filter-bar.schema.ts +4 -0
  134. package/src/components/faceted-filter-bar/faceted-filter-bar.svelte +13 -11
  135. package/src/components/faceted-filter-bar/faceted-filter-bar.types.ts +2 -0
  136. package/src/components/invocation-rule-builder/index.ts +4 -0
  137. package/src/components/invocation-rule-builder/invocation-rule-builder.css +28 -0
  138. package/src/components/invocation-rule-builder/invocation-rule-builder.examples.json +6 -0
  139. package/src/components/invocation-rule-builder/invocation-rule-builder.schema.json +76 -3
  140. package/src/components/invocation-rule-builder/invocation-rule-builder.schema.ts +83 -3
  141. package/src/components/invocation-rule-builder/invocation-rule-builder.svelte +326 -113
  142. package/src/components/invocation-rule-builder/invocation-rule-builder.types.ts +157 -43
  143. package/src/components/navigation-bar/navigation-bar.svelte +2 -2
  144. package/src/components/run-step-timeline/index.ts +8 -0
  145. package/src/components/run-step-timeline/run-step-branch-disclosure.svelte +16 -0
  146. package/src/components/run-step-timeline/run-step-timeline.css +124 -0
  147. package/src/components/run-step-timeline/run-step-timeline.examples.json +18 -0
  148. package/src/components/run-step-timeline/run-step-timeline.schema.json +872 -349
  149. package/src/components/run-step-timeline/run-step-timeline.schema.ts +931 -349
  150. package/src/components/run-step-timeline/run-step-timeline.svelte +388 -269
  151. package/src/components/run-step-timeline/run-step-timeline.types.ts +192 -5
  152. package/src/components/run-step-timeline/run-step-timeline.utilities.ts +310 -0
  153. package/src/components/schedule-builder/index.ts +14 -0
  154. package/src/components/schedule-builder/schedule-builder.css +121 -0
  155. package/src/components/schedule-builder/schedule-builder.examples.json +31 -0
  156. package/src/components/schedule-builder/schedule-builder.schema.json +78 -0
  157. package/src/components/schedule-builder/schedule-builder.schema.ts +86 -0
  158. package/src/components/schedule-builder/schedule-builder.svelte +629 -0
  159. package/src/components/schedule-builder/schedule-builder.types.ts +137 -0
  160. package/src/components/schedule-builder/schedule-builder.utilities.ts +344 -0
  161. package/src/components/schedule-builder/schedule-builder.variables.json +1 -0
  162. package/src/components/schedule-builder/schedule-builder.variables.ts +3 -0
  163. package/src/components/tag-input/tag-input.schema.json +4 -0
  164. package/src/components/tag-input/tag-input.schema.ts +4 -0
  165. package/src/components/tag-input/tag-input.svelte +20 -3
  166. package/src/components/tag-input/tag-input.types.ts +4 -0
  167. package/src/index.ts +23 -0
  168. package/src/styles/components.css +2 -0
  169. package/dist/server/index.server-qk1h0n02.js +0 -393
@@ -0,0 +1,137 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+
4
+ /**
5
+ * The unit for an interval recurrence.
6
+ */
7
+ export type ScheduleIntervalUnit = 'minutes' | 'hours' | 'days' | 'weeks';
8
+
9
+ /**
10
+ * The emitted, lossless recurrence value. There are only ever TWO value
11
+ * variants — `cron` and `interval` — even though the UI offers a third
12
+ * "presets" authoring mode. Presets are sugar that lower to one of these two
13
+ * (e.g. "every 15 minutes" → `interval`; "daily at 09:00" → `cron`); there is
14
+ * no `mode: 'preset'` in the value a consumer receives.
15
+ */
16
+ export type ScheduleValue =
17
+ | {
18
+ /** Discriminator: a 5-field cron recurrence. */
19
+ mode: 'cron';
20
+ /** The 5-field cron expression, space-separated (minute hour day-of-month month day-of-week). */
21
+ expression: string;
22
+ }
23
+ | {
24
+ /** Discriminator: a fixed-interval recurrence. */
25
+ mode: 'interval';
26
+ /** How many `unit`s between fires. A positive integer. */
27
+ every: number;
28
+ /** The interval unit. */
29
+ unit: ScheduleIntervalUnit;
30
+ };
31
+
32
+ /**
33
+ * The authoring mode surfaced in the UI's SegmentedControl. Distinct from
34
+ * {@link ScheduleValue}'s `mode` discriminator: `presets` is an authoring
35
+ * convenience that emits a `cron` or `interval` value.
36
+ */
37
+ export type ScheduleAuthoringMode = 'presets' | 'cron' | 'interval';
38
+
39
+ /**
40
+ * A single computed upcoming fire, as returned by the injected
41
+ * {@link ScheduleBuilderProps.computeNextFires} callback. The component never
42
+ * computes or formats times itself (it ships no date/cron dependency): the
43
+ * consumer returns already-formatted display strings.
44
+ */
45
+ export type ScheduleFire = {
46
+ /** Stable identity for the keyed preview list. */
47
+ id: string;
48
+ /** Human-readable label for this fire, e.g. "Mon Jun 1, 09:00". */
49
+ label: string;
50
+ };
51
+
52
+ /**
53
+ * Props for the ScheduleBuilder component.
54
+ *
55
+ * ScheduleBuilder is a presentational recurrence-definition control. It owns
56
+ * no scheduling execution, persistence, or date math. It is date-library-free:
57
+ * the "next N fires" preview is produced by the injected `computeNextFires`
58
+ * callback, and degrades gracefully (the preview is hidden) when the callback
59
+ * is absent. Scope is recurrence only — overlap policy, jitter, and backfill
60
+ * belong to the consumer's surrounding form.
61
+ */
62
+ export type ScheduleBuilderProps = Omit<
63
+ HTMLAttributes<HTMLDivElement>,
64
+ 'class' | 'children' | 'onchange'
65
+ > & {
66
+ /**
67
+ * The current recurrence value (controlled). Pass the value returned from
68
+ * `onchange` back in to commit an edit. When omitted, the component starts
69
+ * from a sensible default (`interval`, every 15 minutes).
70
+ */
71
+ value?: ScheduleValue | undefined;
72
+ /**
73
+ * Called whenever the user edits the recurrence. Receives the next lossless
74
+ * {@link ScheduleValue}. The consumer owns persistence and validation.
75
+ */
76
+ onchange?: ((value: ScheduleValue) => void) | undefined;
77
+ /**
78
+ * Injected next-fires computation. The component passes the current value and
79
+ * the requested count and renders whatever fires the consumer returns. When
80
+ * omitted, the preview list is hidden (the component ships no date logic).
81
+ */
82
+ computeNextFires?: ((value: ScheduleValue, count: number) => ScheduleFire[]) | undefined;
83
+ /**
84
+ * How many upcoming fires to request from `computeNextFires`. Defaults to 5.
85
+ */
86
+ previewCount?: number | undefined;
87
+ /**
88
+ * Timezone label rendered in the always-visible timezone display slot, e.g.
89
+ * "America/New_York" or "UTC". Purely presentational — the component does not
90
+ * interpret it. When omitted, provide the `timezone` snippet instead; if
91
+ * neither is supplied the slot shows a "Not set" placeholder (the slot itself
92
+ * is always rendered).
93
+ */
94
+ timezoneLabel?: string | undefined;
95
+ /**
96
+ * Custom content for the timezone display slot. Takes precedence over
97
+ * `timezoneLabel` when both are supplied.
98
+ */
99
+ timezone?: Snippet | undefined;
100
+ /** Accessible label for the whole control. Defaults to "Schedule". */
101
+ label?: string | undefined;
102
+ /** Additional CSS classes applied to the root element. */
103
+ class?: string | undefined;
104
+ };
105
+
106
+ /**
107
+ * Schema-facing mirror of {@link ScheduleValue}.
108
+ * @schemaObject
109
+ */
110
+ export type ScheduleValueSchema =
111
+ | { mode: 'cron'; expression: string }
112
+ | { mode: 'interval'; every: number; unit: ScheduleIntervalUnit };
113
+
114
+ /**
115
+ * Cinder-specific schema surface for ScheduleBuilder.
116
+ *
117
+ * The `onchange`, `computeNextFires`, and `timezone` props are documented but
118
+ * unsupported by JSON Schema because functions and snippets cannot be modeled.
119
+ */
120
+ export type ScheduleBuilderSchemaProps = {
121
+ /**
122
+ * The current recurrence value (controlled). When omitted, the component
123
+ * starts from a sensible default (`interval`, every 15 minutes).
124
+ * @schemaObject
125
+ */
126
+ value?: ScheduleValueSchema | undefined;
127
+ /**
128
+ * How many upcoming fires to request from `computeNextFires`. Defaults to 5.
129
+ */
130
+ previewCount?: number | undefined;
131
+ /** Timezone label rendered in the always-visible timezone display slot. */
132
+ timezoneLabel?: string | undefined;
133
+ /** Accessible label for the whole control. Defaults to "Schedule". */
134
+ label?: string | undefined;
135
+ /** Additional CSS classes applied to the root element. */
136
+ class?: string | undefined;
137
+ };
@@ -0,0 +1,344 @@
1
+ import type { ScheduleIntervalUnit, ScheduleValue } from './schedule-builder.types.ts';
2
+
3
+ /** The interval units offered in the interval mode selector, in order. */
4
+ export const INTERVAL_UNITS: readonly ScheduleIntervalUnit[] = [
5
+ 'minutes',
6
+ 'hours',
7
+ 'days',
8
+ 'weeks',
9
+ ];
10
+
11
+ /** Weekday chips for the weekly preset. `value` matches cron day-of-week (0 = Sunday). */
12
+ export const WEEKDAYS: readonly { value: number; short: string; long: string }[] = [
13
+ { value: 1, short: 'Mon', long: 'Monday' },
14
+ { value: 2, short: 'Tue', long: 'Tuesday' },
15
+ { value: 3, short: 'Wed', long: 'Wednesday' },
16
+ { value: 4, short: 'Thu', long: 'Thursday' },
17
+ { value: 5, short: 'Fri', long: 'Friday' },
18
+ { value: 6, short: 'Sat', long: 'Saturday' },
19
+ { value: 0, short: 'Sun', long: 'Sunday' },
20
+ ];
21
+
22
+ /** The five cron fields, in order, with their valid numeric ranges and hints. */
23
+ export const CRON_FIELDS: readonly {
24
+ name: string;
25
+ min: number;
26
+ max: number;
27
+ hint: string;
28
+ }[] = [
29
+ { name: 'Minute', min: 0, max: 59, hint: '0–59' },
30
+ { name: 'Hour', min: 0, max: 23, hint: '0–23' },
31
+ { name: 'Day of month', min: 1, max: 31, hint: '1–31' },
32
+ { name: 'Month', min: 1, max: 12, hint: '1–12' },
33
+ { name: 'Day of week', min: 0, max: 6, hint: '0–6 (Sun–Sat)' },
34
+ ];
35
+
36
+ /** The default value used when the consumer passes none. */
37
+ export function defaultScheduleValue(): ScheduleValue {
38
+ return { mode: 'interval', every: 15, unit: 'minutes' };
39
+ }
40
+
41
+ /** Singularize an interval unit for "every 1 X" phrasing. */
42
+ function singularUnit(unit: ScheduleIntervalUnit): string {
43
+ return unit.slice(0, -1);
44
+ }
45
+
46
+ /**
47
+ * Validate a single cron field value against its field range. Accepts a
48
+ * wildcard, a step, a range, a comma list, or a plain number. Returns an error
49
+ * message when invalid, or `undefined` when the field is acceptable.
50
+ */
51
+ export function validateCronField(rawValue: string, fieldIndex: number): string | undefined {
52
+ const field = CRON_FIELDS[fieldIndex];
53
+ if (!field) return 'Unknown field.';
54
+ const value = rawValue.trim();
55
+ if (value === '') return 'Required.';
56
+
57
+ const inRange = (n: number): boolean => Number.isInteger(n) && n >= field.min && n <= field.max;
58
+
59
+ // Comma-separated list: every part must be valid on its own.
60
+ for (const part of value.split(',')) {
61
+ const token = part.trim();
62
+ if (token === '') return 'Empty list item.';
63
+
64
+ // `*` or `*/step`
65
+ if (token === '*') continue;
66
+ const stepMatch = token.match(/^(\*|\d+-\d+)\/(\d+)$/);
67
+ if (stepMatch) {
68
+ const step = Number(stepMatch[2] ?? '');
69
+ if (!Number.isInteger(step) || step <= 0) return 'Step must be a positive integer.';
70
+ // A step larger than the field's maximum (e.g. `*/100` for minutes) is
71
+ // out of the advertised range and rejected by many cron parsers.
72
+ if (step > field.max) return `Step is larger than ${field.hint}.`;
73
+ const rangeText = stepMatch[1] ?? '*';
74
+ if (rangeText !== '*') {
75
+ const [a, b] = rangeText.split('-').map(Number);
76
+ if (a === undefined || b === undefined || !inRange(a) || !inRange(b)) {
77
+ return `Out of range (${field.hint}).`;
78
+ }
79
+ if (a > b) return 'Range start is after its end.';
80
+ }
81
+ continue;
82
+ }
83
+
84
+ // Range `a-b`
85
+ const rangeMatch = token.match(/^(\d+)-(\d+)$/);
86
+ if (rangeMatch) {
87
+ const a = Number(rangeMatch[1]);
88
+ const b = Number(rangeMatch[2]);
89
+ if (!inRange(a) || !inRange(b)) return `Out of range (${field.hint}).`;
90
+ if (a > b) return 'Range start is after its end.';
91
+ continue;
92
+ }
93
+
94
+ // Plain number
95
+ if (/^\d+$/.test(token)) {
96
+ if (!inRange(Number(token))) return `Out of range (${field.hint}).`;
97
+ continue;
98
+ }
99
+
100
+ return 'Not a valid cron token.';
101
+ }
102
+
103
+ return undefined;
104
+ }
105
+
106
+ /** Whether all five cron fields are individually valid. */
107
+ export function cronFieldsValid(fields: string[]): boolean {
108
+ return (
109
+ fields.length === 5 &&
110
+ fields.every((value, index) => validateCronField(value, index) === undefined)
111
+ );
112
+ }
113
+
114
+ /** Join five cron fields into a normalized single-space expression. */
115
+ export function joinCron(fields: string[]): string {
116
+ return fields.map((field) => field.trim()).join(' ');
117
+ }
118
+
119
+ /** Split a cron expression into exactly five fields, padding with `*` if short. */
120
+ export function splitCron(expression: string): [string, string, string, string, string] {
121
+ const parts = expression.trim().split(/\s+/).filter(Boolean);
122
+ // Preserve any overflow (e.g. a 6-field seconds cron) in the last field rather
123
+ // than silently dropping it — the joined field then fails per-field validation
124
+ // so the out-of-contract expression is surfaced instead of quietly truncated.
125
+ const lastField = parts.length > 5 ? parts.slice(4).join(' ') : parts[4];
126
+ return [parts[0] ?? '*', parts[1] ?? '*', parts[2] ?? '*', parts[3] ?? '*', lastField ?? '*'];
127
+ }
128
+
129
+ /** Clamp/parse a `HH:MM` time string into `{ hour, minute }`, defaulting to 00:00. */
130
+ export function parseTime(time: string): { hour: number; minute: number } {
131
+ const match = time.trim().match(/^(\d{1,2}):(\d{2})$/);
132
+ if (!match) return { hour: 0, minute: 0 };
133
+ const hour = Math.min(23, Math.max(0, Number(match[1])));
134
+ const minute = Math.min(59, Math.max(0, Number(match[2])));
135
+ return { hour, minute };
136
+ }
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // Preset lowering — presets are sugar that lower to a cron or interval value.
140
+ // ---------------------------------------------------------------------------
141
+
142
+ /** "every N minutes/hours" → an interval value. */
143
+ export function lowerEveryN(every: number, unit: ScheduleIntervalUnit): ScheduleValue {
144
+ const safeEvery = Number.isInteger(every) && every > 0 ? every : 1;
145
+ return { mode: 'interval', every: safeEvery, unit };
146
+ }
147
+
148
+ /** "daily at HH:MM" → a cron value. */
149
+ export function lowerDailyAt(time: string): ScheduleValue {
150
+ const { hour, minute } = parseTime(time);
151
+ return { mode: 'cron', expression: `${minute} ${hour} * * *` };
152
+ }
153
+
154
+ /** "weekly on [days] at HH:MM" → a cron value. Empty days falls back to every day. */
155
+ export function lowerWeeklyAt(days: number[], time: string): ScheduleValue {
156
+ const { hour, minute } = parseTime(time);
157
+ // Don't use `Array.prototype.toSorted()` — it is ES2023 and the package
158
+ // targets ES2022, so it crashes runtimes that follow the target. Sort a
159
+ // caller-owned copy in place instead of mutating the input.
160
+ const sortedDays = [...days];
161
+ sortedDays.sort((a, b) => a - b);
162
+ const dow = sortedDays.length > 0 ? sortedDays.join(',') : '*';
163
+ return { mode: 'cron', expression: `${minute} ${hour} * * ${dow}` };
164
+ }
165
+
166
+ /** "monthly on day N at HH:MM" → a cron value. */
167
+ export function lowerMonthlyOnDay(day: number, time: string): ScheduleValue {
168
+ const { hour, minute } = parseTime(time);
169
+ const safeDay = Math.min(31, Math.max(1, Math.trunc(day)));
170
+ return { mode: 'cron', expression: `${minute} ${hour} ${safeDay} * *` };
171
+ }
172
+
173
+ // ---------------------------------------------------------------------------
174
+ // Cross-mode conversion — preserve intent losslessly where representable.
175
+ // ---------------------------------------------------------------------------
176
+
177
+ /**
178
+ * Whether a step wildcard of `every` on a field that wraps every `cycleLength`
179
+ * units (60 for minutes, 24 for hours) is a genuinely fixed-gap interval. A
180
+ * step wildcard restarts counting from the field's own zero point each cycle
181
+ * (e.g. the minute field restarts at :00 every hour), so it only behaves like
182
+ * a true "every N units, forever" interval when `every` evenly divides the
183
+ * cycle — otherwise the LAST gap before the restart is shorter than `every`.
184
+ * For example, an every-45-minutes step wildcard fires at :00 and :45 — a
185
+ * 45-then-15-minute gap, not a steady 45 minutes — because 45 does not
186
+ * divide 60.
187
+ */
188
+ function isEvenDivisorStep(every: number, cycleLength: number): boolean {
189
+ // Must be strictly below the cycle length: a `*/N` where N equals (or exceeds)
190
+ // the cycle is out of the field's range (e.g. `*/60` for minutes, max 59) and
191
+ // is rejected by the cron validator, so it is not a representable step.
192
+ return Number.isInteger(every) && every > 0 && every < cycleLength && cycleLength % every === 0;
193
+ }
194
+
195
+ /**
196
+ * Convert any value into cron fields, so switching into cron mode always has
197
+ * something sensible to show. Only `minutes` and `hours` intervals whose
198
+ * `every` evenly divides their field's cycle (60 for minutes, 24 for hours)
199
+ * are losslessly representable as a cron step wildcard — see
200
+ * `isEvenDivisorStep`. A non-dividing minute/hour interval (e.g. every 45
201
+ * minutes or every 5 hours), and `days`/`weeks` intervals (a step wildcard on
202
+ * the day-of-month field resets at the start of every month rather than
203
+ * repeating every N days, and cron has no field for "every N weeks" at all),
204
+ * would silently change the schedule's actual cadence the moment a user
205
+ * merely LOOKED at the Cron tab if lowered to a step wildcard. Seed an honest
206
+ * neutral default (daily at midnight) instead for all of those — see
207
+ * `valueToInterval` below, which mirrors this by only recovering a step that
208
+ * actually divides evenly.
209
+ */
210
+ export function valueToCronFields(value: ScheduleValue): string[] {
211
+ if (value.mode === 'cron') return splitCron(value.expression);
212
+ switch (value.unit) {
213
+ case 'minutes':
214
+ if (isEvenDivisorStep(value.every, 60)) return [`*/${value.every}`, '*', '*', '*', '*'];
215
+ // A whole-hour minute cadence (60, 120, …) is an hours interval in
216
+ // disguise: lower it through the hour field where representable rather
217
+ // than collapsing to a daily default that would turn hourly into daily.
218
+ if (value.every % 60 === 0) return hoursIntervalFields(value.every / 60);
219
+ return ['0', '0', '*', '*', '*'];
220
+ case 'hours':
221
+ return hoursIntervalFields(value.every);
222
+ case 'days':
223
+ case 'weeks':
224
+ return ['0', '0', '*', '*', '*'];
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Lower an "every N hours" cadence to cron fields. Hourly (`N === 1`) is the
230
+ * plain `0 * * * *`; a larger N that evenly divides 24 becomes an hour step
231
+ * wildcard. Anything that does not divide the day (every 5 hours, or `N >= 24`)
232
+ * has no faithful fixed-gap cron form — a step wildcard would reset at midnight
233
+ * and shift the cadence at the day boundary — so it seeds the neutral daily
234
+ * default instead.
235
+ */
236
+ function hoursIntervalFields(everyHours: number): string[] {
237
+ if (everyHours === 1) return ['0', '*', '*', '*', '*'];
238
+ if (isEvenDivisorStep(everyHours, 24)) return ['0', `*/${everyHours}`, '*', '*', '*'];
239
+ return ['0', '0', '*', '*', '*'];
240
+ }
241
+
242
+ /**
243
+ * Recover an interval `{ every, unit }` from a value when representable, so
244
+ * switching into interval mode is lossless for simple step patterns. Only a
245
+ * `minutes` or `hours` step that evenly divides its field's cycle (60 or 24
246
+ * — see `isEvenDivisorStep`) is recovered: a non-dividing minute/hour step
247
+ * (e.g. a 45-minute step wildcard) is not a true fixed-gap interval, and a
248
+ * day-of-month step
249
+ * wildcard is deliberately NOT read back as `{ unit: 'days' }` at all —
250
+ * cron's day-of-month step resets every month, so it is not equivalent to
251
+ * "every N days" and `valueToCronFields` never produces one for an interval
252
+ * value. Returns `undefined` when the cron expression is not a pure,
253
+ * evenly-dividing minute/hour interval.
254
+ */
255
+ export function valueToInterval(
256
+ value: ScheduleValue,
257
+ ): { every: number; unit: ScheduleIntervalUnit } | undefined {
258
+ if (value.mode === 'interval') return { every: value.every, unit: value.unit };
259
+ const [minute, hour, dom, month, dow] = splitCron(value.expression);
260
+ if (month !== '*' || dow !== '*') return undefined;
261
+ // Common plain-wildcard forms: `* * * * *` = every minute, `0 * * * *` = hourly.
262
+ if (dom === '*' && hour === '*' && minute === '*') return { every: 1, unit: 'minutes' };
263
+ if (dom === '*' && hour === '*' && minute === '0') return { every: 1, unit: 'hours' };
264
+ const minuteStep = cronFieldStep(minute);
265
+ if (minuteStep && isEvenDivisorStep(minuteStep, 60) && hour === '*' && dom === '*') {
266
+ return { every: minuteStep, unit: 'minutes' };
267
+ }
268
+ const hourStep = cronFieldStep(hour);
269
+ if (hourStep && isEvenDivisorStep(hourStep, 24) && minute === '0' && dom === '*') {
270
+ return { every: hourStep, unit: 'hours' };
271
+ }
272
+ return undefined;
273
+ }
274
+
275
+ /** Extract the numeric step from a bare wildcard-step cron token, or `undefined`. */
276
+ function cronFieldStep(token: string): number | undefined {
277
+ const match = token.match(/^\*\/(\d+)$/);
278
+ return match ? Number(match[1]) : undefined;
279
+ }
280
+
281
+ // ---------------------------------------------------------------------------
282
+ // Plain-English summary — no date library required.
283
+ // ---------------------------------------------------------------------------
284
+
285
+ const DOW_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
286
+
287
+ function pad2(n: number): string {
288
+ return n.toString().padStart(2, '0');
289
+ }
290
+
291
+ /** Whether a cron token is a single integer within the inclusive `[min, max]` range. */
292
+ function isFixedInRange(token: string, min: number, max: number): boolean {
293
+ if (!/^\d+$/.test(token)) return false;
294
+ const n = Number(token);
295
+ return Number.isInteger(n) && n >= min && n <= max;
296
+ }
297
+
298
+ /** A concise, human-readable description of a recurrence value. */
299
+ export function describeValue(value: ScheduleValue): string {
300
+ if (value.mode === 'interval') {
301
+ return value.every === 1
302
+ ? `Every ${singularUnit(value.unit)}`
303
+ : `Every ${value.every} ${value.unit}`;
304
+ }
305
+
306
+ const [minute, hour, dom, month, dow] = splitCron(value.expression);
307
+ // A "fixed" field must be a single integer WITHIN its cron range: an
308
+ // out-of-range digit token like `99` is invalid (validateCronField rejects it
309
+ // and preview is disabled), so the always-visible summary must not present
310
+ // `Daily at 99:00` or `Monthly on day 99` as if it were a real cadence.
311
+ const timeIsFixed = isFixedInRange(minute, 0, 59) && isFixedInRange(hour, 0, 23);
312
+ const at = timeIsFixed ? ` at ${pad2(Number(hour))}:${pad2(Number(minute))}` : '';
313
+
314
+ // The "Weekly"/"Monthly" phrasings imply a single fire per day, so they only
315
+ // hold when BOTH minute and hour are fixed. A schedule like `* * * * 1` or
316
+ // `0 * * * 1` fires every minute / every hour on Mondays — calling that
317
+ // "Weekly on Monday" understates the cadence — so those fall through to the
318
+ // raw cron summary below. ("Daily" already requires a fixed time.)
319
+
320
+ // Weekly: fixed day-of-week and a fixed time, wildcards elsewhere.
321
+ if (timeIsFixed && dom === '*' && month === '*' && dow !== '*' && /^[\d,]+$/.test(dow)) {
322
+ const dayNumbers = dow.split(',').map(Number);
323
+ // Day-of-week is declared 0–6 (Sun–Sat) by CRON_FIELDS, and
324
+ // validateCronField already rejects a user-typed 7 for that reason — this
325
+ // component does NOT treat 7 as a Sunday alias (some cron dialects do).
326
+ // An out-of-range day here can only come from a consumer-supplied value
327
+ // that bypassed field validation; fall through to the raw cron fallback
328
+ // below rather than silently wrap it into a misleading weekday via `% 7`.
329
+ const allDaysInRange = dayNumbers.every((day) => Number.isInteger(day) && day >= 0 && day <= 6);
330
+ if (allDaysInRange) {
331
+ const names = dayNumbers.map((day) => DOW_NAMES[day] ?? '').join(', ');
332
+ return `Weekly on ${names}${at}`;
333
+ }
334
+ }
335
+ // Monthly: an in-range fixed day-of-month and a fixed time.
336
+ if (timeIsFixed && isFixedInRange(dom, 1, 31) && month === '*' && dow === '*') {
337
+ return `Monthly on day ${dom}${at}`;
338
+ }
339
+ // Daily: wildcards on day fields, fixed time.
340
+ if (dom === '*' && month === '*' && dow === '*' && timeIsFixed) {
341
+ return `Daily${at}`;
342
+ }
343
+ return `Cron: ${joinCron([minute, hour, dom, month, dow])}`;
344
+ }
@@ -0,0 +1,3 @@
1
+ const variables: readonly string[] = [];
2
+
3
+ export default variables;
@@ -28,6 +28,10 @@
28
28
  "type": "boolean",
29
29
  "description": "Allow the same trimmed tag value to appear more than once."
30
30
  },
31
+ "commitOnSubmit": {
32
+ "type": "boolean",
33
+ "description": "Commit a non-empty pending draft before the parent form submits."
34
+ },
31
35
  "autocapitalize": {
32
36
  "enum": ["off", "on", "characters", "none", "sentences", "words", null],
33
37
  "description": "Autocapitalization hint forwarded to the visible text input."
@@ -31,6 +31,10 @@ const schema = {
31
31
  type: 'boolean',
32
32
  description: 'Allow the same trimmed tag value to appear more than once.',
33
33
  },
34
+ commitOnSubmit: {
35
+ type: 'boolean',
36
+ description: 'Commit a non-empty pending draft before the parent form submits.',
37
+ },
34
38
  autocapitalize: {
35
39
  enum: ['off', 'on', 'characters', 'none', 'sentences', 'words', null],
36
40
  description: 'Autocapitalization hint forwarded to the visible text input.',
@@ -3,7 +3,7 @@ export {};
3
3
  </script>
4
4
 
5
5
  <script lang="ts">
6
- import { tick, untrack } from 'svelte';
6
+ import { flushSync, tick, untrack } from 'svelte';
7
7
  import { devWarn } from '../../utilities/dev-warn.ts';
8
8
  import { ariaInvalid, composeDescribedBy, resolveFieldControl, } from '../../_internal/field-control.ts';
9
9
  import VisuallyHiddenLiveRegion from '../_visually-hidden-live-region.svelte';
@@ -11,7 +11,7 @@ import { getFormFieldContext } from '../../_internal/form-field-context.ts';
11
11
  import { classNames } from '../../utilities/class-names.ts';
12
12
  import { handleRovingKeydown, isRovingKey } from '../../utilities/roving-tabindex.ts';
13
13
  const generatedId = $props.id();
14
- let { id, value, defaultValue, delimiter = ',', max, validate, allowDuplicates = false, disabled, readonly = false, name, class: className, oninput: consumerInput, onkeydown: consumerKeyDown, onfocus: consumerFocus, onblur: consumerBlur, 'aria-describedby': consumerDescribedBy, 'aria-label': consumerAriaLabel, 'aria-labelledby': consumerAriaLabelledBy, 'aria-invalid': consumerInvalid, required: _ignoredRequired, onchange, ...rest } = $props();
14
+ let { id, value, defaultValue, delimiter = ',', max, validate, allowDuplicates = false, commitOnSubmit = false, disabled, readonly = false, name, class: className, oninput: consumerInput, onkeydown: consumerKeyDown, onfocus: consumerFocus, onblur: consumerBlur, 'aria-describedby': consumerDescribedBy, 'aria-label': consumerAriaLabel, 'aria-labelledby': consumerAriaLabelledBy, 'aria-invalid': consumerInvalid, required: _ignoredRequired, onchange, ...rest } = $props();
15
15
  const initialDefaultTags = untrack(() => [...(defaultValue ?? [])]);
16
16
  const context = getFormFieldContext();
17
17
  let rootElement = $state(null);
@@ -92,6 +92,19 @@ $effect(() => {
92
92
  const form = inputElement.closest('form');
93
93
  if (!form)
94
94
  return;
95
+ const onSubmit = (event) => {
96
+ if (!commitOnSubmit || field.disabled || resolvedReadonly)
97
+ return;
98
+ if (!draftValue.trim())
99
+ return;
100
+ let committed = false;
101
+ flushSync(() => {
102
+ committed = commitDraft();
103
+ });
104
+ if (!committed) {
105
+ event.preventDefault();
106
+ }
107
+ };
95
108
  const onReset = () => {
96
109
  draftValue = '';
97
110
  inlineError = null;
@@ -100,8 +113,12 @@ $effect(() => {
100
113
  uncontrolledTags = [...initialDefaultTags];
101
114
  }
102
115
  };
116
+ form.addEventListener('submit', onSubmit, true);
103
117
  form.addEventListener('reset', onReset);
104
- return () => form.removeEventListener('reset', onReset);
118
+ return () => {
119
+ form.removeEventListener('submit', onSubmit, true);
120
+ form.removeEventListener('reset', onReset);
121
+ };
105
122
  });
106
123
  function getChipElements() {
107
124
  // The focusable, keyboard-removable element is the remove <button> — the
@@ -34,6 +34,8 @@ export type TagInputProps = SupportedInputAttributes & {
34
34
  validate?: (tag: string) => boolean | string;
35
35
  /** Allow the same trimmed tag value to appear more than once. */
36
36
  allowDuplicates?: boolean;
37
+ /** Commit a non-empty pending draft before the parent form submits. */
38
+ commitOnSubmit?: boolean;
37
39
  /** Disable the input and chip removal affordances. */
38
40
  disabled?: boolean;
39
41
  /** Render the pending-tag input as read-only and make committed tags non-removable. */
@@ -59,6 +61,8 @@ export interface TagInputSchemaProps {
59
61
  delimiter?: string | RegExp;
60
62
  /** Allow the same trimmed tag value to appear more than once. */
61
63
  allowDuplicates?: boolean;
64
+ /** Commit a non-empty pending draft before the parent form submits. */
65
+ commitOnSubmit?: boolean;
62
66
  /** Autocomplete hint forwarded to the visible text input. */
63
67
  autocomplete?: HTMLInputAttributes['autocomplete'];
64
68
  /** Autocapitalization hint forwarded to the visible text input. */
package/src/index.ts CHANGED
@@ -257,6 +257,12 @@ export type { CommandPaletteProps } from './components/command-palette/index.ts'
257
257
  export { default as ConfirmDialog } from './components/confirm-dialog/index.ts';
258
258
  export type { ConfirmDialogProps } from './components/confirm-dialog/index.ts';
259
259
 
260
+ export { default as ConnectionIndicator } from './components/connection-indicator/index.ts';
261
+ export type {
262
+ ConnectionIndicatorProps,
263
+ ConnectionIndicatorStatus,
264
+ } from './components/connection-indicator/index.ts';
265
+
260
266
  export { default as Container } from './components/container/index.ts';
261
267
  export type {
262
268
  ContainerElement,
@@ -504,10 +510,14 @@ export { default as InvocationRuleBuilder } from './components/invocation-rule-b
504
510
  export type {
505
511
  InvocationRule,
506
512
  InvocationRuleAction,
513
+ InvocationRuleBuilderMode,
507
514
  InvocationRuleBuilderProps,
508
515
  InvocationRuleChange,
509
516
  InvocationRuleCondition,
517
+ InvocationRuleConditionsOnlyOperator,
518
+ InvocationRuleFieldType,
510
519
  InvocationRuleOption,
520
+ InvocationRuleValueChoice,
511
521
  } from './components/invocation-rule-builder/index.ts';
512
522
 
513
523
  export { default as JsonViewer } from './components/json-viewer/index.ts';
@@ -696,9 +706,13 @@ export type { RatingPrecision, RatingProps } from './components/rating/index.ts'
696
706
  export { default as RunStepTimeline } from './components/run-step-timeline/index.ts';
697
707
  export type {
698
708
  RunStep,
709
+ RunStepBranchGroup,
710
+ RunStepBranchLane,
711
+ RunStepBranchLaneOutcome,
699
712
  RunStepDetail,
700
713
  RunStepLink,
701
714
  RunStepStatus,
715
+ RunStepTimelineEntry,
702
716
  RunStepTimelineProps,
703
717
  } from './components/run-step-timeline/index.ts';
704
718
 
@@ -715,6 +729,15 @@ export type {
715
729
  ResizablePanelsResizeReason,
716
730
  } from './components/resizable-panels/index.ts';
717
731
 
732
+ export { default as ScheduleBuilder } from './components/schedule-builder/index.ts';
733
+ export type {
734
+ ScheduleAuthoringMode,
735
+ ScheduleBuilderProps,
736
+ ScheduleFire,
737
+ ScheduleIntervalUnit,
738
+ ScheduleValue,
739
+ } from './components/schedule-builder/index.ts';
740
+
718
741
  export { default as SchemaForm, readSchemaFormData } from './components/schema-form/index.ts';
719
742
  export type {
720
743
  SchemaFormOutput,
@@ -53,6 +53,7 @@
53
53
  @import '../components/command-menu/command-menu.css';
54
54
  @import '../components/command-palette/command-palette.css';
55
55
  @import '../components/confirm-dialog/confirm-dialog.css';
56
+ @import '../components/connection-indicator/connection-indicator.css';
56
57
  @import '../components/container/container.css';
57
58
  @import '../components/context-menu/context-menu.css';
58
59
  @import '../components/copy-button/copy-button.css';
@@ -129,6 +130,7 @@
129
130
  @import '../components/resizable-panels/resizable-panels.css';
130
131
  @import '../components/run-step-timeline/run-step-timeline.css';
131
132
  @import '../components/review-editor/review-editor.css';
133
+ @import '../components/schedule-builder/schedule-builder.css';
132
134
  @import '../components/schema-form/schema-form.css';
133
135
  @import '../components/scroll-area/scroll-area.css';
134
136
  @import '../components/search-field/search-field.css';