@pilotiq/pilotiq 0.2.0 → 0.4.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +2 -2
- package/dist/actions/Action.d.ts +25 -0
- package/dist/actions/Action.d.ts.map +1 -1
- package/dist/actions/Action.js +25 -0
- package/dist/actions/Action.js.map +1 -1
- package/dist/elements/dispatchForm.d.ts +0 -14
- package/dist/elements/dispatchForm.d.ts.map +1 -1
- package/dist/elements/dispatchForm.js +28 -0
- package/dist/elements/dispatchForm.js.map +1 -1
- package/dist/fields/BuilderField.d.ts +27 -1
- package/dist/fields/BuilderField.d.ts.map +1 -1
- package/dist/fields/BuilderField.js +36 -1
- package/dist/fields/BuilderField.js.map +1 -1
- package/dist/fields/FileUploadField.d.ts +65 -0
- package/dist/fields/FileUploadField.d.ts.map +1 -1
- package/dist/fields/FileUploadField.js +72 -0
- package/dist/fields/FileUploadField.js.map +1 -1
- package/dist/fields/RepeaterField.d.ts +34 -1
- package/dist/fields/RepeaterField.d.ts.map +1 -1
- package/dist/fields/RepeaterField.js +43 -1
- package/dist/fields/RepeaterField.js.map +1 -1
- package/dist/fields/RowButton.d.ts +9 -2
- package/dist/fields/RowButton.d.ts.map +1 -1
- package/dist/fields/TextField.d.ts +106 -0
- package/dist/fields/TextField.d.ts.map +1 -1
- package/dist/fields/TextField.js +115 -0
- package/dist/fields/TextField.js.map +1 -1
- package/dist/filters/queryBuilder/Constraint.d.ts +1 -1
- package/dist/filters/queryBuilder/Constraint.d.ts.map +1 -1
- package/dist/filters/queryBuilder/TextConstraint.d.ts.map +1 -1
- package/dist/filters/queryBuilder/TextConstraint.js +2 -3
- package/dist/filters/queryBuilder/TextConstraint.js.map +1 -1
- package/dist/orm/modelDefaults.d.ts +1 -1
- package/dist/orm/modelDefaults.d.ts.map +1 -1
- package/dist/pageData.d.ts +11 -0
- package/dist/pageData.d.ts.map +1 -1
- package/dist/pageData.js +31 -0
- package/dist/pageData.js.map +1 -1
- package/dist/react/FieldLabelSlotRegistry.d.ts +26 -0
- package/dist/react/FieldLabelSlotRegistry.d.ts.map +1 -0
- package/dist/react/FieldLabelSlotRegistry.js +16 -0
- package/dist/react/FieldLabelSlotRegistry.js.map +1 -0
- package/dist/react/SchemaRenderer.d.ts.map +1 -1
- package/dist/react/SchemaRenderer.js +120 -9
- package/dist/react/SchemaRenderer.js.map +1 -1
- package/dist/react/fields/BuilderInput.d.ts.map +1 -1
- package/dist/react/fields/BuilderInput.js +32 -3
- package/dist/react/fields/BuilderInput.js.map +1 -1
- package/dist/react/fields/FieldShell.d.ts +12 -1
- package/dist/react/fields/FieldShell.d.ts.map +1 -1
- package/dist/react/fields/FieldShell.js +5 -4
- package/dist/react/fields/FieldShell.js.map +1 -1
- package/dist/react/fields/FileUploadInput.d.ts +17 -4
- package/dist/react/fields/FileUploadInput.d.ts.map +1 -1
- package/dist/react/fields/FileUploadInput.js +204 -25
- package/dist/react/fields/FileUploadInput.js.map +1 -1
- package/dist/react/fields/RepeaterInput.d.ts.map +1 -1
- package/dist/react/fields/RepeaterInput.js +33 -2
- package/dist/react/fields/RepeaterInput.js.map +1 -1
- package/dist/react/fields/TextLikeInput.d.ts +5 -1
- package/dist/react/fields/TextLikeInput.d.ts.map +1 -1
- package/dist/react/fields/TextLikeInput.js +17 -2
- package/dist/react/fields/TextLikeInput.js.map +1 -1
- package/dist/react/fields/rowChromeButton.d.ts +24 -5
- package/dist/react/fields/rowChromeButton.d.ts.map +1 -1
- package/dist/react/fields/rowChromeButton.js +51 -8
- package/dist/react/fields/rowChromeButton.js.map +1 -1
- package/dist/react/fields/textInputControls.d.ts +47 -0
- package/dist/react/fields/textInputControls.d.ts.map +1 -0
- package/dist/react/fields/textInputControls.js +134 -0
- package/dist/react/fields/textInputControls.js.map +1 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +21 -1
- package/dist/routes.js.map +1 -1
- package/dist/schema/Alert.d.ts +58 -0
- package/dist/schema/Alert.d.ts.map +1 -1
- package/dist/schema/Alert.js +68 -1
- package/dist/schema/Alert.js.map +1 -1
- package/dist/schema/resolveSchema.d.ts.map +1 -1
- package/dist/schema/resolveSchema.js +32 -0
- package/dist/schema/resolveSchema.js.map +1 -1
- package/package.json +2 -1
- package/src/actions/Action.test.ts +47 -0
- package/src/actions/Action.ts +35 -0
- package/src/elements/dispatchForm.ts +28 -0
- package/src/fields/BuilderField.ts +38 -1
- package/src/fields/FileUploadField.test.ts +46 -0
- package/src/fields/FileUploadField.ts +90 -2
- package/src/fields/RepeaterField.ts +45 -1
- package/src/fields/RowButton.test.ts +70 -0
- package/src/fields/RowButton.ts +11 -1
- package/src/fields/TextField.test.ts +168 -0
- package/src/fields/TextField.ts +141 -1
- package/src/filters/QueryBuilderFilter.test.ts +18 -0
- package/src/filters/queryBuilder/Constraint.ts +1 -1
- package/src/filters/queryBuilder/TextConstraint.ts +5 -6
- package/src/orm/modelDefaults.ts +1 -1
- package/src/pageData.ts +33 -0
- package/src/react/FieldLabelSlotRegistry.ts +30 -0
- package/src/react/SchemaRenderer.tsx +238 -16
- package/src/react/fields/BuilderInput.tsx +37 -0
- package/src/react/fields/FieldShell.tsx +17 -2
- package/src/react/fields/FileUploadInput.tsx +516 -85
- package/src/react/fields/RepeaterInput.tsx +39 -0
- package/src/react/fields/TextLikeInput.tsx +22 -2
- package/src/react/fields/rowChromeButton.tsx +102 -6
- package/src/react/fields/textInputControls.tsx +238 -0
- package/src/react/index.ts +1 -0
- package/src/routes.ts +21 -1
- package/src/schema/Alert.test.ts +46 -0
- package/src/schema/Alert.ts +90 -8
- package/src/schema/resolveSchema.ts +32 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props the field label slot component receives from `SchemaRenderer`.
|
|
4
|
+
* For AI actions this carries the field name, action list, and the
|
|
5
|
+
* pre-composed agent-run base URL stamped by `tagFieldAiUrls`.
|
|
6
|
+
*/
|
|
7
|
+
export interface FieldLabelSlotProps {
|
|
8
|
+
fieldName: string;
|
|
9
|
+
actions: Array<{
|
|
10
|
+
slug: string;
|
|
11
|
+
label: string;
|
|
12
|
+
icon?: string;
|
|
13
|
+
}>;
|
|
14
|
+
agentRunBase: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Register a component to render inline next to every field label that
|
|
18
|
+
* has `aiActions` stamped on its meta. Called once at boot by a plugin's
|
|
19
|
+
* `register(panel)` step (e.g. `@pilotiq-pro/ai`). No-op when no plugin
|
|
20
|
+
* registers — `getFieldLabelSlot()` returns `null` and `SchemaRenderer`
|
|
21
|
+
* skips the slot.
|
|
22
|
+
*/
|
|
23
|
+
export declare function registerFieldLabelSlot(C: ComponentType<FieldLabelSlotProps>): void;
|
|
24
|
+
/** Returns the registered field label slot component, or `null`. */
|
|
25
|
+
export declare function getFieldLabelSlot(): ComponentType<FieldLabelSlotProps> | null;
|
|
26
|
+
//# sourceMappingURL=FieldLabelSlotRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldLabelSlotRegistry.d.ts","sourceRoot":"","sources":["../../src/react/FieldLabelSlotRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAK,MAAM,CAAA;IACpB,OAAO,EAAO,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACnE,YAAY,EAAE,MAAM,CAAA;CACrB;AAID;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,aAAa,CAAC,mBAAmB,CAAC,GAAG,IAAI,CAElF;AAED,oEAAoE;AACpE,wBAAgB,iBAAiB,IAAI,aAAa,CAAC,mBAAmB,CAAC,GAAG,IAAI,CAE7E"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
let _component = null;
|
|
2
|
+
/**
|
|
3
|
+
* Register a component to render inline next to every field label that
|
|
4
|
+
* has `aiActions` stamped on its meta. Called once at boot by a plugin's
|
|
5
|
+
* `register(panel)` step (e.g. `@pilotiq-pro/ai`). No-op when no plugin
|
|
6
|
+
* registers — `getFieldLabelSlot()` returns `null` and `SchemaRenderer`
|
|
7
|
+
* skips the slot.
|
|
8
|
+
*/
|
|
9
|
+
export function registerFieldLabelSlot(C) {
|
|
10
|
+
_component = C;
|
|
11
|
+
}
|
|
12
|
+
/** Returns the registered field label slot component, or `null`. */
|
|
13
|
+
export function getFieldLabelSlot() {
|
|
14
|
+
return _component;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=FieldLabelSlotRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldLabelSlotRegistry.js","sourceRoot":"","sources":["../../src/react/FieldLabelSlotRegistry.ts"],"names":[],"mappings":"AAaA,IAAI,UAAU,GAA8C,IAAI,CAAA;AAEhE;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,CAAqC;IAC1E,UAAU,GAAG,CAAC,CAAA;AAChB,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,iBAAiB;IAC/B,OAAO,UAAU,CAAA;AACnB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SchemaRenderer.d.ts","sourceRoot":"","sources":["../../src/react/SchemaRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAA;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"SchemaRenderer.d.ts","sourceRoot":"","sources":["../../src/react/SchemaRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAA;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AA4HvD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAG,WAAW,EAAE,CAAA;IACxB,MAAM,CAAC,EAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,eAAe,GAAG,KAAK,CAAC,YAAY,CAapF;AAieD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AAExE,KAAK,MAAM,GAAM,CAAC,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,KAAK,IAAI,CAAA;AAC7E,KAAK,QAAQ,GAAI,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;AAkDtC;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAO,MAAM,EAChB,GAAG,EAAO,MAAM,EAAE,EAClB,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAI,MAAM,EAChB,MAAM,GAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACrC,YAAY,CAAC,EAAE,QAAQ,GACtB,OAAO,CAAC,IAAI,CAAC,CAoCf;AAilFD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAC/B,KAAK,CAAC,SAAS,CAejB;AA41FD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,WAAW,EAAE,CAAA;IACvB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrC;AAED,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,mBAAmB,kDAW3E"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { getFieldRenderer } from './registry.js';
|
|
4
|
+
import { getFieldLabelSlot } from './FieldLabelSlotRegistry.js';
|
|
4
5
|
import { FormStateProvider, useFormState, FormIdContext } from './FormStateContext.js';
|
|
5
6
|
import { Checkbox } from './ui/checkbox.js';
|
|
6
7
|
import { Input } from './ui/input.js';
|
|
7
8
|
import { Popover, PopoverTrigger, PopoverContent } from './ui/popover.js';
|
|
8
9
|
import { FieldShell } from './fields/FieldShell.js';
|
|
9
10
|
import { TextLikeInput } from './fields/TextLikeInput.js';
|
|
11
|
+
import { useTextInputControls } from './fields/textInputControls.js';
|
|
10
12
|
import { SelectFieldInput } from './fields/SelectFieldInput.js';
|
|
11
13
|
import { ToggleFieldInput } from './fields/ToggleFieldInput.js';
|
|
12
14
|
import { DateFieldInput } from './fields/DateFieldInput.js';
|
|
@@ -30,7 +32,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '
|
|
|
30
32
|
import { Table as DataTable, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow, } from './ui/table.js';
|
|
31
33
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from './ui/dropdown-menu.js';
|
|
32
34
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from './ui/tooltip.js';
|
|
33
|
-
import { FilterIcon, CircleIcon, InboxIcon, GripVerticalIcon, ChevronDownIcon, CopyIcon, CheckIcon, XIcon, } from 'lucide-react';
|
|
35
|
+
import { FilterIcon, CircleIcon, InboxIcon, GripVerticalIcon, ChevronDownIcon, CopyIcon, CheckIcon, XIcon, InfoIcon, TriangleAlertIcon, CircleCheckIcon, CircleAlertIcon, } from 'lucide-react';
|
|
34
36
|
import { useNavigate } from './navigate.js';
|
|
35
37
|
import { parseDateRangeValue, encodeDateRangeValue, } from '../filters/DateRangeFilter.js';
|
|
36
38
|
import { parseMultiSelectValue, encodeMultiSelectValue, } from '../filters/MultiSelectFilter.js';
|
|
@@ -87,6 +89,15 @@ function renderField(el, index) {
|
|
|
87
89
|
if (fieldType === 'hidden') {
|
|
88
90
|
return _jsx(HiddenInput, { name: name, defaultValue: defaultValue }, index);
|
|
89
91
|
}
|
|
92
|
+
// Field label slot — rendered next to the label when a plugin registered
|
|
93
|
+
// a component via registerFieldLabelSlot() and the field has aiActions +
|
|
94
|
+
// _agentRunBase stamped on its meta (set by tagFieldAiUrls in pageData).
|
|
95
|
+
const LabelSlot = getFieldLabelSlot();
|
|
96
|
+
const aiActions = Array.isArray(el['aiActions']) ? el['aiActions'] : undefined;
|
|
97
|
+
const agentRunBase = typeof el['_agentRunBase'] === 'string' ? el['_agentRunBase'] : undefined;
|
|
98
|
+
const labelSlot = (LabelSlot && aiActions?.length && agentRunBase)
|
|
99
|
+
? _jsx(LabelSlot, { fieldName: name, actions: aiActions, agentRunBase: agentRunBase })
|
|
100
|
+
: undefined;
|
|
90
101
|
const autofocus = el['autofocus'] === true;
|
|
91
102
|
const extraInput = el['extraInputAttributes'];
|
|
92
103
|
const common = {
|
|
@@ -104,10 +115,40 @@ function renderField(el, index) {
|
|
|
104
115
|
// switch so consumers can override built-ins too if they want.
|
|
105
116
|
const Custom = getFieldRenderer(fieldType);
|
|
106
117
|
if (Custom) {
|
|
107
|
-
return (_jsx(FieldShell, { el: el, name: name, label: label, required: required, children: _jsx(Custom, { el: el, name: name, defaultValue: defaultValue, required: required, disabled: disabled, placeholder: placeholder }) }, index));
|
|
118
|
+
return (_jsx(FieldShell, { el: el, name: name, label: label, required: required, labelSlot: labelSlot, children: _jsx(Custom, { el: el, name: name, defaultValue: defaultValue, required: required, disabled: disabled, placeholder: placeholder }) }, index));
|
|
119
|
+
}
|
|
120
|
+
// TextField (and slug) rich affordances live in a dedicated shell so
|
|
121
|
+
// `useTextInputControls` can hold reveal-toggle / mask state via React
|
|
122
|
+
// hooks (renderField itself is a plain function, hooks would violate
|
|
123
|
+
// rules-of-hooks here).
|
|
124
|
+
if (fieldType === 'text' || fieldType === 'slug') {
|
|
125
|
+
return (_jsx(TextFieldShell, { el: el, name: name, label: label, required: required, common: common, labelSlot: labelSlot }, index));
|
|
108
126
|
}
|
|
109
127
|
const input = renderFieldInput(fieldType, el, name, defaultValue, defaultStr, common, disabled, required, placeholder);
|
|
110
|
-
return (_jsx(FieldShell, { el: el, name: name, label: label, required: required, children: input }, index));
|
|
128
|
+
return (_jsx(FieldShell, { el: el, name: name, label: label, required: required, labelSlot: labelSlot, children: input }, index));
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Component-shape TextField renderer — wraps the input shell so we can
|
|
132
|
+
* use `useTextInputControls()` (which holds the eye-toggle / mask state).
|
|
133
|
+
* Keeps `renderField` itself hook-free.
|
|
134
|
+
*/
|
|
135
|
+
function TextFieldShell({ el, name, label, required, common, labelSlot, }) {
|
|
136
|
+
const controls = useTextInputControls(el, name, (m) => renderElement(m, 0));
|
|
137
|
+
// Build the input with all the new HTML attrs (inputMode /
|
|
138
|
+
// autocapitalize / list / maxLength + the password/text type from
|
|
139
|
+
// the controls hook).
|
|
140
|
+
const textExtra = {};
|
|
141
|
+
if (el['maxLength'] !== undefined)
|
|
142
|
+
textExtra['maxLength'] = Number(el['maxLength']);
|
|
143
|
+
if (el['inputMode'] !== undefined)
|
|
144
|
+
textExtra['inputMode'] = String(el['inputMode']);
|
|
145
|
+
if (el['autocapitalize'] !== undefined)
|
|
146
|
+
textExtra['autoCapitalize'] = String(el['autocapitalize']);
|
|
147
|
+
if (Array.isArray(el['datalist']))
|
|
148
|
+
textExtra['list'] = `${name}__datalist`;
|
|
149
|
+
const datalist = Array.isArray(el['datalist']) ? el['datalist'] : undefined;
|
|
150
|
+
const input = (_jsxs(_Fragment, { children: [_jsx(TextLikeInput, { el: el, name: name, common: common, type: controls.type, extraProps: textExtra, multiline: false, applyMask: controls.applyMask }), datalist && (_jsx("datalist", { id: `${name}__datalist`, children: datalist.map((v, i) => _jsx("option", { value: v }, i)) }))] }));
|
|
151
|
+
return (_jsx(FieldShell, { el: el, name: name, label: label, required: required, before: controls.before, after: controls.after, labelSlot: labelSlot, children: input }));
|
|
111
152
|
}
|
|
112
153
|
function renderFieldInput(fieldType, el, name, defaultValue, defaultStr, common, disabled, required, placeholder) {
|
|
113
154
|
switch (fieldType) {
|
|
@@ -178,7 +219,13 @@ function renderFieldInput(fieldType, el, name, defaultValue, defaultStr, common,
|
|
|
178
219
|
return (_jsx(TagsInput, { name: name, defaultValue: defaultValue, disabled: disabled, placeholder: placeholder, suggestions: suggestions, separator: separator, splitKeys: splitKeys, maxTags: maxTags, reorderable: reorderable }));
|
|
179
220
|
}
|
|
180
221
|
case 'fileUpload': {
|
|
181
|
-
return (_jsx(FileUploadInput, { name: name, defaultValue: defaultValue, disabled: disabled, accept: el['accept'], maxSize: typeof el['maxSize'] === 'number' ? el['maxSize'] : undefined, multiple: Boolean(el['multiple']), preview: el['preview'] !== false, directory: typeof el['directory'] === 'string' ? el['directory'] : undefined, uploadUrl: typeof el['uploadUrl'] === 'string' ? el['uploadUrl'] : undefined
|
|
222
|
+
return (_jsx(FileUploadInput, { name: name, defaultValue: defaultValue, disabled: disabled, accept: el['accept'], maxSize: typeof el['maxSize'] === 'number' ? el['maxSize'] : undefined, multiple: Boolean(el['multiple']), preview: el['preview'] !== false, directory: typeof el['directory'] === 'string' ? el['directory'] : undefined, uploadUrl: typeof el['uploadUrl'] === 'string' ? el['uploadUrl'] : undefined, downloadable: Boolean(el['downloadable']), openable: Boolean(el['openable']), reorderable: Boolean(el['reorderable']), appendFiles: Boolean(el['appendFiles']), panelLayout: el['panelLayout'] === 'grid' ? 'grid'
|
|
223
|
+
: el['panelLayout'] === 'integrated' ? 'integrated'
|
|
224
|
+
: 'list', ...(el['automaticallyResize'] && typeof el['automaticallyResize'] === 'object'
|
|
225
|
+
? { automaticallyResize: el['automaticallyResize'] }
|
|
226
|
+
: {}), imageEditor: Boolean(el['imageEditor']), circleCropper: Boolean(el['circleCropper']), automaticallyCropImagesToAspectRatio: Boolean(el['automaticallyCropImagesToAspectRatio']), ...(Array.isArray(el['imageEditorAspectRatioOptions'])
|
|
227
|
+
? { imageEditorAspectRatioOptions: el['imageEditorAspectRatioOptions'] }
|
|
228
|
+
: {}) }));
|
|
182
229
|
}
|
|
183
230
|
case 'markdown': {
|
|
184
231
|
const toolbarButtons = el['toolbarButtons'] ?? [];
|
|
@@ -403,6 +450,9 @@ function ActionModalDialog({ trigger, meta, ids, initialValues = {}, open: contr
|
|
|
403
450
|
const dispatchUrl = meta['dispatchUrl'];
|
|
404
451
|
const fields = (meta.children ?? []);
|
|
405
452
|
const hasForm = fields.length > 0;
|
|
453
|
+
// Filament v5 — auxiliary Elements stamped by the resolver between
|
|
454
|
+
// the body and the footer (Alert / Text / Heading / Action / …).
|
|
455
|
+
const contentFooter = (meta['modalContentFooter'] ?? []);
|
|
406
456
|
const heading = modal?.heading ?? confirm?.title ?? (hasForm ? String(meta['label'] ?? 'Submit') : 'Are you sure?');
|
|
407
457
|
const description = modal?.description ?? confirm?.message;
|
|
408
458
|
const submitLabel = modal?.submitLabel ?? (destructive ? 'Delete' : (hasForm ? 'Submit' : 'Confirm'));
|
|
@@ -534,7 +584,7 @@ function ActionModalDialog({ trigger, meta, ids, initialValues = {}, open: contr
|
|
|
534
584
|
if (!o)
|
|
535
585
|
reset();
|
|
536
586
|
setOpen(o);
|
|
537
|
-
}, children: _jsxs(DialogContent, { className: popupClass, children: [showCloseButton && (_jsx("button", { type: "button", "aria-label": "Close", onClick: () => setOpen(false), className: "absolute top-3 right-3 z-20 inline-flex items-center justify-center rounded-md h-8 w-8 text-muted-foreground hover:bg-accent hover:text-accent-foreground", children: _jsx(XIcon, { className: "size-4" }) })), _jsxs("form", { ref: formRef, onSubmit: onSubmit, className: formCls, children: [_jsxs(DialogHeader, { className: headerCls, children: [_jsxs(DialogTitle, { className: modal?.icon ? 'flex items-center gap-2' : undefined, children: [HeaderIcon && (_jsx(HeaderIcon, { "aria-hidden": true, className: `size-5 shrink-0 ${iconColorClass ?? ''}`.trim() })), _jsx("span", { children: heading })] }), description && _jsx(DialogDescription, { children: description })] }), hasForm && (
|
|
587
|
+
}, children: _jsxs(DialogContent, { className: popupClass, children: [showCloseButton && (_jsx("button", { type: "button", "aria-label": "Close", onClick: () => setOpen(false), className: "absolute top-3 right-3 z-20 inline-flex items-center justify-center rounded-md h-8 w-8 text-muted-foreground hover:bg-accent hover:text-accent-foreground", children: _jsx(XIcon, { className: "size-4" }) })), _jsxs("form", { ref: formRef, onSubmit: onSubmit, className: formCls, children: [_jsxs(DialogHeader, { className: headerCls, children: [_jsxs(DialogTitle, { className: modal?.icon ? 'flex items-center gap-2' : undefined, children: [HeaderIcon && (_jsx(HeaderIcon, { "aria-hidden": true, className: `size-5 shrink-0 ${iconColorClass ?? ''}`.trim() })), _jsx("span", { children: heading })] }), description && _jsx(DialogDescription, { children: description })] }), (hasForm || contentFooter.length > 0) && (_jsxs("div", { className: `flex flex-col gap-3 py-2 ${bodyCls}`.trim(), children: [fields.map((f, i) => renderFormChild(f, i, initialValues, errors)), contentFooter.map((c, i) => renderElement(c, fields.length + i))] })), !hasForm && contentFooter.length === 0 && stickyMode && _jsx("div", { className: bodyCls }), serverError && (_jsx("p", { className: `py-2 text-sm text-destructive ${stickyMode ? 'px-6' : ''}`.trim(), children: serverError })), _jsxs(DialogFooter, { className: footerCls, children: [_jsx("button", { type: "button", onClick: () => setOpen(false), className: cancelClass, children: cancelLabel }), _jsx("button", { type: "submit", disabled: submitting, autoFocus: submitAutofocus, className: confirmClass, children: submitting ? 'Working…' : submitLabel })] })] })] }) })] }));
|
|
538
588
|
}
|
|
539
589
|
/**
|
|
540
590
|
* Confirm-style dialog wrapping an action's button. The trigger button is
|
|
@@ -1460,6 +1510,70 @@ function EntryCopyButton({ text, label }) {
|
|
|
1460
1510
|
};
|
|
1461
1511
|
return (_jsx("button", { type: "button", onClick: handleClick, "aria-label": label, title: label, className: "inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-muted", children: copied ? _jsx(CheckIcon, { className: "size-3.5" }) : _jsx(CopyIcon, { className: "size-3.5" }) }));
|
|
1462
1512
|
}
|
|
1513
|
+
// ─── Alert renderer ─────────────────────────────────────────
|
|
1514
|
+
//
|
|
1515
|
+
// Owns dismissal state (per-mount + optional localStorage persistence)
|
|
1516
|
+
// + icon dispatch + footer-actions alignment. Lifted out of the inline
|
|
1517
|
+
// `case 'alert'` branch when Alert gained `dismissible() / iconColor() /
|
|
1518
|
+
// footerActionsAlignment()` setters — those need component-local state
|
|
1519
|
+
// (the dismiss button, the persisted-dismissal hydration on mount), and
|
|
1520
|
+
// inlining the hooks under a switch arm is fragile.
|
|
1521
|
+
const ALERT_TYPE_ICONS = {
|
|
1522
|
+
info: InfoIcon,
|
|
1523
|
+
warning: TriangleAlertIcon,
|
|
1524
|
+
success: CircleCheckIcon,
|
|
1525
|
+
danger: CircleAlertIcon,
|
|
1526
|
+
};
|
|
1527
|
+
const ALERT_TYPE_DEFAULT_ICON_COLOR = {
|
|
1528
|
+
info: 'info',
|
|
1529
|
+
warning: 'warning',
|
|
1530
|
+
success: 'success',
|
|
1531
|
+
danger: 'destructive',
|
|
1532
|
+
};
|
|
1533
|
+
const ALERT_ACTIONS_ALIGNMENT = {
|
|
1534
|
+
start: 'justify-start',
|
|
1535
|
+
center: 'justify-center',
|
|
1536
|
+
end: 'justify-end',
|
|
1537
|
+
};
|
|
1538
|
+
function alertPersistKey(persistKey) {
|
|
1539
|
+
return `pilotiq.alert.${persistKey}`;
|
|
1540
|
+
}
|
|
1541
|
+
function AlertRenderer(props) {
|
|
1542
|
+
const { alertType, content, title, dismissible, persistDismissal, iconColor, actionsAlignment, footer, } = props;
|
|
1543
|
+
const [dismissed, setDismissed] = useState(false);
|
|
1544
|
+
// Hydrate persisted-dismissal on first paint. `useState(false)` keeps
|
|
1545
|
+
// SSR + first client paint identical (Hydration safe); the effect
|
|
1546
|
+
// flips to dismissed if localStorage has the flag set.
|
|
1547
|
+
useEffect(() => {
|
|
1548
|
+
if (!persistDismissal)
|
|
1549
|
+
return;
|
|
1550
|
+
if (typeof window === 'undefined')
|
|
1551
|
+
return;
|
|
1552
|
+
try {
|
|
1553
|
+
if (window.localStorage.getItem(alertPersistKey(persistDismissal)) === '1') {
|
|
1554
|
+
setDismissed(true);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
catch { /* localStorage blocked (Safari ITP / SSR) — render visible */ }
|
|
1558
|
+
}, [persistDismissal]);
|
|
1559
|
+
if (dismissed)
|
|
1560
|
+
return null;
|
|
1561
|
+
const styles = alertStyles[alertType] ?? alertStyles['info'];
|
|
1562
|
+
const Icon = ALERT_TYPE_ICONS[alertType] ?? InfoIcon;
|
|
1563
|
+
const iconColorKey = iconColor ?? ALERT_TYPE_DEFAULT_ICON_COLOR[alertType] ?? 'info';
|
|
1564
|
+
const iconColorCls = TEXT_COLOR_CLASSES[iconColorKey] ?? '';
|
|
1565
|
+
const alignCls = ALERT_ACTIONS_ALIGNMENT[actionsAlignment ?? 'start'] ?? 'justify-start';
|
|
1566
|
+
const handleDismiss = () => {
|
|
1567
|
+
setDismissed(true);
|
|
1568
|
+
if (persistDismissal && typeof window !== 'undefined') {
|
|
1569
|
+
try {
|
|
1570
|
+
window.localStorage.setItem(alertPersistKey(persistDismissal), '1');
|
|
1571
|
+
}
|
|
1572
|
+
catch { /* localStorage blocked — dismiss is per-mount only */ }
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
return (_jsxs("div", { className: `relative rounded-lg border p-4 ${styles} ${dismissible ? 'pr-9' : ''}`, children: [_jsxs("div", { className: "flex gap-3", children: [_jsx(Icon, { className: `size-5 shrink-0 mt-0.5 ${iconColorCls}`, "aria-hidden": "true" }), _jsxs("div", { className: "flex-1 min-w-0", children: [title !== undefined && _jsx("p", { className: "font-medium mb-1", children: title }), _jsx("p", { className: "text-sm", children: content }), footer.length > 0 && (_jsx("div", { className: `flex items-center gap-2 mt-3 ${alignCls}`, children: footer }))] })] }), dismissible && (_jsx("button", { type: "button", onClick: handleDismiss, "aria-label": "Dismiss", title: "Dismiss", className: "absolute top-3 right-3 inline-flex h-6 w-6 items-center justify-center rounded opacity-70 hover:opacity-100 transition-opacity", children: _jsx(XIcon, { className: "size-4", "aria-hidden": "true" }) }))] }));
|
|
1576
|
+
}
|
|
1463
1577
|
function renderElement(el, index) {
|
|
1464
1578
|
switch (el.type) {
|
|
1465
1579
|
case 'text':
|
|
@@ -1508,11 +1622,8 @@ function renderElement(el, index) {
|
|
|
1508
1622
|
return (_jsxs("div", { className: "flex items-start justify-between gap-4", children: [titleBlock, _jsx("div", { className: "flex items-center gap-2 shrink-0", children: headerActions.map((a, i) => renderActionLike(a, i)) })] }, index));
|
|
1509
1623
|
}
|
|
1510
1624
|
case 'alert': {
|
|
1511
|
-
const alertType = String(el['alertType'] ?? 'info');
|
|
1512
|
-
const styles = alertStyles[alertType] ?? alertStyles['info'];
|
|
1513
|
-
const title = el['title'] ? String(el['title']) : undefined;
|
|
1514
1625
|
const footer = (el.children ?? []).filter(c => c.type === 'action' || c.type === 'actionGroup');
|
|
1515
|
-
return (
|
|
1626
|
+
return (_jsx(AlertRenderer, { alertType: String(el['alertType'] ?? 'info'), content: String(el['content'] ?? ''), ...(el['title'] !== undefined ? { title: String(el['title']) } : {}), ...(el['dismissible'] ? { dismissible: Boolean(el['dismissible']) } : {}), ...(el['persistDismissal'] !== undefined ? { persistDismissal: String(el['persistDismissal']) } : {}), ...(el['iconColor'] !== undefined ? { iconColor: String(el['iconColor']) } : {}), ...(el['actionsAlignment'] !== undefined ? { actionsAlignment: String(el['actionsAlignment']) } : {}), footer: footer.map((a, i) => renderActionLike(a, i)) }, index));
|
|
1516
1627
|
}
|
|
1517
1628
|
case 'emptyState': {
|
|
1518
1629
|
const heading = String(el['heading'] ?? '');
|