@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.
Files changed (118) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +14 -0
  3. package/CLAUDE.md +2 -2
  4. package/dist/actions/Action.d.ts +25 -0
  5. package/dist/actions/Action.d.ts.map +1 -1
  6. package/dist/actions/Action.js +25 -0
  7. package/dist/actions/Action.js.map +1 -1
  8. package/dist/elements/dispatchForm.d.ts +0 -14
  9. package/dist/elements/dispatchForm.d.ts.map +1 -1
  10. package/dist/elements/dispatchForm.js +28 -0
  11. package/dist/elements/dispatchForm.js.map +1 -1
  12. package/dist/fields/BuilderField.d.ts +27 -1
  13. package/dist/fields/BuilderField.d.ts.map +1 -1
  14. package/dist/fields/BuilderField.js +36 -1
  15. package/dist/fields/BuilderField.js.map +1 -1
  16. package/dist/fields/FileUploadField.d.ts +65 -0
  17. package/dist/fields/FileUploadField.d.ts.map +1 -1
  18. package/dist/fields/FileUploadField.js +72 -0
  19. package/dist/fields/FileUploadField.js.map +1 -1
  20. package/dist/fields/RepeaterField.d.ts +34 -1
  21. package/dist/fields/RepeaterField.d.ts.map +1 -1
  22. package/dist/fields/RepeaterField.js +43 -1
  23. package/dist/fields/RepeaterField.js.map +1 -1
  24. package/dist/fields/RowButton.d.ts +9 -2
  25. package/dist/fields/RowButton.d.ts.map +1 -1
  26. package/dist/fields/TextField.d.ts +106 -0
  27. package/dist/fields/TextField.d.ts.map +1 -1
  28. package/dist/fields/TextField.js +115 -0
  29. package/dist/fields/TextField.js.map +1 -1
  30. package/dist/filters/queryBuilder/Constraint.d.ts +1 -1
  31. package/dist/filters/queryBuilder/Constraint.d.ts.map +1 -1
  32. package/dist/filters/queryBuilder/TextConstraint.d.ts.map +1 -1
  33. package/dist/filters/queryBuilder/TextConstraint.js +2 -3
  34. package/dist/filters/queryBuilder/TextConstraint.js.map +1 -1
  35. package/dist/orm/modelDefaults.d.ts +1 -1
  36. package/dist/orm/modelDefaults.d.ts.map +1 -1
  37. package/dist/pageData.d.ts +11 -0
  38. package/dist/pageData.d.ts.map +1 -1
  39. package/dist/pageData.js +31 -0
  40. package/dist/pageData.js.map +1 -1
  41. package/dist/react/FieldLabelSlotRegistry.d.ts +26 -0
  42. package/dist/react/FieldLabelSlotRegistry.d.ts.map +1 -0
  43. package/dist/react/FieldLabelSlotRegistry.js +16 -0
  44. package/dist/react/FieldLabelSlotRegistry.js.map +1 -0
  45. package/dist/react/SchemaRenderer.d.ts.map +1 -1
  46. package/dist/react/SchemaRenderer.js +120 -9
  47. package/dist/react/SchemaRenderer.js.map +1 -1
  48. package/dist/react/fields/BuilderInput.d.ts.map +1 -1
  49. package/dist/react/fields/BuilderInput.js +32 -3
  50. package/dist/react/fields/BuilderInput.js.map +1 -1
  51. package/dist/react/fields/FieldShell.d.ts +12 -1
  52. package/dist/react/fields/FieldShell.d.ts.map +1 -1
  53. package/dist/react/fields/FieldShell.js +5 -4
  54. package/dist/react/fields/FieldShell.js.map +1 -1
  55. package/dist/react/fields/FileUploadInput.d.ts +17 -4
  56. package/dist/react/fields/FileUploadInput.d.ts.map +1 -1
  57. package/dist/react/fields/FileUploadInput.js +204 -25
  58. package/dist/react/fields/FileUploadInput.js.map +1 -1
  59. package/dist/react/fields/RepeaterInput.d.ts.map +1 -1
  60. package/dist/react/fields/RepeaterInput.js +33 -2
  61. package/dist/react/fields/RepeaterInput.js.map +1 -1
  62. package/dist/react/fields/TextLikeInput.d.ts +5 -1
  63. package/dist/react/fields/TextLikeInput.d.ts.map +1 -1
  64. package/dist/react/fields/TextLikeInput.js +17 -2
  65. package/dist/react/fields/TextLikeInput.js.map +1 -1
  66. package/dist/react/fields/rowChromeButton.d.ts +24 -5
  67. package/dist/react/fields/rowChromeButton.d.ts.map +1 -1
  68. package/dist/react/fields/rowChromeButton.js +51 -8
  69. package/dist/react/fields/rowChromeButton.js.map +1 -1
  70. package/dist/react/fields/textInputControls.d.ts +47 -0
  71. package/dist/react/fields/textInputControls.d.ts.map +1 -0
  72. package/dist/react/fields/textInputControls.js +134 -0
  73. package/dist/react/fields/textInputControls.js.map +1 -0
  74. package/dist/react/index.d.ts +1 -0
  75. package/dist/react/index.d.ts.map +1 -1
  76. package/dist/react/index.js +1 -0
  77. package/dist/react/index.js.map +1 -1
  78. package/dist/routes.d.ts.map +1 -1
  79. package/dist/routes.js +21 -1
  80. package/dist/routes.js.map +1 -1
  81. package/dist/schema/Alert.d.ts +58 -0
  82. package/dist/schema/Alert.d.ts.map +1 -1
  83. package/dist/schema/Alert.js +68 -1
  84. package/dist/schema/Alert.js.map +1 -1
  85. package/dist/schema/resolveSchema.d.ts.map +1 -1
  86. package/dist/schema/resolveSchema.js +32 -0
  87. package/dist/schema/resolveSchema.js.map +1 -1
  88. package/package.json +2 -1
  89. package/src/actions/Action.test.ts +47 -0
  90. package/src/actions/Action.ts +35 -0
  91. package/src/elements/dispatchForm.ts +28 -0
  92. package/src/fields/BuilderField.ts +38 -1
  93. package/src/fields/FileUploadField.test.ts +46 -0
  94. package/src/fields/FileUploadField.ts +90 -2
  95. package/src/fields/RepeaterField.ts +45 -1
  96. package/src/fields/RowButton.test.ts +70 -0
  97. package/src/fields/RowButton.ts +11 -1
  98. package/src/fields/TextField.test.ts +168 -0
  99. package/src/fields/TextField.ts +141 -1
  100. package/src/filters/QueryBuilderFilter.test.ts +18 -0
  101. package/src/filters/queryBuilder/Constraint.ts +1 -1
  102. package/src/filters/queryBuilder/TextConstraint.ts +5 -6
  103. package/src/orm/modelDefaults.ts +1 -1
  104. package/src/pageData.ts +33 -0
  105. package/src/react/FieldLabelSlotRegistry.ts +30 -0
  106. package/src/react/SchemaRenderer.tsx +238 -16
  107. package/src/react/fields/BuilderInput.tsx +37 -0
  108. package/src/react/fields/FieldShell.tsx +17 -2
  109. package/src/react/fields/FileUploadInput.tsx +516 -85
  110. package/src/react/fields/RepeaterInput.tsx +39 -0
  111. package/src/react/fields/TextLikeInput.tsx +22 -2
  112. package/src/react/fields/rowChromeButton.tsx +102 -6
  113. package/src/react/fields/textInputControls.tsx +238 -0
  114. package/src/react/index.ts +1 -0
  115. package/src/routes.ts +21 -1
  116. package/src/schema/Alert.test.ts +46 -0
  117. package/src/schema/Alert.ts +90 -8
  118. 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;AAyHvD;;;;;;;;;;;;;;;;;;;;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;AAqXD,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;AAk+ED;;;;;;;;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
+ {"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 && (_jsx("div", { className: `flex flex-col gap-3 py-2 ${bodyCls}`.trim(), children: fields.map((f, i) => renderFormChild(f, i, initialValues, errors)) })), !hasForm && 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 })] })] })] }) })] }));
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 (_jsxs("div", { className: `rounded-lg border p-4 ${styles}`, children: [title && _jsx("p", { className: "font-medium mb-1", children: title }), _jsx("p", { className: "text-sm", children: String(el['content'] ?? '') }), footer.length > 0 && (_jsx("div", { className: "flex items-center gap-2 mt-3", children: footer.map((a, i) => renderActionLike(a, i)) }))] }, index));
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'] ?? '');