@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.
- package/CHANGELOG.md +26 -0
- package/components.json +82 -4
- package/dist/components/access-gate/index.js +381 -239
- package/dist/components/chat/builders.d.ts +17 -8
- package/dist/components/chat/conversation-model.d.ts +1 -1
- package/dist/components/chat/index.d.ts +1 -1
- package/dist/components/chat/index.js +300 -17
- package/dist/components/chat/utilities/conversation.d.ts +6 -6
- package/dist/components/chat-conversation-header/index.js +19 -6
- package/dist/components/chat-conversation-list/index.js +19 -6
- package/dist/components/code-block/code-block.css +15 -9
- package/dist/components/code-block/code-block.variables.js +8 -2
- package/dist/components/confirm-dialog/confirm-dialog.css +2 -0
- package/dist/components/confirm-dialog/confirm-dialog.schema.js +10 -1
- package/dist/components/confirm-dialog/confirm-dialog.types.d.ts +7 -0
- package/dist/components/confirm-dialog/index.js +478 -106
- package/dist/components/connection-indicator/connection-indicator.css +136 -0
- package/dist/components/connection-indicator/connection-indicator.css.d.ts +1 -0
- package/dist/components/connection-indicator/connection-indicator.schema.d.ts +3 -0
- package/dist/components/connection-indicator/connection-indicator.schema.js +89 -0
- package/dist/components/connection-indicator/connection-indicator.svelte.d.ts +21 -0
- package/dist/components/connection-indicator/connection-indicator.types.d.ts +46 -0
- package/dist/components/connection-indicator/connection-indicator.variables.d.ts +2 -0
- package/dist/components/connection-indicator/connection-indicator.variables.js +61 -0
- package/dist/components/connection-indicator/index.d.ts +5 -0
- package/dist/components/connection-indicator/index.js +409 -0
- package/dist/components/container/container.schema.js +4 -4
- package/dist/components/faceted-filter-bar/faceted-filter-bar.schema.js +5 -1
- package/dist/components/faceted-filter-bar/faceted-filter-bar.types.d.ts +2 -0
- package/dist/components/faceted-filter-bar/index.js +40 -31
- package/dist/components/invocation-rule-builder/index.d.ts +1 -1
- package/dist/components/invocation-rule-builder/index.js +305 -150
- package/dist/components/invocation-rule-builder/invocation-rule-builder.css +28 -0
- package/dist/components/invocation-rule-builder/invocation-rule-builder.schema.js +89 -5
- package/dist/components/invocation-rule-builder/invocation-rule-builder.svelte.d.ts +2 -1
- package/dist/components/invocation-rule-builder/invocation-rule-builder.types.d.ts +114 -16
- package/dist/components/navigation-bar/index.js +11 -17
- package/dist/components/run-step-timeline/index.d.ts +1 -1
- package/dist/components/run-step-timeline/index.js +1052 -560
- package/dist/components/run-step-timeline/run-step-branch-disclosure.svelte.d.ts +17 -0
- package/dist/components/run-step-timeline/run-step-timeline.css +124 -0
- package/dist/components/run-step-timeline/run-step-timeline.schema.js +873 -350
- package/dist/components/run-step-timeline/run-step-timeline.svelte.d.ts +1 -15
- package/dist/components/run-step-timeline/run-step-timeline.types.d.ts +182 -5
- package/dist/components/run-step-timeline/run-step-timeline.utilities.d.ts +60 -0
- package/dist/components/schedule-builder/index.d.ts +5 -0
- package/dist/components/schedule-builder/index.js +3176 -0
- package/dist/components/schedule-builder/schedule-builder.css +121 -0
- package/dist/components/schedule-builder/schedule-builder.css.d.ts +1 -0
- package/dist/components/schedule-builder/schedule-builder.schema.d.ts +3 -0
- package/dist/components/schedule-builder/schedule-builder.schema.js +141 -0
- package/dist/components/schedule-builder/schedule-builder.svelte.d.ts +22 -0
- package/dist/components/schedule-builder/schedule-builder.types.d.ts +130 -0
- package/dist/components/schedule-builder/schedule-builder.utilities.d.ts +78 -0
- package/dist/components/schedule-builder/schedule-builder.variables.d.ts +2 -0
- package/dist/components/schedule-builder/schedule-builder.variables.js +61 -0
- package/dist/components/tag-input/index.js +22 -4
- package/dist/components/tag-input/tag-input.schema.js +5 -1
- package/dist/components/tag-input/tag-input.types.d.ts +4 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +23069 -20833
- package/dist/server/components/access-gate/index.js +2 -1
- package/dist/server/components/chat/index.js +4 -4
- package/dist/server/components/chat-conversation-header/index.js +5 -5
- package/dist/server/components/chat-conversation-list/index.js +3 -3
- package/dist/server/components/color-field/index.js +2 -2
- package/dist/server/components/confirm-dialog/index.js +7 -2
- package/dist/server/components/connection-indicator/index.js +16 -0
- package/dist/server/components/data-table/index.js +3 -3
- package/dist/server/components/diff-viewer/index.js +4 -3
- package/dist/server/components/faceted-filter-bar/index.js +1 -1
- package/dist/server/components/invocation-rule-builder/index.js +1 -1
- package/dist/server/components/json-schema-editor/index.js +4 -3
- package/dist/server/components/navigation-bar/index.js +1 -1
- package/dist/server/components/review-editor/index.js +4 -3
- package/dist/server/components/run-step-timeline/index.js +1 -1
- package/dist/server/components/schedule-builder/index.js +30 -0
- package/dist/server/components/side-navigation/index.js +2 -2
- package/dist/server/components/table/index.js +3 -3
- package/dist/server/components/tag-input/index.js +1 -1
- package/dist/server/components/time-field/index.js +5 -3
- package/dist/server/index.js +105 -92
- package/dist/server/index.server-0sx92swg.js +9 -0
- package/dist/server/{index.server-mzzb7dnm.js → index.server-2bbwajb2.js} +1 -1
- package/dist/server/{index.server-q7w4jw89.js → index.server-2s5h1d05.js} +2 -2
- package/dist/server/{index.server-dg83ke3k.js → index.server-2zdbz3rj.js} +1 -1
- package/dist/server/{index.server-0jhra85x.js → index.server-3w72dxz7.js} +4 -4
- package/dist/server/{index.server-5tf22d6e.js → index.server-4wx0qven.js} +3 -3
- package/dist/server/{index.server-qh9qzd5t.js → index.server-4x4hgvqe.js} +283 -13
- package/dist/server/{index.server-c6pe4kaf.js → index.server-5gzyqv78.js} +1 -1
- package/dist/server/{index.server-mqt1ngqw.js → index.server-6hnfzc7f.js} +19 -6
- package/dist/server/{index.server-894t97kv.js → index.server-7yvpb6z7.js} +4 -4
- package/dist/server/index.server-be9bpqeh.js +93 -0
- package/dist/server/{index.server-f3z7tpxr.js → index.server-c42b462p.js} +2 -4
- package/dist/server/{index.server-7vyzen5x.js → index.server-c5qv3p61.js} +171 -63
- package/dist/server/index.server-cch0eed5.js +144 -0
- package/dist/server/{index.server-btgb3d56.js → index.server-d5a5ccz7.js} +18 -12
- package/dist/server/index.server-g5jgq3jp.js +793 -0
- package/dist/server/index.server-gmh5ar16.js +2 -0
- package/dist/server/{index.server-0atyznp7.js → index.server-h89zk5ev.js} +7 -7
- package/dist/server/{index.server-jxg42jpz.js → index.server-jdcq5zza.js} +7 -7
- package/dist/server/{index.server-2dga5yzr.js → index.server-jfrssta6.js} +1 -1
- package/dist/server/{index.server-fy068yne.js → index.server-jj76374p.js} +1 -1
- package/dist/server/{index.server-39ne5cd1.js → index.server-pxq277t5.js} +32 -1
- package/dist/server/index.server-qbkxz7r2.js +750 -0
- package/dist/server/{index.server-marjz8ps.js → index.server-tcay903b.js} +3 -2
- package/dist/server/{index.server-yz4k36cw.js → index.server-z7hxtqs1.js} +1 -1
- package/package.json +87 -97
- package/src/components/chat/builders.ts +445 -14
- package/src/components/chat/conversation-model.ts +1 -0
- package/src/components/chat/index.ts +1 -0
- package/src/components/chat/utilities/conversation.ts +33 -15
- package/src/components/code-block/code-block.css +15 -9
- package/src/components/code-block/code-block.variables.json +7 -1
- package/src/components/code-block/code-block.variables.ts +7 -1
- package/src/components/confirm-dialog/confirm-dialog.css +2 -0
- package/src/components/confirm-dialog/confirm-dialog.schema.json +8 -0
- package/src/components/confirm-dialog/confirm-dialog.schema.ts +10 -0
- package/src/components/confirm-dialog/confirm-dialog.svelte +31 -3
- package/src/components/confirm-dialog/confirm-dialog.types.ts +7 -0
- package/src/components/connection-indicator/connection-indicator.css +136 -0
- package/src/components/connection-indicator/connection-indicator.examples.json +31 -0
- package/src/components/connection-indicator/connection-indicator.schema.json +29 -0
- package/src/components/connection-indicator/connection-indicator.schema.ts +36 -0
- package/src/components/connection-indicator/connection-indicator.svelte +63 -0
- package/src/components/connection-indicator/connection-indicator.types.ts +58 -0
- package/src/components/connection-indicator/connection-indicator.variables.json +1 -0
- package/src/components/connection-indicator/connection-indicator.variables.ts +3 -0
- package/src/components/connection-indicator/index.ts +10 -0
- package/src/components/container/container.schema.json +3 -3
- package/src/components/container/container.schema.ts +3 -3
- package/src/components/faceted-filter-bar/faceted-filter-bar.schema.json +4 -0
- package/src/components/faceted-filter-bar/faceted-filter-bar.schema.ts +4 -0
- package/src/components/faceted-filter-bar/faceted-filter-bar.svelte +13 -11
- package/src/components/faceted-filter-bar/faceted-filter-bar.types.ts +2 -0
- package/src/components/invocation-rule-builder/index.ts +4 -0
- package/src/components/invocation-rule-builder/invocation-rule-builder.css +28 -0
- package/src/components/invocation-rule-builder/invocation-rule-builder.examples.json +6 -0
- package/src/components/invocation-rule-builder/invocation-rule-builder.schema.json +76 -3
- package/src/components/invocation-rule-builder/invocation-rule-builder.schema.ts +83 -3
- package/src/components/invocation-rule-builder/invocation-rule-builder.svelte +326 -113
- package/src/components/invocation-rule-builder/invocation-rule-builder.types.ts +157 -43
- package/src/components/navigation-bar/navigation-bar.svelte +2 -2
- package/src/components/run-step-timeline/index.ts +8 -0
- package/src/components/run-step-timeline/run-step-branch-disclosure.svelte +16 -0
- package/src/components/run-step-timeline/run-step-timeline.css +124 -0
- package/src/components/run-step-timeline/run-step-timeline.examples.json +18 -0
- package/src/components/run-step-timeline/run-step-timeline.schema.json +872 -349
- package/src/components/run-step-timeline/run-step-timeline.schema.ts +931 -349
- package/src/components/run-step-timeline/run-step-timeline.svelte +388 -269
- package/src/components/run-step-timeline/run-step-timeline.types.ts +192 -5
- package/src/components/run-step-timeline/run-step-timeline.utilities.ts +310 -0
- package/src/components/schedule-builder/index.ts +14 -0
- package/src/components/schedule-builder/schedule-builder.css +121 -0
- package/src/components/schedule-builder/schedule-builder.examples.json +31 -0
- package/src/components/schedule-builder/schedule-builder.schema.json +78 -0
- package/src/components/schedule-builder/schedule-builder.schema.ts +86 -0
- package/src/components/schedule-builder/schedule-builder.svelte +629 -0
- package/src/components/schedule-builder/schedule-builder.types.ts +137 -0
- package/src/components/schedule-builder/schedule-builder.utilities.ts +344 -0
- package/src/components/schedule-builder/schedule-builder.variables.json +1 -0
- package/src/components/schedule-builder/schedule-builder.variables.ts +3 -0
- package/src/components/tag-input/tag-input.schema.json +4 -0
- package/src/components/tag-input/tag-input.schema.ts +4 -0
- package/src/components/tag-input/tag-input.svelte +20 -3
- package/src/components/tag-input/tag-input.types.ts +4 -0
- package/src/index.ts +23 -0
- package/src/styles/components.css +2 -0
- package/dist/server/index.server-qk1h0n02.js +0 -393
|
@@ -5,10 +5,150 @@ export {};
|
|
|
5
5
|
<script lang="ts">
|
|
6
6
|
import { tick } from 'svelte';
|
|
7
7
|
import { classNames } from '../../utilities/class-names.ts';
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Fixed operator vocabulary for conditions-only mode (`mode="conditions"`).
|
|
10
|
+
* Cinder supplies these with default labels; the `operatorOptions` prop is
|
|
11
|
+
* not accepted in this mode.
|
|
12
|
+
*/
|
|
13
|
+
const CONDITIONS_ONLY_OPERATOR_OPTIONS = [
|
|
14
|
+
{ value: 'eq', label: 'equals' },
|
|
15
|
+
{ value: 'gt', label: 'greater than' },
|
|
16
|
+
{ value: 'lt', label: 'less than' },
|
|
17
|
+
{ value: 'gte', label: 'greater than or equal' },
|
|
18
|
+
{ value: 'lte', label: 'less than or equal' },
|
|
19
|
+
];
|
|
20
|
+
let { rules, onchange, fieldOptions, operatorOptions, actionOptions, mode = 'full', readonly = false, addRuleLabel = 'Add rule', addConditionLabel = 'Add condition', addActionLabel = 'Add action', label, class: className, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, ...rest } = $props();
|
|
9
21
|
const baseId = $props.id();
|
|
10
22
|
const sectionAriaLabel = $derived(ariaLabelledby ? undefined : (ariaLabel ?? label ?? 'Invocation rules'));
|
|
11
23
|
const effectiveReadonly = $derived(readonly || onchange === undefined);
|
|
24
|
+
const conditionsOnly = $derived(mode === 'conditions');
|
|
25
|
+
/**
|
|
26
|
+
* The operator options actually rendered and used for new conditions.
|
|
27
|
+
* Fixed in conditions-only mode; consumer-supplied in full mode.
|
|
28
|
+
*/
|
|
29
|
+
const resolvedOperatorOptions = $derived(conditionsOnly ? CONDITIONS_ONLY_OPERATOR_OPTIONS : (operatorOptions ?? []));
|
|
30
|
+
const resolvedActionOptions = $derived(actionOptions ?? []);
|
|
31
|
+
/** The declared value type for a field, defaulting to `'string'`. */
|
|
32
|
+
function fieldValueType(fieldValue) {
|
|
33
|
+
return fieldOptions.find((option) => option.value === fieldValue)?.type ?? 'string';
|
|
34
|
+
}
|
|
35
|
+
/** Enum choices declared on a field option, or an empty list. */
|
|
36
|
+
function fieldEnumOptions(fieldValue) {
|
|
37
|
+
return fieldOptions.find((option) => option.value === fieldValue)?.options ?? [];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The type-appropriate default `value` for a field in conditions-only mode —
|
|
41
|
+
* used both when a condition is first added and when its `field` changes to
|
|
42
|
+
* one of a different (or enum-incompatible) type, so the stored value always
|
|
43
|
+
* matches what the typed control actually renders.
|
|
44
|
+
*/
|
|
45
|
+
function defaultConditionValue(fieldValue) {
|
|
46
|
+
const type = fieldValueType(fieldValue);
|
|
47
|
+
switch (type) {
|
|
48
|
+
case 'boolean':
|
|
49
|
+
// Matches the checkbox's own read: `checked={condition.value === 'true'}`.
|
|
50
|
+
return 'false';
|
|
51
|
+
case 'enum':
|
|
52
|
+
return fieldEnumOptions(fieldValue)[0]?.value ?? '';
|
|
53
|
+
case 'number':
|
|
54
|
+
case 'string':
|
|
55
|
+
return '';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A number-LIKE string — permissive enough to accept every intermediate
|
|
60
|
+
* state a user legitimately passes through while typing into a native
|
|
61
|
+
* `<input type="number">` (a lone `-`, a trailing `.` as in `'1.'`, a
|
|
62
|
+
* partial exponent like `'1e'` or `'1e5'`, `'-2.5'`, and so on), so
|
|
63
|
+
* `emitChange` — which runs on every value edit, not just field changes —
|
|
64
|
+
* never fights live typing by blanking a value mid-keystroke.
|
|
65
|
+
*
|
|
66
|
+
* It still rejects strings a number input would never produce and that
|
|
67
|
+
* only arrive as stale prop data: hex (`'0x10'`), units (`'5px'`), plain
|
|
68
|
+
* words (`'foo'`), exponent-only fragments with no mantissa (`'e'`, `'+e'`,
|
|
69
|
+
* `'e+'`), or whitespace mixed with letters. The first alternative covers a
|
|
70
|
+
* digit-bearing number with an optional exponent; the second covers the bare
|
|
71
|
+
* sign/decimal intermediates (`''`, `'-'`, `'+'`, `'.'`, `'-.'`) that a number
|
|
72
|
+
* input surfaces mid-typing — an exponent is only ever valid glued to a
|
|
73
|
+
* mantissa, never on its own.
|
|
74
|
+
*/
|
|
75
|
+
const NUMBER_LIKE_VALUE_PATTERN = /^([+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d*)?|[+-]?\.?)$/i;
|
|
76
|
+
/**
|
|
77
|
+
* Whether a stored condition value is still a faithful representation of
|
|
78
|
+
* what the typed control for `type` would render — i.e. whether it's safe
|
|
79
|
+
* to keep across a field change instead of resetting to the type's default.
|
|
80
|
+
*/
|
|
81
|
+
function isValueValidForFieldType(value, type, fieldValue) {
|
|
82
|
+
switch (type) {
|
|
83
|
+
case 'boolean':
|
|
84
|
+
return value === 'true' || value === 'false';
|
|
85
|
+
case 'enum':
|
|
86
|
+
return fieldEnumOptions(fieldValue).some((option) => option.value === value);
|
|
87
|
+
case 'number':
|
|
88
|
+
// Blank is allowed; otherwise a number-like string is valid — lenient
|
|
89
|
+
// enough to not fight live typing, strict enough to still reject stale
|
|
90
|
+
// non-numeric data like hex or unit-suffixed strings. A whitespace-only
|
|
91
|
+
// value renders blank in the number input, so it is treated as invalid
|
|
92
|
+
// (normalized to '') rather than persisting the hidden spaces.
|
|
93
|
+
return (value === '' || (value.trim() !== '' && NUMBER_LIKE_VALUE_PATTERN.test(value.trim())));
|
|
94
|
+
case 'string':
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Whether `operator` is one of the fixed conditions-only mode operators. */
|
|
99
|
+
function isConditionsOnlyOperator(operator) {
|
|
100
|
+
return CONDITIONS_ONLY_OPERATOR_OPTIONS.some((option) => option.value === operator);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The operator to display for a condition — in the readonly summary label
|
|
104
|
+
* and as the selected value of the editable operator `<select>`. In full
|
|
105
|
+
* mode this is always the raw stored operator. In conditions-only mode, a
|
|
106
|
+
* rule can arrive carrying a legacy operator outside the fixed
|
|
107
|
+
* eq/gt/lt/gte/lte set (e.g. a leftover `'matches'` from full mode); this
|
|
108
|
+
* displays `'eq'` in that case instead of an operator the mode forbids —
|
|
109
|
+
* matching what `emitChange` would coerce it to on the next edit, and
|
|
110
|
+
* keeping the operator `<select>` always showing a valid selected option.
|
|
111
|
+
*/
|
|
112
|
+
function displayOperator(operator) {
|
|
113
|
+
return conditionsOnly && !isConditionsOnlyOperator(operator) ? 'eq' : operator;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Emits `onchange`. In conditions-only mode, normalizes every emitted rule
|
|
117
|
+
* first so it's internally consistent regardless of what the incoming
|
|
118
|
+
* `rules` prop originally carried (e.g. a consumer switching existing
|
|
119
|
+
* full-mode rules into conditions-only mode):
|
|
120
|
+
*
|
|
121
|
+
* - Strips `actions` — conditions-only rules never carry an action target.
|
|
122
|
+
* - Coerces any condition `operator` outside the fixed eq/gt/lt/gte/lte set
|
|
123
|
+
* to `'eq'` (e.g. a leftover full-mode operator like `'matches'`).
|
|
124
|
+
* - Coerces any condition `value` that isn't valid for its field's
|
|
125
|
+
* inferred type to that type's default (e.g. a boolean field whose value
|
|
126
|
+
* is an arbitrary string, or an enum field whose value isn't one of its
|
|
127
|
+
* choices) — the same check and defaults used for field-type changes.
|
|
128
|
+
*
|
|
129
|
+
* Full mode passes rules through unchanged.
|
|
130
|
+
*/
|
|
131
|
+
function emitChange(nextRules, change) {
|
|
132
|
+
const rulesToEmit = conditionsOnly
|
|
133
|
+
? nextRules.map((rule) => ({
|
|
134
|
+
...rule,
|
|
135
|
+
actions: [],
|
|
136
|
+
conditions: rule.conditions.map((condition) => {
|
|
137
|
+
const operator = isConditionsOnlyOperator(condition.operator)
|
|
138
|
+
? condition.operator
|
|
139
|
+
: 'eq';
|
|
140
|
+
const type = fieldValueType(condition.field);
|
|
141
|
+
const value = isValueValidForFieldType(condition.value, type, condition.field)
|
|
142
|
+
? condition.value
|
|
143
|
+
: defaultConditionValue(condition.field);
|
|
144
|
+
return operator === condition.operator && value === condition.value
|
|
145
|
+
? condition
|
|
146
|
+
: { ...condition, operator, value };
|
|
147
|
+
}),
|
|
148
|
+
}))
|
|
149
|
+
: nextRules;
|
|
150
|
+
onchange?.(rulesToEmit, change);
|
|
151
|
+
}
|
|
12
152
|
/** Announcement text for the live region. */
|
|
13
153
|
let announcement = $state('');
|
|
14
154
|
let ruleLabelDrafts = $state({});
|
|
@@ -39,13 +179,13 @@ function handleAddRule() {
|
|
|
39
179
|
{ id: ruleId, label: `Rule ${rules.length + 1}`, conditions: [], actions: [] },
|
|
40
180
|
];
|
|
41
181
|
const change = { type: 'add-rule', ruleId };
|
|
42
|
-
|
|
182
|
+
emitChange(nextRules, change);
|
|
43
183
|
announce('Rule added.');
|
|
44
184
|
}
|
|
45
185
|
function handleRemoveRule(ruleId, ruleLabel, ruleIndex) {
|
|
46
186
|
const nextRules = rules.filter((rule) => rule.id !== ruleId);
|
|
47
187
|
const change = { type: 'remove-rule', ruleId };
|
|
48
|
-
|
|
188
|
+
emitChange(nextRules, change);
|
|
49
189
|
announce(`${ruleLabel} removed.`);
|
|
50
190
|
// Move focus to the previous rule's remove button, or the add-rule button.
|
|
51
191
|
tick().then(() => {
|
|
@@ -64,7 +204,7 @@ function handleMoveRule(ruleId, fromIndex, direction) {
|
|
|
64
204
|
const [moved] = nextRules.splice(fromIndex, 1);
|
|
65
205
|
nextRules.splice(toIndex, 0, moved);
|
|
66
206
|
const change = { type: 'move-rule', ruleId, fromIndex, toIndex };
|
|
67
|
-
|
|
207
|
+
emitChange(nextRules, change);
|
|
68
208
|
announce(`${moved.label} moved to position ${toIndex + 1} of ${nextRules.length}.`);
|
|
69
209
|
}
|
|
70
210
|
function handleRenameRule(ruleId, label) {
|
|
@@ -76,7 +216,7 @@ function handleRenameRule(ruleId, label) {
|
|
|
76
216
|
return;
|
|
77
217
|
const nextRules = updateRules(ruleId, (rule) => ({ ...rule, label: nextLabel }));
|
|
78
218
|
const change = { type: 'rename-rule', ruleId };
|
|
79
|
-
|
|
219
|
+
emitChange(nextRules, change);
|
|
80
220
|
}
|
|
81
221
|
function ruleLabelDraft(rule) {
|
|
82
222
|
const draft = ruleLabelDrafts[rule.id];
|
|
@@ -88,16 +228,20 @@ function ruleLabelDraft(rule) {
|
|
|
88
228
|
function handleAddCondition(ruleId) {
|
|
89
229
|
const conditionId = generateId();
|
|
90
230
|
const firstField = fieldOptions[0]?.value ?? '';
|
|
91
|
-
const firstOperator =
|
|
231
|
+
const firstOperator = resolvedOperatorOptions[0]?.value ?? '';
|
|
232
|
+
// In conditions-only mode, seed a type-appropriate default (e.g. 'false'
|
|
233
|
+
// for a boolean field, the first choice for an enum field) so the stored
|
|
234
|
+
// value matches what the typed control renders immediately on add.
|
|
235
|
+
const initialValue = conditionsOnly ? defaultConditionValue(firstField) : '';
|
|
92
236
|
const nextRules = updateRules(ruleId, (rule) => ({
|
|
93
237
|
...rule,
|
|
94
238
|
conditions: [
|
|
95
239
|
...rule.conditions,
|
|
96
|
-
{ id: conditionId, field: firstField, operator: firstOperator, value:
|
|
240
|
+
{ id: conditionId, field: firstField, operator: firstOperator, value: initialValue },
|
|
97
241
|
],
|
|
98
242
|
}));
|
|
99
243
|
const change = { type: 'add-condition', ruleId, conditionId };
|
|
100
|
-
|
|
244
|
+
emitChange(nextRules, change);
|
|
101
245
|
announce('Condition added.');
|
|
102
246
|
}
|
|
103
247
|
function handleRemoveCondition(ruleId, conditionId, conditionIndex, ruleIndex) {
|
|
@@ -106,7 +250,7 @@ function handleRemoveCondition(ruleId, conditionId, conditionIndex, ruleIndex) {
|
|
|
106
250
|
conditions: rule.conditions.filter((condition) => condition.id !== conditionId),
|
|
107
251
|
}));
|
|
108
252
|
const change = { type: 'remove-condition', ruleId, conditionId };
|
|
109
|
-
|
|
253
|
+
emitChange(nextRules, change);
|
|
110
254
|
announce('Condition removed.');
|
|
111
255
|
tick().then(() => {
|
|
112
256
|
const ruleEl = document.querySelector(`[data-irb-region="${baseId}"] [data-irb-rule="${ruleIndex}"]`);
|
|
@@ -119,7 +263,25 @@ function handleRemoveCondition(ruleId, conditionId, conditionIndex, ruleIndex) {
|
|
|
119
263
|
function handleUpdateCondition(ruleId, conditionId, field, value) {
|
|
120
264
|
const nextRules = updateRules(ruleId, (rule) => ({
|
|
121
265
|
...rule,
|
|
122
|
-
conditions: rule.conditions.map((condition) =>
|
|
266
|
+
conditions: rule.conditions.map((condition) => {
|
|
267
|
+
if (condition.id !== conditionId)
|
|
268
|
+
return condition;
|
|
269
|
+
// Changing which field a condition tests, in conditions-only mode: if
|
|
270
|
+
// the current value is no longer a faithful representation of the new
|
|
271
|
+
// field's type (e.g. an arbitrary string against a field that's now
|
|
272
|
+
// boolean, or a value outside a new enum field's choices), reset it to
|
|
273
|
+
// that type's default so the stored value matches the typed control
|
|
274
|
+
// that's about to render. Same-type (and enum-with-matching-value)
|
|
275
|
+
// field changes keep the value the user already entered.
|
|
276
|
+
if (field === 'field' && conditionsOnly) {
|
|
277
|
+
const nextType = fieldValueType(value);
|
|
278
|
+
const nextValue = isValueValidForFieldType(condition.value, nextType, value)
|
|
279
|
+
? condition.value
|
|
280
|
+
: defaultConditionValue(value);
|
|
281
|
+
return { ...condition, field: value, value: nextValue };
|
|
282
|
+
}
|
|
283
|
+
return { ...condition, [field]: value };
|
|
284
|
+
}),
|
|
123
285
|
}));
|
|
124
286
|
const change = {
|
|
125
287
|
type: 'update-condition',
|
|
@@ -127,20 +289,20 @@ function handleUpdateCondition(ruleId, conditionId, field, value) {
|
|
|
127
289
|
conditionId,
|
|
128
290
|
field,
|
|
129
291
|
};
|
|
130
|
-
|
|
292
|
+
emitChange(nextRules, change);
|
|
131
293
|
}
|
|
132
294
|
// ---------------------------------------------------------------------------
|
|
133
295
|
// Action handlers
|
|
134
296
|
// ---------------------------------------------------------------------------
|
|
135
297
|
function handleAddAction(ruleId) {
|
|
136
298
|
const actionId = generateId();
|
|
137
|
-
const firstTarget =
|
|
299
|
+
const firstTarget = resolvedActionOptions[0]?.value ?? '';
|
|
138
300
|
const nextRules = updateRules(ruleId, (rule) => ({
|
|
139
301
|
...rule,
|
|
140
302
|
actions: [...rule.actions, { id: actionId, target: firstTarget }],
|
|
141
303
|
}));
|
|
142
304
|
const change = { type: 'add-action', ruleId, actionId };
|
|
143
|
-
|
|
305
|
+
emitChange(nextRules, change);
|
|
144
306
|
announce('Action added.');
|
|
145
307
|
}
|
|
146
308
|
function handleRemoveAction(ruleId, actionId, actionIndex, ruleIndex) {
|
|
@@ -149,7 +311,7 @@ function handleRemoveAction(ruleId, actionId, actionIndex, ruleIndex) {
|
|
|
149
311
|
actions: rule.actions.filter((action) => action.id !== actionId),
|
|
150
312
|
}));
|
|
151
313
|
const change = { type: 'remove-action', ruleId, actionId };
|
|
152
|
-
|
|
314
|
+
emitChange(nextRules, change);
|
|
153
315
|
announce('Action removed.');
|
|
154
316
|
tick().then(() => {
|
|
155
317
|
const ruleEl = document.querySelector(`[data-irb-region="${baseId}"] [data-irb-rule="${ruleIndex}"]`);
|
|
@@ -165,7 +327,7 @@ function handleUpdateAction(ruleId, actionId, target) {
|
|
|
165
327
|
actions: rule.actions.map((action) => action.id === actionId ? { ...action, target } : action),
|
|
166
328
|
}));
|
|
167
329
|
const change = { type: 'update-action', ruleId, actionId };
|
|
168
|
-
|
|
330
|
+
emitChange(nextRules, change);
|
|
169
331
|
}
|
|
170
332
|
// ---------------------------------------------------------------------------
|
|
171
333
|
// Label helpers
|
|
@@ -174,10 +336,11 @@ function fieldLabel(value) {
|
|
|
174
336
|
return fieldOptions.find((option) => option.value === value)?.label ?? value;
|
|
175
337
|
}
|
|
176
338
|
function operatorLabel(value) {
|
|
177
|
-
|
|
339
|
+
const displayValue = displayOperator(value);
|
|
340
|
+
return (resolvedOperatorOptions.find((option) => option.value === displayValue)?.label ?? displayValue);
|
|
178
341
|
}
|
|
179
342
|
function actionTargetLabel(value) {
|
|
180
|
-
return
|
|
343
|
+
return resolvedActionOptions.find((option) => option.value === value)?.label ?? value;
|
|
181
344
|
}
|
|
182
345
|
function conditionSummary(condition) {
|
|
183
346
|
return `${fieldLabel(condition.field)} ${operatorLabel(condition.operator)} "${condition.value}"`;
|
|
@@ -324,7 +487,7 @@ function actionSummary(action) {
|
|
|
324
487
|
<select
|
|
325
488
|
class="cinder-invocation-rule-builder__condition-select"
|
|
326
489
|
aria-label={`Operator for condition ${conditionIndex + 1} of ${rule.label}`}
|
|
327
|
-
value={condition.operator}
|
|
490
|
+
value={displayOperator(condition.operator)}
|
|
328
491
|
onchange={(event) =>
|
|
329
492
|
handleUpdateCondition(
|
|
330
493
|
rule.id,
|
|
@@ -333,25 +496,73 @@ function actionSummary(action) {
|
|
|
333
496
|
(event.target as HTMLSelectElement).value,
|
|
334
497
|
)}
|
|
335
498
|
>
|
|
336
|
-
{#each
|
|
499
|
+
{#each resolvedOperatorOptions as option (option.value)}
|
|
337
500
|
<option value={option.value}>{option.label}</option>
|
|
338
501
|
{/each}
|
|
339
502
|
</select>
|
|
340
503
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
504
|
+
{#if conditionsOnly && fieldValueType(condition.field) === 'number'}
|
|
505
|
+
<input
|
|
506
|
+
type="number"
|
|
507
|
+
class="cinder-invocation-rule-builder__condition-value"
|
|
508
|
+
aria-label={`Value for condition ${conditionIndex + 1} of ${rule.label}`}
|
|
509
|
+
placeholder="Value to compare"
|
|
510
|
+
value={condition.value}
|
|
511
|
+
oninput={(event) =>
|
|
512
|
+
handleUpdateCondition(
|
|
513
|
+
rule.id,
|
|
514
|
+
condition.id,
|
|
515
|
+
'value',
|
|
516
|
+
(event.target as HTMLInputElement).value,
|
|
517
|
+
)}
|
|
518
|
+
/>
|
|
519
|
+
{:else if conditionsOnly && fieldValueType(condition.field) === 'boolean'}
|
|
520
|
+
<input
|
|
521
|
+
type="checkbox"
|
|
522
|
+
class="cinder-invocation-rule-builder__condition-value-checkbox"
|
|
523
|
+
aria-label={`Value for condition ${conditionIndex + 1} of ${rule.label}`}
|
|
524
|
+
checked={condition.value === 'true'}
|
|
525
|
+
onchange={(event) =>
|
|
526
|
+
handleUpdateCondition(
|
|
527
|
+
rule.id,
|
|
528
|
+
condition.id,
|
|
529
|
+
'value',
|
|
530
|
+
String((event.target as HTMLInputElement).checked),
|
|
531
|
+
)}
|
|
532
|
+
/>
|
|
533
|
+
{:else if conditionsOnly && fieldValueType(condition.field) === 'enum'}
|
|
534
|
+
<select
|
|
535
|
+
class="cinder-invocation-rule-builder__condition-select cinder-invocation-rule-builder__condition-value-select"
|
|
536
|
+
aria-label={`Value for condition ${conditionIndex + 1} of ${rule.label}`}
|
|
537
|
+
value={condition.value}
|
|
538
|
+
onchange={(event) =>
|
|
539
|
+
handleUpdateCondition(
|
|
540
|
+
rule.id,
|
|
541
|
+
condition.id,
|
|
542
|
+
'value',
|
|
543
|
+
(event.target as HTMLSelectElement).value,
|
|
544
|
+
)}
|
|
545
|
+
>
|
|
546
|
+
{#each fieldEnumOptions(condition.field) as option (option.value)}
|
|
547
|
+
<option value={option.value}>{option.label}</option>
|
|
548
|
+
{/each}
|
|
549
|
+
</select>
|
|
550
|
+
{:else}
|
|
551
|
+
<input
|
|
552
|
+
type="text"
|
|
553
|
+
class="cinder-invocation-rule-builder__condition-value"
|
|
554
|
+
aria-label={`Value for condition ${conditionIndex + 1} of ${rule.label}`}
|
|
555
|
+
placeholder="Value to compare"
|
|
556
|
+
value={condition.value}
|
|
557
|
+
oninput={(event) =>
|
|
558
|
+
handleUpdateCondition(
|
|
559
|
+
rule.id,
|
|
560
|
+
condition.id,
|
|
561
|
+
'value',
|
|
562
|
+
(event.target as HTMLInputElement).value,
|
|
563
|
+
)}
|
|
564
|
+
/>
|
|
565
|
+
{/if}
|
|
355
566
|
|
|
356
567
|
<button
|
|
357
568
|
type="button"
|
|
@@ -386,90 +597,92 @@ function actionSummary(action) {
|
|
|
386
597
|
{/if}
|
|
387
598
|
</div>
|
|
388
599
|
|
|
389
|
-
<!-- Actions section -->
|
|
390
|
-
|
|
391
|
-
<
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
Actions
|
|
396
|
-
</span>
|
|
397
|
-
{#if !effectiveReadonly && rule.actions.length === 0}
|
|
398
|
-
<p class="cinder-invocation-rule-builder__validation" role="status">
|
|
399
|
-
Add at least one action for this rule.
|
|
400
|
-
</p>
|
|
401
|
-
{/if}
|
|
402
|
-
|
|
403
|
-
{#if effectiveReadonly}
|
|
404
|
-
<div
|
|
405
|
-
class="cinder-invocation-rule-builder__summary"
|
|
406
|
-
aria-label={`Actions for ${rule.label}`}
|
|
600
|
+
<!-- Actions section — hidden entirely in conditions-only mode. -->
|
|
601
|
+
{#if mode === 'full'}
|
|
602
|
+
<div>
|
|
603
|
+
<span
|
|
604
|
+
id={`${baseId}-rule-${ruleIndex}-actions-label`}
|
|
605
|
+
class="cinder-invocation-rule-builder__section-heading"
|
|
407
606
|
>
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
607
|
+
Actions
|
|
608
|
+
</span>
|
|
609
|
+
{#if !effectiveReadonly && rule.actions.length === 0}
|
|
610
|
+
<p class="cinder-invocation-rule-builder__validation" role="status">
|
|
611
|
+
Add at least one action for this rule.
|
|
612
|
+
</p>
|
|
613
|
+
{/if}
|
|
614
|
+
|
|
615
|
+
{#if effectiveReadonly}
|
|
616
|
+
<div
|
|
617
|
+
class="cinder-invocation-rule-builder__summary"
|
|
618
|
+
aria-label={`Actions for ${rule.label}`}
|
|
619
|
+
>
|
|
620
|
+
{#if rule.actions.length === 0}
|
|
621
|
+
<p class="cinder-invocation-rule-builder__empty">No actions configured.</p>
|
|
622
|
+
{:else}
|
|
623
|
+
{#each rule.actions as action (action.id)}
|
|
624
|
+
<div class="cinder-invocation-rule-builder__summary-row">
|
|
625
|
+
{actionSummary(action)}
|
|
626
|
+
</div>
|
|
627
|
+
{/each}
|
|
628
|
+
{/if}
|
|
629
|
+
</div>
|
|
630
|
+
{:else}
|
|
631
|
+
<div
|
|
632
|
+
class="cinder-invocation-rule-builder__actions"
|
|
633
|
+
role="list"
|
|
634
|
+
aria-labelledby={`${baseId}-rule-${ruleIndex}-actions-label`}
|
|
635
|
+
>
|
|
636
|
+
{#each rule.actions as action, actionIndex (action.id)}
|
|
637
|
+
<div class="cinder-invocation-rule-builder__action" role="listitem">
|
|
638
|
+
<select
|
|
639
|
+
class="cinder-invocation-rule-builder__action-select"
|
|
640
|
+
aria-label={`Action ${actionIndex + 1} target for ${rule.label}`}
|
|
641
|
+
value={action.target}
|
|
642
|
+
onchange={(event) =>
|
|
643
|
+
handleUpdateAction(
|
|
644
|
+
rule.id,
|
|
645
|
+
action.id,
|
|
646
|
+
(event.target as HTMLSelectElement).value,
|
|
647
|
+
)}
|
|
648
|
+
>
|
|
649
|
+
{#each resolvedActionOptions as option (option.value)}
|
|
650
|
+
<option value={option.value}>{option.label}</option>
|
|
651
|
+
{/each}
|
|
652
|
+
</select>
|
|
653
|
+
|
|
654
|
+
<button
|
|
655
|
+
type="button"
|
|
656
|
+
class="cinder-invocation-rule-builder__icon-btn"
|
|
657
|
+
aria-label={`Remove action ${actionIndex + 1} of ${rule.label}`}
|
|
658
|
+
data-irb-action-remove
|
|
659
|
+
onclick={() => handleRemoveAction(rule.id, action.id, actionIndex, ruleIndex)}
|
|
660
|
+
>
|
|
661
|
+
<svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true">
|
|
662
|
+
<path
|
|
663
|
+
d="M4 4l8 8M12 4l-8 8"
|
|
664
|
+
stroke="currentColor"
|
|
665
|
+
stroke-width="2"
|
|
666
|
+
fill="none"
|
|
667
|
+
/>
|
|
668
|
+
</svg>
|
|
669
|
+
</button>
|
|
414
670
|
</div>
|
|
415
671
|
{/each}
|
|
416
|
-
|
|
417
|
-
</div>
|
|
418
|
-
{:else}
|
|
419
|
-
<div
|
|
420
|
-
class="cinder-invocation-rule-builder__actions"
|
|
421
|
-
role="list"
|
|
422
|
-
aria-labelledby={`${baseId}-rule-${ruleIndex}-actions-label`}
|
|
423
|
-
>
|
|
424
|
-
{#each rule.actions as action, actionIndex (action.id)}
|
|
425
|
-
<div class="cinder-invocation-rule-builder__action" role="listitem">
|
|
426
|
-
<select
|
|
427
|
-
class="cinder-invocation-rule-builder__action-select"
|
|
428
|
-
aria-label={`Action ${actionIndex + 1} target for ${rule.label}`}
|
|
429
|
-
value={action.target}
|
|
430
|
-
onchange={(event) =>
|
|
431
|
-
handleUpdateAction(
|
|
432
|
-
rule.id,
|
|
433
|
-
action.id,
|
|
434
|
-
(event.target as HTMLSelectElement).value,
|
|
435
|
-
)}
|
|
436
|
-
>
|
|
437
|
-
{#each actionOptions as option (option.value)}
|
|
438
|
-
<option value={option.value}>{option.label}</option>
|
|
439
|
-
{/each}
|
|
440
|
-
</select>
|
|
441
|
-
|
|
442
|
-
<button
|
|
443
|
-
type="button"
|
|
444
|
-
class="cinder-invocation-rule-builder__icon-btn"
|
|
445
|
-
aria-label={`Remove action ${actionIndex + 1} of ${rule.label}`}
|
|
446
|
-
data-irb-action-remove
|
|
447
|
-
onclick={() => handleRemoveAction(rule.id, action.id, actionIndex, ruleIndex)}
|
|
448
|
-
>
|
|
449
|
-
<svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true">
|
|
450
|
-
<path
|
|
451
|
-
d="M4 4l8 8M12 4l-8 8"
|
|
452
|
-
stroke="currentColor"
|
|
453
|
-
stroke-width="2"
|
|
454
|
-
fill="none"
|
|
455
|
-
/>
|
|
456
|
-
</svg>
|
|
457
|
-
</button>
|
|
458
|
-
</div>
|
|
459
|
-
{/each}
|
|
460
|
-
</div>
|
|
672
|
+
</div>
|
|
461
673
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
674
|
+
<button
|
|
675
|
+
type="button"
|
|
676
|
+
class="cinder-invocation-rule-builder__add-btn"
|
|
677
|
+
aria-label={`${addActionLabel} to ${rule.label}`}
|
|
678
|
+
data-irb-add-action
|
|
679
|
+
onclick={() => handleAddAction(rule.id)}
|
|
680
|
+
>
|
|
681
|
+
+ {addActionLabel}
|
|
682
|
+
</button>
|
|
683
|
+
{/if}
|
|
684
|
+
</div>
|
|
685
|
+
{/if}
|
|
473
686
|
</div>
|
|
474
687
|
{/each}
|
|
475
688
|
|