@stll/workspace-ui 0.0.1-placeholder.0 → 0.2.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/README.md +59 -0
- package/dist/calculation-format.d.ts +54 -0
- package/dist/calculation-format.js +72 -0
- package/dist/calculation-selection.d.ts +15 -0
- package/dist/calculation-selection.js +14 -0
- package/dist/calculations.d.ts +67 -0
- package/dist/calculations.js +108 -0
- package/dist/colors.d.ts +2 -0
- package/dist/colors.js +2 -0
- package/dist/conditions/builder.d.ts +98 -0
- package/dist/conditions/builder.js +447 -0
- package/dist/conditions/index.d.ts +3 -0
- package/dist/conditions/index.js +3 -0
- package/dist/conditions/logic.d.ts +70 -0
- package/dist/conditions/logic.js +248 -0
- package/dist/field-value-logic.d.ts +7 -0
- package/dist/field-value-logic.js +8 -0
- package/dist/field-value.d.ts +30 -0
- package/dist/field-value.js +308 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/property-icon.d.ts +12 -0
- package/dist/property-icon.js +56 -0
- package/dist/sorts.d.ts +41 -0
- package/dist/sorts.js +99 -0
- package/dist/table-skeleton-rows.d.ts +18 -0
- package/dist/table-skeleton-rows.js +34 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.js +17 -0
- package/dist/view-switcher.d.ts +33 -0
- package/dist/view-switcher.js +159 -0
- package/dist/view-switcher.logic.d.ts +14 -0
- package/dist/view-switcher.logic.js +14 -0
- package/package.json +135 -3
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import { appendChild, asGroup, buildLeaf, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, removeChild, replaceChild, valueEditorFor } from "./logic.js";
|
|
2
|
+
import { panic } from "better-result";
|
|
3
|
+
import { PlusIcon, XIcon } from "lucide-react";
|
|
4
|
+
import { Button } from "@stll/ui/button";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { cn } from "@stll/ui/utils";
|
|
7
|
+
import { createContext, use } from "react";
|
|
8
|
+
import { DatePickerPopover } from "@stll/ui/date-picker-popover";
|
|
9
|
+
import { Input } from "@stll/ui/input";
|
|
10
|
+
import { Select, SelectItem, SelectPopup, SelectSeparator, SelectTrigger, SelectValue } from "@stll/ui/select";
|
|
11
|
+
//#region src/conditions/builder.tsx
|
|
12
|
+
const BuilderContext = createContext(null);
|
|
13
|
+
/**
|
|
14
|
+
* A context rather than a prop on every row because the builder is recursive:
|
|
15
|
+
* threading one object through six nested components would put it in every
|
|
16
|
+
* signature and still arrive at the same leaf.
|
|
17
|
+
*/
|
|
18
|
+
const useBuilderContext = () => {
|
|
19
|
+
const context = use(BuilderContext);
|
|
20
|
+
if (!context) panic("ConditionBuilder rendered outside its own provider");
|
|
21
|
+
return context;
|
|
22
|
+
};
|
|
23
|
+
const useLabels = () => useBuilderContext().labels;
|
|
24
|
+
/** The formula leaf's left operand carries no field type, so its operators come
|
|
25
|
+
* from the numeric set and its value editor is the int input. */
|
|
26
|
+
const FORMULA_VALUE_TYPE = "int";
|
|
27
|
+
/** Sentinel select value that switches a leaf to a formula operand. Leads with a
|
|
28
|
+
* space so it never collides with a real field index. */
|
|
29
|
+
const FORMULA_OPTION = " formula";
|
|
30
|
+
const ConditionBuilder = ({ value, onChange, capabilities, labels, locale }) => /* @__PURE__ */ jsx(BuilderContext, {
|
|
31
|
+
value: {
|
|
32
|
+
labels,
|
|
33
|
+
locale
|
|
34
|
+
},
|
|
35
|
+
children: /* @__PURE__ */ jsx(ConditionBuilderTree, {
|
|
36
|
+
capabilities,
|
|
37
|
+
onChange,
|
|
38
|
+
value
|
|
39
|
+
})
|
|
40
|
+
});
|
|
41
|
+
const ConditionBuilderTree = ({ value, onChange, capabilities }) => {
|
|
42
|
+
const labels = useLabels();
|
|
43
|
+
const group = asGroup(value);
|
|
44
|
+
const { fields, allowNesting = false } = capabilities;
|
|
45
|
+
const firstField = fields.at(0);
|
|
46
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
47
|
+
className: "flex flex-col gap-2",
|
|
48
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
49
|
+
className: "flex flex-col gap-2",
|
|
50
|
+
children: group.children.map((child, index) => {
|
|
51
|
+
if (child.type === "group" && allowNesting) return /* @__PURE__ */ jsx(NestedGroupRow, {
|
|
52
|
+
capabilities,
|
|
53
|
+
combinator: group.combinator,
|
|
54
|
+
index,
|
|
55
|
+
onChange: (next) => onChange(replaceChild(group, index, next)),
|
|
56
|
+
onRemove: () => onChange(removeChild(group, index)),
|
|
57
|
+
onSetCombinator: (combinator) => onChange({
|
|
58
|
+
...group,
|
|
59
|
+
combinator
|
|
60
|
+
}),
|
|
61
|
+
value: child
|
|
62
|
+
}, index);
|
|
63
|
+
return /* @__PURE__ */ jsx(LeafRow, {
|
|
64
|
+
capabilities,
|
|
65
|
+
combinator: group.combinator,
|
|
66
|
+
index,
|
|
67
|
+
node: child,
|
|
68
|
+
onChange: (next) => onChange(replaceChild(group, index, next)),
|
|
69
|
+
onRemove: () => onChange(removeChild(group, index)),
|
|
70
|
+
onSetCombinator: (combinator) => onChange({
|
|
71
|
+
...group,
|
|
72
|
+
combinator
|
|
73
|
+
})
|
|
74
|
+
}, index);
|
|
75
|
+
})
|
|
76
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
77
|
+
className: "ms-[5.375rem] flex flex-wrap gap-1",
|
|
78
|
+
children: [/* @__PURE__ */ jsxs(Button, {
|
|
79
|
+
className: "w-fit justify-start",
|
|
80
|
+
disabled: !firstField,
|
|
81
|
+
onClick: () => {
|
|
82
|
+
if (firstField) onChange(appendChild(group, leafFromField(firstField)));
|
|
83
|
+
},
|
|
84
|
+
size: "xs",
|
|
85
|
+
type: "button",
|
|
86
|
+
variant: "ghost",
|
|
87
|
+
children: [/* @__PURE__ */ jsx(PlusIcon, {}), labels.addCondition]
|
|
88
|
+
}), allowNesting && /* @__PURE__ */ jsxs(Button, {
|
|
89
|
+
className: "w-fit justify-start",
|
|
90
|
+
disabled: !firstField,
|
|
91
|
+
onClick: () => {
|
|
92
|
+
if (firstField) onChange(appendChild(group, {
|
|
93
|
+
type: "group",
|
|
94
|
+
combinator: "and",
|
|
95
|
+
children: [leafFromField(firstField)]
|
|
96
|
+
}));
|
|
97
|
+
},
|
|
98
|
+
size: "xs",
|
|
99
|
+
type: "button",
|
|
100
|
+
variant: "ghost",
|
|
101
|
+
children: [/* @__PURE__ */ jsx(PlusIcon, {}), labels.addGroup]
|
|
102
|
+
})]
|
|
103
|
+
})]
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
/** Left gutter: row 0 reads "When", row 1 carries the editable And/Or that sets
|
|
107
|
+
* the group's combinator, later rows echo it read-only. A single-child group
|
|
108
|
+
* shows no combinator word at all. */
|
|
109
|
+
const ConditionGutter = ({ index, combinator, onCombinator }) => {
|
|
110
|
+
const labels = useLabels();
|
|
111
|
+
const gutterClass = "w-20 shrink-0 text-muted-foreground text-xs";
|
|
112
|
+
if (index === 0) return /* @__PURE__ */ jsx("span", {
|
|
113
|
+
className: cn(gutterClass, "ps-1"),
|
|
114
|
+
children: labels.when
|
|
115
|
+
});
|
|
116
|
+
if (index === 1) return /* @__PURE__ */ jsxs(Select, {
|
|
117
|
+
onValueChange: (next) => onCombinator(next === "or" ? "or" : "and"),
|
|
118
|
+
value: combinator,
|
|
119
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
120
|
+
"aria-label": labels.match,
|
|
121
|
+
className: "h-7 min-h-0 w-20 min-w-0 shrink-0 text-xs",
|
|
122
|
+
size: "sm",
|
|
123
|
+
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
124
|
+
}), /* @__PURE__ */ jsxs(SelectPopup, { children: [/* @__PURE__ */ jsx(SelectItem, {
|
|
125
|
+
value: "and",
|
|
126
|
+
children: labels.and
|
|
127
|
+
}), /* @__PURE__ */ jsx(SelectItem, {
|
|
128
|
+
value: "or",
|
|
129
|
+
children: labels.or
|
|
130
|
+
})] })]
|
|
131
|
+
});
|
|
132
|
+
return /* @__PURE__ */ jsx("span", {
|
|
133
|
+
className: cn(gutterClass, "ps-1"),
|
|
134
|
+
children: combinator === "or" ? labels.or : labels.and
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
const NestedGroupRow = ({ value, capabilities, index, combinator, onChange, onRemove, onSetCombinator }) => {
|
|
138
|
+
const removeLabel = useLabels().remove;
|
|
139
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
140
|
+
className: "flex items-start gap-2",
|
|
141
|
+
children: [/* @__PURE__ */ jsx(ConditionGutter, {
|
|
142
|
+
combinator,
|
|
143
|
+
index,
|
|
144
|
+
onCombinator: onSetCombinator
|
|
145
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
146
|
+
className: "border-border/70 bg-muted/20 flex flex-1 items-start gap-2 rounded-md border p-2",
|
|
147
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
148
|
+
className: "flex-1",
|
|
149
|
+
children: /* @__PURE__ */ jsx(ConditionBuilderTree, {
|
|
150
|
+
capabilities,
|
|
151
|
+
onChange,
|
|
152
|
+
value
|
|
153
|
+
})
|
|
154
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
155
|
+
"aria-label": removeLabel,
|
|
156
|
+
onClick: onRemove,
|
|
157
|
+
size: "icon-xs",
|
|
158
|
+
tooltip: removeLabel,
|
|
159
|
+
type: "button",
|
|
160
|
+
variant: "ghost",
|
|
161
|
+
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
162
|
+
})]
|
|
163
|
+
})]
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
const LeafRow = ({ node, capabilities, index, combinator, onChange, onRemove, onSetCombinator }) => {
|
|
167
|
+
const labels = useLabels();
|
|
168
|
+
const { fields, allowFormula = false } = capabilities;
|
|
169
|
+
const operand = leafOperand(node);
|
|
170
|
+
if (operand?.type === "formula") return /* @__PURE__ */ jsx(FormulaLeafRow, {
|
|
171
|
+
capabilities,
|
|
172
|
+
combinator,
|
|
173
|
+
expr: operand.expr,
|
|
174
|
+
index,
|
|
175
|
+
node,
|
|
176
|
+
onChange,
|
|
177
|
+
onRemove,
|
|
178
|
+
onSetCombinator
|
|
179
|
+
});
|
|
180
|
+
const opsFor = capabilities.operatorsFor ?? operatorsFor;
|
|
181
|
+
const editorFor = capabilities.valueEditorFor ?? valueEditorFor;
|
|
182
|
+
const fieldIndex = operand ? fields.findIndex((f) => operandsEqual(f.operand, operand)) : -1;
|
|
183
|
+
const field = fields[fieldIndex];
|
|
184
|
+
if (!field) return null;
|
|
185
|
+
const operator = leafOperator(node) ?? opsFor(field.valueType).at(0);
|
|
186
|
+
if (!operator) return null;
|
|
187
|
+
const operators = opsFor(field.valueType);
|
|
188
|
+
const editorKind = editorFor(field.valueType, operator);
|
|
189
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
190
|
+
className: "flex items-center gap-1.5",
|
|
191
|
+
children: [
|
|
192
|
+
/* @__PURE__ */ jsx(ConditionGutter, {
|
|
193
|
+
combinator,
|
|
194
|
+
index,
|
|
195
|
+
onCombinator: onSetCombinator
|
|
196
|
+
}),
|
|
197
|
+
/* @__PURE__ */ jsxs("div", {
|
|
198
|
+
className: "flex min-w-0 flex-1 items-center gap-1.5",
|
|
199
|
+
children: [
|
|
200
|
+
/* @__PURE__ */ jsxs(Select, {
|
|
201
|
+
onValueChange: (next) => {
|
|
202
|
+
if (next === null) return;
|
|
203
|
+
if (next === FORMULA_OPTION) {
|
|
204
|
+
onChange(buildLeaf({
|
|
205
|
+
operand: {
|
|
206
|
+
type: "formula",
|
|
207
|
+
expr: ""
|
|
208
|
+
},
|
|
209
|
+
operator: opsFor(FORMULA_VALUE_TYPE).at(0) ?? "eq",
|
|
210
|
+
value: ""
|
|
211
|
+
}));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const nextField = fields[Number(next)];
|
|
215
|
+
if (nextField) onChange(leafFromField(nextField));
|
|
216
|
+
},
|
|
217
|
+
value: String(fieldIndex),
|
|
218
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
219
|
+
className: "h-7 min-h-0 w-auto max-w-56 text-xs",
|
|
220
|
+
size: "sm",
|
|
221
|
+
children: /* @__PURE__ */ jsx(SelectValue, {
|
|
222
|
+
placeholder: labels.fieldPlaceholder,
|
|
223
|
+
children: field.label
|
|
224
|
+
})
|
|
225
|
+
}), /* @__PURE__ */ jsxs(SelectPopup, {
|
|
226
|
+
alignItemWithTrigger: false,
|
|
227
|
+
children: [fields.map((option, optionIndex) => /* @__PURE__ */ jsx(SelectItem, {
|
|
228
|
+
value: String(optionIndex),
|
|
229
|
+
children: option.label
|
|
230
|
+
}, JSON.stringify(option.operand))), allowFormula && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SelectSeparator, {}), /* @__PURE__ */ jsx(SelectItem, {
|
|
231
|
+
value: FORMULA_OPTION,
|
|
232
|
+
children: labels.useFormula
|
|
233
|
+
})] })]
|
|
234
|
+
})]
|
|
235
|
+
}),
|
|
236
|
+
/* @__PURE__ */ jsx(OperatorSelect, {
|
|
237
|
+
field,
|
|
238
|
+
node,
|
|
239
|
+
onChange,
|
|
240
|
+
operator,
|
|
241
|
+
operators
|
|
242
|
+
}),
|
|
243
|
+
/* @__PURE__ */ jsx(LeafValueEditor, {
|
|
244
|
+
capabilities,
|
|
245
|
+
editorKind,
|
|
246
|
+
field,
|
|
247
|
+
node,
|
|
248
|
+
onChange,
|
|
249
|
+
operator
|
|
250
|
+
})
|
|
251
|
+
]
|
|
252
|
+
}),
|
|
253
|
+
/* @__PURE__ */ jsx(RemoveButton, { onRemove })
|
|
254
|
+
]
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
const FormulaLeafRow = ({ node, expr, capabilities, index, combinator, onChange, onRemove, onSetCombinator }) => {
|
|
258
|
+
const labels = useLabels();
|
|
259
|
+
const { fields, formulaNumberFields = [] } = capabilities;
|
|
260
|
+
const opsFor = capabilities.operatorsFor ?? operatorsFor;
|
|
261
|
+
const operator = leafOperator(node) ?? opsFor(FORMULA_VALUE_TYPE).at(0) ?? "eq";
|
|
262
|
+
const operators = opsFor(FORMULA_VALUE_TYPE);
|
|
263
|
+
const firstField = fields.at(0);
|
|
264
|
+
const formulaOperand = {
|
|
265
|
+
type: "formula",
|
|
266
|
+
expr
|
|
267
|
+
};
|
|
268
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
269
|
+
className: "flex items-center gap-1.5",
|
|
270
|
+
children: [
|
|
271
|
+
/* @__PURE__ */ jsx(ConditionGutter, {
|
|
272
|
+
combinator,
|
|
273
|
+
index,
|
|
274
|
+
onCombinator: onSetCombinator
|
|
275
|
+
}),
|
|
276
|
+
/* @__PURE__ */ jsxs("div", {
|
|
277
|
+
className: "flex min-w-0 flex-1 items-center gap-1.5",
|
|
278
|
+
children: [
|
|
279
|
+
capabilities.renderFormulaCell?.({
|
|
280
|
+
expr,
|
|
281
|
+
numberFields: formulaNumberFields,
|
|
282
|
+
onChangeExpr: (nextExpr) => onChange(buildLeaf({
|
|
283
|
+
operand: {
|
|
284
|
+
type: "formula",
|
|
285
|
+
expr: nextExpr
|
|
286
|
+
},
|
|
287
|
+
operator,
|
|
288
|
+
value: leafValueString(node)
|
|
289
|
+
})),
|
|
290
|
+
onUseField: () => {
|
|
291
|
+
if (firstField) onChange(leafFromField(firstField));
|
|
292
|
+
}
|
|
293
|
+
}),
|
|
294
|
+
/* @__PURE__ */ jsx(OperatorSelectFormula, {
|
|
295
|
+
node,
|
|
296
|
+
onChange,
|
|
297
|
+
operand: formulaOperand,
|
|
298
|
+
operator,
|
|
299
|
+
operators
|
|
300
|
+
}),
|
|
301
|
+
/* @__PURE__ */ jsx(Input, {
|
|
302
|
+
"aria-label": labels.valuePlaceholder,
|
|
303
|
+
className: "h-7! w-24 text-xs",
|
|
304
|
+
onChange: (e) => onChange(buildLeaf({
|
|
305
|
+
operand: formulaOperand,
|
|
306
|
+
operator,
|
|
307
|
+
value: e.currentTarget.value
|
|
308
|
+
})),
|
|
309
|
+
placeholder: labels.valuePlaceholder,
|
|
310
|
+
size: "sm",
|
|
311
|
+
type: "number",
|
|
312
|
+
value: leafValueString(node)
|
|
313
|
+
})
|
|
314
|
+
]
|
|
315
|
+
}),
|
|
316
|
+
/* @__PURE__ */ jsx(RemoveButton, { onRemove })
|
|
317
|
+
]
|
|
318
|
+
});
|
|
319
|
+
};
|
|
320
|
+
const OperatorSelect = ({ field, node, operator, operators, onChange }) => {
|
|
321
|
+
const labels = useLabels();
|
|
322
|
+
return /* @__PURE__ */ jsxs(Select, {
|
|
323
|
+
onValueChange: (next) => {
|
|
324
|
+
if (next === null || !isConditionOperator(next)) return;
|
|
325
|
+
onChange(buildLeaf({
|
|
326
|
+
operand: field.operand,
|
|
327
|
+
operator: next,
|
|
328
|
+
value: isMultiValue(next) ? leafValueList(node) : leafValueString(node)
|
|
329
|
+
}));
|
|
330
|
+
},
|
|
331
|
+
value: operator,
|
|
332
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
333
|
+
className: "h-7 min-h-0 w-auto min-w-24 text-xs",
|
|
334
|
+
size: "sm",
|
|
335
|
+
children: /* @__PURE__ */ jsx(SelectValue, { children: () => labels.operator(field.valueType, operator) })
|
|
336
|
+
}), /* @__PURE__ */ jsx(SelectPopup, {
|
|
337
|
+
alignItemWithTrigger: false,
|
|
338
|
+
children: operators.map((op) => /* @__PURE__ */ jsx(SelectItem, {
|
|
339
|
+
value: op,
|
|
340
|
+
children: labels.operator(field.valueType, op)
|
|
341
|
+
}, op))
|
|
342
|
+
})]
|
|
343
|
+
});
|
|
344
|
+
};
|
|
345
|
+
const OperatorSelectFormula = ({ node, operand, operator, operators, onChange }) => {
|
|
346
|
+
const labels = useLabels();
|
|
347
|
+
return /* @__PURE__ */ jsxs(Select, {
|
|
348
|
+
onValueChange: (next) => {
|
|
349
|
+
if (next === null || !isConditionOperator(next)) return;
|
|
350
|
+
onChange(buildLeaf({
|
|
351
|
+
operand,
|
|
352
|
+
operator: next,
|
|
353
|
+
value: leafValueString(node)
|
|
354
|
+
}));
|
|
355
|
+
},
|
|
356
|
+
value: operator,
|
|
357
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
358
|
+
className: "h-7 min-h-0 w-auto min-w-24 text-xs",
|
|
359
|
+
size: "sm",
|
|
360
|
+
children: /* @__PURE__ */ jsx(SelectValue, { children: () => labels.operator(FORMULA_VALUE_TYPE, operator) })
|
|
361
|
+
}), /* @__PURE__ */ jsx(SelectPopup, {
|
|
362
|
+
alignItemWithTrigger: false,
|
|
363
|
+
children: operators.map((op) => /* @__PURE__ */ jsx(SelectItem, {
|
|
364
|
+
value: op,
|
|
365
|
+
children: labels.operator(FORMULA_VALUE_TYPE, op)
|
|
366
|
+
}, op))
|
|
367
|
+
})]
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
const RemoveButton = ({ onRemove }) => {
|
|
371
|
+
const labels = useLabels();
|
|
372
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
373
|
+
"aria-label": labels.remove,
|
|
374
|
+
onClick: onRemove,
|
|
375
|
+
size: "icon-xs",
|
|
376
|
+
type: "button",
|
|
377
|
+
variant: "ghost",
|
|
378
|
+
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
379
|
+
});
|
|
380
|
+
};
|
|
381
|
+
const LeafValueEditor = ({ capabilities, editorKind, field, node, operator, onChange }) => {
|
|
382
|
+
const { labels, locale } = useBuilderContext();
|
|
383
|
+
const emit = (value) => {
|
|
384
|
+
onChange(buildLeaf({
|
|
385
|
+
operand: field.operand,
|
|
386
|
+
operator,
|
|
387
|
+
value
|
|
388
|
+
}));
|
|
389
|
+
};
|
|
390
|
+
const injected = capabilities.renderValueEditor?.({
|
|
391
|
+
editorKind,
|
|
392
|
+
field,
|
|
393
|
+
node,
|
|
394
|
+
operator,
|
|
395
|
+
emit
|
|
396
|
+
});
|
|
397
|
+
if (injected !== null && injected !== void 0) return injected;
|
|
398
|
+
if (editorKind === "none") return null;
|
|
399
|
+
if (editorKind === "select") return /* @__PURE__ */ jsxs(Select, {
|
|
400
|
+
multiple: isMultiValue(operator),
|
|
401
|
+
onValueChange: (next) => {
|
|
402
|
+
if (next !== null) emit(next);
|
|
403
|
+
},
|
|
404
|
+
value: isMultiValue(operator) ? leafValueList(node) : leafValueString(node),
|
|
405
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
406
|
+
className: "h-7 min-h-0 w-auto min-w-28 text-xs",
|
|
407
|
+
size: "sm",
|
|
408
|
+
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: labels.valuePlaceholder })
|
|
409
|
+
}), /* @__PURE__ */ jsx(SelectPopup, {
|
|
410
|
+
alignItemWithTrigger: false,
|
|
411
|
+
children: field.options?.map((option) => /* @__PURE__ */ jsx(SelectItem, {
|
|
412
|
+
value: option.value,
|
|
413
|
+
children: option.label
|
|
414
|
+
}, option.value))
|
|
415
|
+
})]
|
|
416
|
+
});
|
|
417
|
+
if (editorKind === "int") return /* @__PURE__ */ jsx(Input, {
|
|
418
|
+
"aria-label": labels.valuePlaceholder,
|
|
419
|
+
className: "h-7! w-24 text-xs",
|
|
420
|
+
onChange: (e) => emit(e.currentTarget.value),
|
|
421
|
+
placeholder: labels.valuePlaceholder,
|
|
422
|
+
size: "sm",
|
|
423
|
+
type: "number",
|
|
424
|
+
value: leafValueString(node)
|
|
425
|
+
});
|
|
426
|
+
if (editorKind === "date") return /* @__PURE__ */ jsx("div", {
|
|
427
|
+
className: "border-input bg-background min-w-28 rounded-md border px-1",
|
|
428
|
+
children: /* @__PURE__ */ jsx(DatePickerPopover, {
|
|
429
|
+
clearLabel: labels.clearDate,
|
|
430
|
+
locale,
|
|
431
|
+
onChange: (next) => emit(next ?? ""),
|
|
432
|
+
placeholderLabel: labels.selectDate,
|
|
433
|
+
todayLabel: labels.today,
|
|
434
|
+
value: leafValueString(node) || null
|
|
435
|
+
})
|
|
436
|
+
});
|
|
437
|
+
return /* @__PURE__ */ jsx(Input, {
|
|
438
|
+
"aria-label": labels.valuePlaceholder,
|
|
439
|
+
className: "h-7! w-32 text-xs",
|
|
440
|
+
onChange: (e) => emit(e.currentTarget.value),
|
|
441
|
+
placeholder: labels.valuePlaceholder,
|
|
442
|
+
size: "sm",
|
|
443
|
+
value: leafValueString(node)
|
|
444
|
+
});
|
|
445
|
+
};
|
|
446
|
+
//#endregion
|
|
447
|
+
export { ConditionBuilder };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { CONDITION_OPERATORS, ConditionOperator, FieldOption, FieldOptionChoice, FieldValueType, ValueEditorKind, appendChild, asGroup, buildLeaf, fieldForNode, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, removeChild, replaceChild, valueEditorFor } from "./logic.js";
|
|
2
|
+
import { ConditionBuilder, ConditionBuilderLabels, ConditionCapabilities, FormulaCellRenderCtx, ValueEditorRenderCtx } from "./builder.js";
|
|
3
|
+
export { CONDITION_OPERATORS, ConditionBuilder, type ConditionBuilderLabels, type ConditionCapabilities, type ConditionOperator, type FieldOption, type FieldOptionChoice, type FieldValueType, type FormulaCellRenderCtx, type ValueEditorKind, type ValueEditorRenderCtx, appendChild, asGroup, buildLeaf, fieldForNode, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, removeChild, replaceChild, valueEditorFor };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { CONDITION_OPERATORS, appendChild, asGroup, buildLeaf, fieldForNode, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, removeChild, replaceChild, valueEditorFor } from "./logic.js";
|
|
2
|
+
import { ConditionBuilder } from "./builder.js";
|
|
3
|
+
export { CONDITION_OPERATORS, ConditionBuilder, appendChild, asGroup, buildLeaf, fieldForNode, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, removeChild, replaceChild, valueEditorFor };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { PropertyIconType } from "../property-icon.js";
|
|
2
|
+
import { ConditionNode, GroupNode, RefOperand } from "@stll/conditions";
|
|
3
|
+
//#region src/conditions/logic.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Context-agnostic description of one operand the builder may filter
|
|
6
|
+
* on. `valueType` drives both the operator set (`operatorsFor`) and
|
|
7
|
+
* the value editor; `options` supplies the choices for any select-like
|
|
8
|
+
* value type.
|
|
9
|
+
*/
|
|
10
|
+
type FieldValueType = "text" | "single-select" | "multi-select" | "date" | "int" | "money" | "person" | "kind" | "status" | "priority";
|
|
11
|
+
type FieldOptionChoice = {
|
|
12
|
+
value: string;
|
|
13
|
+
label: string;
|
|
14
|
+
color?: string;
|
|
15
|
+
};
|
|
16
|
+
type FieldOption = {
|
|
17
|
+
operand: RefOperand;
|
|
18
|
+
label: string;
|
|
19
|
+
valueType: FieldValueType;
|
|
20
|
+
/** Drives the property-type icon shown in chips and the picker. */
|
|
21
|
+
type: PropertyIconType;
|
|
22
|
+
options?: FieldOptionChoice[];
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The surface operators the builder exposes. A subset maps to
|
|
26
|
+
* `compare` nodes (binary comparisons against a literal); the rest map
|
|
27
|
+
* to `predicate` nodes. `operatorKind` is the single place that
|
|
28
|
+
* decides which AST node each operator builds.
|
|
29
|
+
*/
|
|
30
|
+
declare const CONDITION_OPERATORS: readonly ["eq", "neq", "contains", "not_contains", "starts_with", "ends_with", "contains_all", "in", "gt", "lt", "gte", "lte", "is_empty", "is_not_empty"];
|
|
31
|
+
type ConditionOperator = (typeof CONDITION_OPERATORS)[number];
|
|
32
|
+
declare const isConditionOperator: (value: string) => value is ConditionOperator;
|
|
33
|
+
declare const operatorsFor: (valueType: FieldValueType) => readonly ConditionOperator[];
|
|
34
|
+
/** How a value type renders its value editor. */
|
|
35
|
+
type ValueEditorKind = "none" | "text" | "int" | "date" | "select";
|
|
36
|
+
declare const valueEditorFor: (valueType: FieldValueType, operator: ConditionOperator) => ValueEditorKind;
|
|
37
|
+
/** Whether the value editor accepts multiple selections. */
|
|
38
|
+
declare const isMultiValue: (operator: ConditionOperator) => boolean;
|
|
39
|
+
/** Compares two ref operands for the field-matching needed by the row. */
|
|
40
|
+
declare const operandsEqual: (a: RefOperand, b: RefOperand) => boolean;
|
|
41
|
+
/** Reads the ref operand a leaf node filters on, or null for a group. */
|
|
42
|
+
declare const leafOperand: (node: ConditionNode) => RefOperand | null;
|
|
43
|
+
/** Reads the surface operator a leaf node represents. */
|
|
44
|
+
declare const leafOperator: (node: ConditionNode) => ConditionOperator | null;
|
|
45
|
+
/** Reads a leaf's value as a string (scalar editors). */
|
|
46
|
+
declare const leafValueString: (node: ConditionNode) => string;
|
|
47
|
+
/** Reads a leaf's value as a string list (multi editors). */
|
|
48
|
+
declare const leafValueList: (node: ConditionNode) => string[];
|
|
49
|
+
type BuildLeafArgs = {
|
|
50
|
+
operand: RefOperand;
|
|
51
|
+
operator: ConditionOperator;
|
|
52
|
+
value: string | string[];
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Rebuilds a leaf node from its editor state. `compare` operators
|
|
56
|
+
* produce a literal right operand; predicate operators carry their
|
|
57
|
+
* payload (or none, for `is_empty`).
|
|
58
|
+
*/
|
|
59
|
+
declare const buildLeaf: ({ operand, operator, value }: BuildLeafArgs) => ConditionNode;
|
|
60
|
+
/** A fresh leaf for a newly added row, with the field's first operator. */
|
|
61
|
+
declare const leafFromField: (field: FieldOption) => ConditionNode;
|
|
62
|
+
/** Finds the field a leaf node targets, or null when none matches. */
|
|
63
|
+
declare const fieldForNode: (node: ConditionNode, fields: FieldOption[]) => FieldOption | null;
|
|
64
|
+
/** Normalizes the controlled `value` prop into a concrete group root. */
|
|
65
|
+
declare const asGroup: (value: ConditionNode | null) => GroupNode;
|
|
66
|
+
declare const replaceChild: (group: GroupNode, index: number, child: ConditionNode) => GroupNode;
|
|
67
|
+
declare const removeChild: (group: GroupNode, index: number) => GroupNode;
|
|
68
|
+
declare const appendChild: (group: GroupNode, child: ConditionNode) => GroupNode;
|
|
69
|
+
//#endregion
|
|
70
|
+
export { CONDITION_OPERATORS, ConditionOperator, FieldOption, FieldOptionChoice, FieldValueType, ValueEditorKind, appendChild, asGroup, buildLeaf, fieldForNode, isConditionOperator, isMultiValue, leafFromField, leafOperand, leafOperator, leafValueList, leafValueString, operandsEqual, operatorsFor, removeChild, replaceChild, valueEditorFor };
|