@archduck/gst-forms 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Alan Aydelott
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,429 @@
1
+ # gst-forms
2
+
3
+ Form plugin for `gst-core`. Adds field definitions, buttons, actions, validation, lifecycle hooks, and dirty tracking on top of the generic rendering engine.
4
+
5
+ **gst** stands for **Grand Schema Things** -- a play on "grand scheme of things." Forms are defined as schemas (JSON configurations) rather than code, and this library handles the form-specific concerns that sit on top of that config-driven foundation.
6
+
7
+ ```
8
+ npm install @archduck/gst-forms
9
+ ```
10
+
11
+ ```js
12
+ import { mountForm, loadJsonConfig } from '@archduck/gst-forms'
13
+ import '@archduck/gst-forms/style.css' // structural layout (not theming)
14
+ ```
15
+
16
+ The stylesheet uses `em` units throughout, so the entire form scales proportionally when you set `font-size` on the form container.
17
+
18
+ ---
19
+
20
+ ## Why forms need their own layer
21
+
22
+ Forms look simple. A few inputs, a submit button, done. Then you need to know whether the user changed anything. Then you need to validate before saving. Then you need different behavior for creating a new record vs updating an existing one. Then you need confirmation dialogs when the user navigates away with unsaved changes. Then delete needs its own confirmation. Then you want hooks that fire before and after each action so you can log, audit, or transform data on the way through.
23
+
24
+ gst-forms encodes that state machine as a plugin for gst-core. You declare fields, buttons, and hooks in config. The engine manages dirty tracking, validation, action lifecycles, confirmation dialogs, and state transitions. Your application code handles one thing: what happens when the user clicks save.
25
+
26
+ ```js
27
+ mountForm(document.getElementById('app'), form, {
28
+ onAction: async ({ action, record }) => {
29
+ if (action === 'save') await api.save(record)
30
+ }
31
+ })
32
+ ```
33
+
34
+ Everything between the button click and that callback -- validation, before/after hooks, dirty state reset, error handling -- is the engine's job.
35
+
36
+ ---
37
+
38
+ ## Quick start
39
+
40
+ Install gst-forms (pulls in gst-core and gst-compose automatically):
41
+
42
+ ```bash
43
+ npm install @archduck/gst-forms
44
+ ```
45
+
46
+ Create a form from three JSON files and a few lines of JS:
47
+
48
+ ```
49
+ my-app/
50
+ public/
51
+ forms/
52
+ UserView/
53
+ fields.json
54
+ layout.json
55
+ buttons.json
56
+ src/
57
+ main.js
58
+ index.html
59
+ ```
60
+
61
+ **fields.json** -- define your fields:
62
+
63
+ ```json
64
+ {
65
+ "username": { "label": "Username", "required": true },
66
+ "email": { "type": "email", "label": "Email" }
67
+ }
68
+ ```
69
+
70
+ **layout.json** -- arrange fields into rows:
71
+
72
+ ```json
73
+ [["username"], ["email"]]
74
+ ```
75
+
76
+ **buttons.json** -- pick from built-in buttons:
77
+
78
+ ```json
79
+ ["save", "cancel"]
80
+ ```
81
+
82
+ **main.js** -- load the config and mount:
83
+
84
+ ```js
85
+ import { mountForm, loadJsonConfig } from '@archduck/gst-forms'
86
+
87
+ const form = await loadJsonConfig('/forms/UserView')
88
+
89
+ mountForm(document.getElementById('app'), form, {
90
+ onAction: async ({ action, record }) => {
91
+ if (action === 'save') {
92
+ await fetch('/api/users', { method: 'POST', body: JSON.stringify(record.data) })
93
+ }
94
+ }
95
+ })
96
+ ```
97
+
98
+ That's it. The form renders with two-way data binding, dirty tracking, validation, and lifecycle hooks. No framework required.
99
+
100
+ Fields use standard HTML input types by default. Set `type` to `email`, `number`, `date`, `textarea`, `checkbox`, etc. For dropdowns, set `component` to `select` and provide `options`. For any other HTML element, use its tag name as the `component` value.
101
+
102
+ ---
103
+
104
+ ## Actions and lifecycle
105
+
106
+ ### Built-in actions
107
+
108
+ Five actions handle common form operations:
109
+
110
+ **save** -- Validates required fields, fires `beforeSave` -> `beforeCreate`/`beforeUpdate` -> your `onAction` callback -> `afterCreate`/`afterUpdate` -> `afterSave`.
111
+
112
+ **cancel** -- Confirms if dirty (configurable via `meta.confirmCancel`), then resets to baseline.
113
+
114
+ **delete** -- Confirms (configurable via `meta.confirmDelete`), marks as deleted. Save label changes to "Undo Delete."
115
+
116
+ **new** -- Confirms if dirty, clears to a blank record (or `meta.defaultRecord`).
117
+
118
+ **duplicate** -- Confirms if dirty, clones current record with key cleared.
119
+
120
+ ### Action resolution
121
+
122
+ When a button fires, the engine resolves the handler in priority order:
123
+
124
+ 1. **Default actions** (highest) -- built-in save, cancel, reset, delete, duplicate, new
125
+ 2. **Specific prop handlers** -- `onSave`, `onDelete`, etc. passed to `mountForm`
126
+ 3. **Generic `onAction` prop** -- single handler for all actions
127
+ 4. **Registry functions** (lowest) -- custom actions defined in the functions registry
128
+
129
+ ### Lifecycle hooks
130
+
131
+ Every action gets `before*` and `after*` hooks automatically. The hook name is derived from the action: `before${capitalize(actionName)}` / `after${capitalize(actionName)}`. Define a custom action named `publish` and `beforePublish`/`afterPublish` fire around it with no extra wiring.
132
+
133
+ Return `false` from any `before*` hook to cancel the action.
134
+
135
+ ```json
136
+ {
137
+ "actions": {
138
+ "hooks": {
139
+ "beforeSave": "{{validateForm}}",
140
+ "afterSave": ["{{logSave}}", "{{showToast}}"],
141
+ "onDirty": "{{enableAutoSave}}"
142
+ }
143
+ }
144
+ }
145
+ ```
146
+
147
+ Hooks for built-in actions:
148
+
149
+ - `beforeSave` / `afterSave`, `beforeCreate` / `afterCreate`, `beforeUpdate` / `afterUpdate`
150
+ - `beforeCancel` / `afterCancel`, `beforeDelete` / `afterDelete`
151
+ - `beforeNew` / `afterNew`, `beforeDuplicate` / `afterDuplicate`
152
+
153
+ ### State-transition hooks
154
+
155
+ These fire on form state changes, not around actions:
156
+
157
+ - `onChange` -- any field value changes
158
+ - `onDirty` / `onClean` -- dirty state transitions
159
+ - `onError` / `onRecover` -- validation error state transitions
160
+ - `onLoad` / `onUnload` -- form mount/unmount
161
+ - `beforeUnload` -- browser navigation when the form is dirty
162
+
163
+ Note: hooks are NOT a registry. They don't use spread syntax. They're plain objects nested under `actions.hooks`.
164
+
165
+ ---
166
+
167
+ ## Form state
168
+
169
+ The form tracks five states derived from three flags (`hasKey`, `isDirty`, `isDeleted`):
170
+
171
+ | State | hasKey | isDirty | isDeleted |
172
+ |-------|--------|---------|-----------|
173
+ | Loaded | yes | no | no |
174
+ | Editing | yes | yes | no |
175
+ | New | no | no | no |
176
+ | Creating | no | yes | no |
177
+ | Deleted | yes | no | yes |
178
+
179
+ Button visibility and the save button's label adapt to the current state automatically.
180
+
181
+ ### Built-in buttons
182
+
183
+ | Button | Shows when | Label |
184
+ |--------|-----------|-------|
185
+ | save | Always | "Save", "Create", or "Undo Delete" |
186
+ | cancel | Dirty and not deleted | "Cancel" |
187
+ | delete | Has key, not deleted | "Delete" |
188
+ | new | Has key or dirty, not deleted | "New" |
189
+ | duplicate | Has key, not deleted | "Duplicate" |
190
+
191
+ Override any button by defining it in your `buttons.json`. Reference defaults by name in a `buttonSet` array.
192
+
193
+ ---
194
+
195
+ ## Navigation guards
196
+
197
+ `mountForm()` automatically registers a dirty-form guard and wires the browser's `beforeunload` event. When the form has unsaved changes:
198
+
199
+ - Browser navigation triggers a native confirmation dialog
200
+ - In-app navigation can be guarded by calling `controller.checkGuards()` before transitioning
201
+
202
+ ```js
203
+ const ctrl = mountForm(container, form, options)
204
+
205
+ function navigate(url) {
206
+ const result = ctrl.checkGuards()
207
+ if (result !== true) {
208
+ if (!confirm(result)) return // user chose to stay
209
+ }
210
+ // proceed with navigation
211
+ }
212
+ ```
213
+
214
+ Disable the built-in guard with `meta.preventNavigationWhenDirty: false`.
215
+
216
+ ---
217
+
218
+ ## Functions
219
+
220
+ Functions in `registries.functions` receive `(context, event)` with `this` bound to a stage object. Use regular functions (not arrow functions) to access `this`.
221
+
222
+ ### Context
223
+
224
+ ```js
225
+ {
226
+ action, // current action name
227
+ record, // { key, data }
228
+ meta, // form metadata
229
+ registries, // all registries
230
+ form: {
231
+ isDirty, isSubmitting, isDeleted,
232
+ getValue(name), setValue(name, value),
233
+ setFieldError(name, msg), clearFieldError(name),
234
+ reset(), loadRecord(record),
235
+ isRequired(name), isReadonly(name)
236
+ }
237
+ }
238
+ ```
239
+
240
+ ### Stage (this)
241
+
242
+ For field functions:
243
+ - `this.value`, `this.initialValue`
244
+ - `this.setValue(val)`, `this.clearValue()`
245
+ - `this.setError(msg)`, `this.clearErrors()`
246
+ - `this.focus()`, `this.blur()`
247
+ - `this.def`, `this.path`
248
+
249
+ For button functions:
250
+ - `this.isLoading`
251
+ - `this.executeAction()`
252
+ - `this.def`, `this.path`
253
+
254
+ ### The onChange timing trap
255
+
256
+ Inside an `onChange` handler, `context.form.getValue(fieldName)` may return the old value. The reactive store hasn't flushed yet when your handler runs.
257
+
258
+ Read from `event.target.value` instead:
259
+
260
+ ```js
261
+ // Wrong -- may return stale value
262
+ export function onRoleChange(context, event) {
263
+ const role = context.form.getValue('role') // old value!
264
+ context.form.setValue('accessLevel', getMinLevel(role))
265
+ }
266
+
267
+ // Right -- read the new value from the event
268
+ export function onRoleChange(context, event) {
269
+ const role = event.target.value // current value
270
+ context.form.setValue('accessLevel', getMinLevel(role))
271
+ }
272
+ ```
273
+
274
+ Dynamic properties (`{{functionName}}`) are not affected -- they re-evaluate after the store updates, so they always see current values. The timing trap only applies to imperative onChange/onBlur handlers.
275
+
276
+ ---
277
+
278
+ ## Meta configuration
279
+
280
+ Control form behavior through `meta.json`:
281
+
282
+ ```json
283
+ {
284
+ "confirmCancel": true,
285
+ "confirmDelete": true,
286
+ "confirmDuplicate": true,
287
+ "confirmNew": true,
288
+ "clearHiddenFields": true,
289
+ "preventNavigationWhenDirty": true,
290
+ "submitOnEnter": true,
291
+ "debug": false,
292
+ "recordKeyPath": "key",
293
+ "recordDataPath": "data",
294
+ "readonly": [],
295
+ "systemFields": []
296
+ }
297
+ ```
298
+
299
+ All values shown are defaults.
300
+
301
+ ---
302
+
303
+ ## Custom components
304
+
305
+ Register your own components with a `_adapter` object in the component registry:
306
+
307
+ ```js
308
+ const form = await loadJsonConfig('/forms/UserView')
309
+
310
+ form.registries.components = {
311
+ ...form.registries.components,
312
+ DatePicker: {
313
+ _adapter: {
314
+ mount(container, { def, store }) {
315
+ const picker = document.createElement('my-date-picker')
316
+ picker.value = store.record.data[def.name]
317
+ picker.addEventListener('change', (e) => {
318
+ store.record.data[def.name] = e.target.value
319
+ })
320
+ container.appendChild(picker)
321
+ return { unmount() { container.innerHTML = '' } }
322
+ }
323
+ }
324
+ }
325
+ }
326
+
327
+ mountForm(document.getElementById('app'), form, { /* ... */ })
328
+ ```
329
+
330
+ Reference by name in field definitions:
331
+
332
+ ```json
333
+ {
334
+ "startDate": { "component": "DatePicker", "label": "Start Date" }
335
+ }
336
+ ```
337
+
338
+ ---
339
+
340
+ ## Config loading
341
+
342
+ ```js
343
+ import { loadJsonConfig, createLiveConfig } from '@archduck/gst-forms'
344
+
345
+ // Load from files (form schema pre-applied)
346
+ const form = await loadJsonConfig('./UserView')
347
+
348
+ // With variant overlays
349
+ const form = await loadJsonConfig('./UserView', ['uchealth', 'admin'])
350
+
351
+ // With translation map
352
+ const form = await loadJsonConfig('./UserView', ['uchealth'], { map: 'es' })
353
+ ```
354
+
355
+ File naming: `property[-variant][~map].{json|js}`. Variants stack left to right. Maps are applied after all variants have merged.
356
+
357
+ ### Runtime patching
358
+
359
+ ```js
360
+ const live = createLiveConfig()
361
+ await live.load('./UserView')
362
+ live.patch('fields', { email: { required: true } })
363
+ const form = live.get()
364
+ ```
365
+
366
+ Patches cascade through dependencies. If `workEmail` spreads from `email`, patching `email` re-resolves `workEmail` too.
367
+
368
+ ### Update without unmounting
369
+
370
+ ```js
371
+ const ctrl = mountForm(container, form, options)
372
+
373
+ // Later, load a different record or a different config
374
+ ctrl.update(newRecord)
375
+ ```
376
+
377
+ This preserves DOM state, scroll position, and focus while swapping the underlying data.
378
+
379
+ ---
380
+
381
+ ## Debug API
382
+
383
+ When mounted, the form exposes `window.gst`:
384
+
385
+ ```js
386
+ window.gst.record // current record
387
+ window.gst.data // shorthand for record.data
388
+ window.gst.isDirty // dirty state
389
+ window.gst.errors // field errors
390
+
391
+ window.gst.getValue('email')
392
+ window.gst.setValue('email', 'new@example.com')
393
+ window.gst.executeAction('save')
394
+
395
+ window.gst.getField('email')
396
+ window.gst.listFields()
397
+ window.gst.listButtons()
398
+ window.gst.listFunctions()
399
+ ```
400
+
401
+ ---
402
+
403
+ ## API reference
404
+
405
+ ### Functions
406
+
407
+ - **`mountForm(container, config, options)`** -- Mount a form. Returns controller with `update`, `executeAction`, `checkGuards`, `unmount`, `store`.
408
+ - **`loadJsonConfig(path, variants?, options?)`** -- Load form config with form schema. Options include `map` for translations.
409
+ - **`createLiveConfig(options?)`** -- Create LiveConfig with form schema.
410
+
411
+ ### Defaults
412
+
413
+ - **`defaultActions`** -- Built-in action handlers (`save`, `cancel`, `delete`, `new`, `duplicate`).
414
+ - **`defaultButtons`** -- Built-in button definitions with dynamic visibility.
415
+ - **`coreButtonFunctions`** -- Functions used by default buttons (`showSave`, `saveLabel`, etc.).
416
+ - **`META_DEFAULTS`** -- Default meta configuration values.
417
+
418
+ ### Schema
419
+
420
+ - **`FORM_TLP_DECLARATIONS`** -- Raw TLP declarations for form properties.
421
+ - **`formSchema`** -- Pre-built schema from declarations (`resolutionOrder`, `dependencies`, `registryMapping`, `validRegistries`, `getDefault`).
422
+
423
+ ### Utilities
424
+
425
+ - **`getRecordData(record, meta)`** / **`setRecordData(record, meta, data)`** -- Access record data respecting `meta.recordDataPath`.
426
+ - **`getRecordKey(record, meta)`** -- Access record key respecting `meta.recordKeyPath`.
427
+ - **`executeHooks(hookName, context, data)`** -- Run hook functions.
428
+ - **`validateConfig(config)`** -- Validate form config completeness.
429
+ - **`sanitizeHTML(html)`** / **`escapeHTML(str)`** / **`sanitizeInput(value, type)`** -- Security utilities.
@@ -0,0 +1 @@
1
+ @layer gst{.gst{container-type:inline-size;container-name:gst;display:flex;flex-direction:column}.gst,.gst *,.gst *:before,.gst *:after{box-sizing:border-box;gap:1em}.gst .field{width:100%}.gst .field label{display:block;width:100%}.gst .field.component-checkboxfield{align-self:center}.gst .field.component-checkboxfield label{display:flex;align-items:center}.gst .option label{display:flex;align-items:center}.gst .group{display:flex;flex-direction:column;width:100%}.gst .row{display:flex;flex-direction:column}.gst .row:empty,.gst .group:empty,.gst .group-buttons:empty,.gst .form-actions:empty{display:none}.gst .form-actions,.gst .group-buttons,.gst .field.button-row{display:flex;align-items:end;flex-wrap:wrap;justify-content:end}.gst .buttonset{display:flex;flex-direction:row;flex-wrap:wrap;width:100%;justify-content:end}.gst .button-group{display:flex;justify-content:center}.gst .contents{display:contents}.gst h1{font-size:2em}.gst h2{font-size:1.5em}.gst h3{font-size:1.17em}.gst h4{font-size:1em}.gst h5{font-size:.83em}.gst h6{font-size:.67em}.gst .btn,.gst button,.gst input,.gst select,.gst textarea{font-family:inherit;font-size:inherit;line-height:inherit}.gst .option,.gst .btn,.gst button,.gst input,.gst select,.gst textarea{touch-action:manipulation}.gst .btn,.gst button{width:fit-content;cursor:pointer}.gst input:not([type=checkbox]):not([type=radio]),.gst select,.gst textarea{width:100%;-webkit-tap-highlight-color:transparent}.gst input[type=date],.gst input[type=time],.gst input[type=datetime-local],.gst input[type=month],.gst input[type=week]{max-width:100%}.gst input[type=date][data-empty]::-webkit-datetime-edit-fields-wrapper,.gst input[type=time][data-empty]::-webkit-datetime-edit-fields-wrapper,.gst input[type=datetime-local][data-empty]::-webkit-datetime-edit-fields-wrapper,.gst input[type=month][data-empty]::-webkit-datetime-edit-fields-wrapper,.gst input[type=week][data-empty]::-webkit-datetime-edit-fields-wrapper{color:var(--gst-text-placeholder, #999)}.gst input[type=date]:focus::-webkit-datetime-edit-fields-wrapper,.gst input[type=time]:focus::-webkit-datetime-edit-fields-wrapper,.gst input[type=datetime-local]:focus::-webkit-datetime-edit-fields-wrapper,.gst input[type=month]:focus::-webkit-datetime-edit-fields-wrapper,.gst input[type=week]:focus::-webkit-datetime-edit-fields-wrapper{color:inherit}@supports (-webkit-touch-callout: none){.gst input[type=date],.gst input[type=time],.gst input[type=datetime-local],.gst input[type=month],.gst input[type=week]{-moz-appearance:none;appearance:none;-webkit-appearance:none;text-align:left;text-align-last:left;-webkit-text-align:left}.gst input[type=date]::-webkit-date-and-time-value,.gst input[type=time]::-webkit-date-and-time-value,.gst input[type=datetime-local]::-webkit-date-and-time-value,.gst input[type=month]::-webkit-date-and-time-value,.gst input[type=week]::-webkit-date-and-time-value{text-align:left}.gst label:has(input[data-empty]){position:relative}.gst label:has(input[data-empty]):after{position:absolute;bottom:.1625em;left:.0625em;right:.0625em;padding:var(--gst-padding-sm, .5em);line-height:1;color:var(--gst-text-placeholder, #999);font-weight:400;pointer-events:none;background:var(--gst-bg-input, #fff)}.gst label:has(input[data-empty]):focus-within:after{display:none}.gst label:has(input[data-empty]):has(input[type=date]):after{content:"mm/dd/yyyy"}.gst label:has(input[data-empty]):has(input[type=time]):after{content:"hh:mm"}.gst label:has(input[data-empty]):has(input[type=datetime-local]):after{content:"mm/dd/yyyy hh:mm"}.gst label:has(input[data-empty]):has(input[type=month]):after{content:"mm/yyyy"}.gst label:has(input[data-empty]):has(input[type=week]):after{content:"Week"}}.gst .error-message,.gst .field-error{display:block}.gst .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.gst .skip-link{position:absolute;left:-9999px;z-index:100;padding:.5em 1em;background:Canvas;color:CanvasText;text-decoration:underline}.gst .skip-link:focus{position:static;left:auto}@container gst (min-width: 30em){.gst .row{flex-direction:row}}}
package/dist/index.cjs ADDED
@@ -0,0 +1,16 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("@archduck/gst-core"),M={functions:{registry:!0,dependencies:[]},actions:{dependencies:["functions"],children:{buttons:{registry:!0},hooks:{}}},optionSets:{registry:!0,dependencies:[]},fields:{registry:!0,dependencies:["functions","optionSets","actions"]},components:{registry:!0,dependencies:["fields"]},meta:{dependencies:[]},state:{dependencies:[],default:{}},layout:{dependencies:["fields","components","actions"],default:[]},record:{dependencies:[],default:null},styles:{dependencies:[],default:null}},R=l.createTLPSchema(M);async function I(e,t={}){return l.loadJsonConfigRaw(e,{...t,schema:R})}function J(e={}){return l.createLiveConfigRaw({...e,schema:R})}async function Y(e,t={}){return l.loadLiveConfigRaw(e,{...t,schema:R})}function Z(e){const t=new Set;function n(r){if(!r)return;if(typeof r=="string"){t.add(r);return}r.name&&!r.rows&&!r.layout&&t.add(r.name);const i=r.rows||r.layout;if(Array.isArray(i))for(const s of i)if(Array.isArray(s))for(const o of s)n(o);else n(s);if(Array.isArray(r.children))for(const s of r.children)n(s)}return n(e),t}let S=null;function Q(e,t){var n,r,i,s,o;l.pauseTracking();try{if(!(((n=t.meta)==null?void 0:n.clearHiddenFields)??!0))return;const c=Z(e);if(c.size===0)return;const f=new Set([...((r=t.meta)==null?void 0:r.systemFields)||[],...((i=t.meta)==null?void 0:i.readonly)||[]]);for(const p of c){if(f.has(p))continue;const m=(o=(s=t.registries)==null?void 0:s.fields)==null?void 0:o[p];if(m!=null&&m.bind)continue;const u=l.getFieldValue(t,p,m);u!=null&&u!==""&&(S||(S={store:t,fields:new Set},queueMicrotask(X)),S.fields.add(p))}}finally{l.enableTracking()}}function X(){var i,s,o,a,c,f,p,m,u;const e=S;if(S=null,!e||e.fields.size===0)return;const{store:t}=e,n=new Map;for(const d of e.fields){const g=(s=(i=t.registries)==null?void 0:i.fields)==null?void 0:s[d];n.set(d,l.getFieldValue(t,d,g))}for(const d of e.fields){const g=(a=(o=t.registries)==null?void 0:o.fields)==null?void 0:a[d];l.clearFieldValue(t,d,g)}for(;S&&S.fields.size>0;){const d=S;S=null;for(const g of d.fields){const b=(f=(c=t.registries)==null?void 0:c.fields)==null?void 0:f[g];n.has(g)||n.set(g,l.getFieldValue(t,g,b)),l.clearFieldValue(t,g,b)}}if((((p=t.meta)==null?void 0:p.confirmClearFields)??!1)&&n.size>0){const d=Array.from(n.keys()).map(b=>{var y,h;const w=(h=(y=t.registries)==null?void 0:y.fields)==null?void 0:h[b];return(w==null?void 0:w.label)||b});if(!window.confirm(`The following fields will be cleared because their section is now hidden:
2
+
3
+ ${d.join(", ")}
4
+
5
+ Do you want to proceed?`))for(const[b,w]of n){const y=(u=(m=t.registries)==null?void 0:m.fields)==null?void 0:u[b];l.setFieldValue(t,b,y,w)}}}function x(e,t){const n=typeof e=="function"?e:()=>e;return Object.freeze({get isDirty(){return(t==null?void 0:t.isDirty)||!1},get isSubmitting(){return(t==null?void 0:t.isSubmitting)||!1},get isDeleted(){return(t==null?void 0:t.isDeleted)||!1},get record(){return t==null?void 0:t.record},get data(){var r;return(r=t==null?void 0:t.record)==null?void 0:r.data},get meta(){return t==null?void 0:t.meta},getValue(r){var s,o;const i=(o=(s=t==null?void 0:t.registries)==null?void 0:s.fields)==null?void 0:o[r];return l.getFieldValue(t,r,i)},setValue(r,i){var o,a;const s=(a=(o=t==null?void 0:t.registries)==null?void 0:o.fields)==null?void 0:a[r];l.setFieldValue(t,r,s,i)},getField(r){var i,s;return(s=(i=t==null?void 0:t.registries)==null?void 0:i.fields)==null?void 0:s[r]},getErrors(r){var i;return((i=t==null?void 0:t.fieldErrors)==null?void 0:i[r])||[]},setError(r,i){t!=null&&t.fieldErrors&&(t.fieldErrors[r]=Array.isArray(i)?i:[i])},clearErrors(r){t!=null&&t.fieldErrors&&(t.fieldErrors[r]=[])},get hasErrors(){return Object.values((t==null?void 0:t.fieldErrors)||{}).some(r=>Array.isArray(r)&&r.length>0)},executeAction(r,i){var s;return(s=t==null?void 0:t.executeAction)==null?void 0:s.call(t,r,i)},reset(){var r,i,s;(s=(i=(r=n())==null?void 0:r.form)==null?void 0:i.reset)==null||s.call(i)},loadRecord(r){var i,s,o;(o=(s=(i=n())==null?void 0:i.form)==null?void 0:s.loadRecord)==null||o.call(s,r)}})}function ee(e,t){const{actionHandlers:n={},wrapAction:r,onAction:i}=t,s=u=>JSON.parse(JSON.stringify(u||{}));let o=s(e.record);function a(){const u=s(o);e.record.key=u.key??null;const d=e.record.data;for(const g of Object.keys(d))delete d[g];for(const[g,b]of Object.entries(u.data||{}))d[g]=b;e.fieldErrors={},e.isDeleted=!1}function c(u){o=s(u),e.record.key=u.key??null;const d=e.record.data;for(const g of Object.keys(d))delete d[g];for(const[g,b]of Object.entries(u.data||{}))d[g]=b;e.fieldErrors={},e.isDeleted=!1}function f(u,d={}){var g,b,w;return{action:u,record:e.record,meta:e.meta,registries:e.registries,functions:((g=e.registries)==null?void 0:g.functions)||{},hooks:((b=e.registries)==null?void 0:b.hooks)||((w=e.actions)==null?void 0:w.hooks)||{},form:{isDirty:e.isDirty,isSubmitting:e.isSubmitting,isDeleted:e.isDeleted,getValue:y=>{var A,E;const h=(E=(A=e.registries)==null?void 0:A.fields)==null?void 0:E[y];return l.getFieldValue(e,y,h)},setValue:(y,h)=>{var E,k;const A=(k=(E=e.registries)==null?void 0:E.fields)==null?void 0:k[y];l.setFieldValue(e,y,A,h)},getFieldErrors:y=>{var h;return((h=e.fieldErrors)==null?void 0:h[y])||[]},setFieldErrors:(y,h)=>{e.fieldErrors&&(e.fieldErrors[y]=h)},setFieldError:(y,h)=>{e.fieldErrors&&(e.fieldErrors[y]=Array.isArray(h)?h:[h])},clearFieldError:y=>{e.fieldErrors&&(e.fieldErrors[y]=[])},reset:a,loadRecord:c,setDeleted:y=>{e.isDeleted=y},isRequired:y=>{var h,A,E;return!!((E=(A=(h=e.registries)==null?void 0:h.fields)==null?void 0:A[y])!=null&&E.required)},isReadonly:y=>{var h,A,E;return!!((h=e.meta)!=null&&h.readOnly)||((E=(A=e.meta)==null?void 0:A.readonly)==null?void 0:E.includes(y))||!1}},props:{onAction:i},...d}}const p=x(()=>f("_stage"),e);function m(u){var b,w,y,h;const d=(w=(b=e.actions)==null?void 0:b.hooks)==null?void 0:w[u];if(!d)return l.isDebugActive()&&l.trace("hook",`resolve-hook "${u}"`,"not defined",{hookName:u}),null;const g=typeof d=="function"?d:typeof d=="string"&&d.startsWith("{{")?(h=(y=e.registries)==null?void 0:y.functions)==null?void 0:h[d.slice(2,-2).trim()]:null;return l.isDebugActive()&&l.trace("hook",`resolve-hook "${u}"`,g?"found":"MISSING function",{hookName:u,hookType:typeof d,hookValue:typeof d=="string"?d:"(function)",resolved:!!g}),g?g.bind(p):null}e._fireHook=u=>{const d=m(u);d&&queueMicrotask(()=>d(f(u)))},e.executeAction=async(u,d={})=>{var w,y;let g=u;typeof u=="string"&&u.startsWith("{{")&&u.endsWith("}}")&&(g=u.slice(2,-2).trim());const b=f(g,d);e.isSubmitting=!0;try{const h=(y=(w=e.registries)==null?void 0:w.functions)==null?void 0:y[g],A=(typeof h=="function"?h:null)||n[g]||(i?async k=>i(k):null);if(!A){l.isDebugActive()&&l.trace("action",`execute-action "${g}"`,"NO HANDLER found",{actionName:g,inFunctionRegistry:!!h,inActionHandlers:!!n[g],hasOnAction:!!i});return}if(l.isDebugActive()){const k=typeof h=="function"?"functions registry":n[g]?"actionHandlers":"onAction callback";l.trace("action",`execute-action "${g}"`,`dispatching (via ${k})`,{actionName:g,source:k,extra:d})}const E=A.bind(p);r?await r(g,E).call(p,b,d):await E(b,d)}finally{e.isSubmitting=!1}}}function te(e,t){const{onDirtyChange:n}=t,i=(s=>JSON.parse(JSON.stringify(s||{})))(e.record);return l.effect(()=>{var f;const s=e.record.data,o=i.data||{};let a=!1;const c=new Set([...Object.keys(s),...Object.keys(o)]);for(const p of c){const m=s[p],u=o[p];if(m!==u){if(typeof m=="object"&&typeof u=="object"&&JSON.stringify(m)===JSON.stringify(u))continue;a=!0;break}}e.isDirty!==a&&(e.isDirty=a,n&&queueMicrotask(()=>n(a)),(f=e._fireHook)==null||f.call(e,a?"onDirty":"onClean"))})}function ne(e,t){const{onChange:n}=t;let r=!0;return l.effect(()=>{var i;if({...e.record.data},r){r=!1;return}n&&queueMicrotask(()=>n(e.record)),(i=e._fireHook)==null||i.call(e,"onChange")})}function re(e){let t=!1;return l.effect(()=>{var r;const n=Object.values(e.fieldErrors).some(i=>Array.isArray(i)&&i.length>0);n!==t&&(t=n,(r=e._fireHook)==null||r.call(e,n?"onError":"onRecover"),n&&queueMicrotask(()=>{var o;const i=(o=Object.entries(e.fieldErrors).find(([,a])=>Array.isArray(a)&&a.length>0))==null?void 0:o[0];if(!i)return;const s=document.querySelector(`[name="${CSS.escape(i)}"]`);s&&typeof s.focus=="function"&&s.focus()}))})}function ie(e){typeof window>"u"||(window.gst={meta:e.meta,state:e.state,registries:e.registries,actions:e.actions,record:e.record,get data(){var t;return(t=e.record)==null?void 0:t.data},get isDirty(){return e.isDirty},get isSubmitting(){return e.isSubmitting},get errors(){return e.fieldErrors},getField:t=>{var n,r;return(r=(n=e.registries)==null?void 0:n.fields)==null?void 0:r[t]},getButton:t=>{var n,r;return(r=(n=e.registries)==null?void 0:n.buttons)==null?void 0:r[t]},getFunction:t=>{var n,r;return(r=(n=e.registries)==null?void 0:n.functions)==null?void 0:r[t]},getOptions:t=>{var n,r;return(r=(n=e.registries)==null?void 0:n.optionSets)==null?void 0:r[t]},executeAction:(t,n)=>e.executeAction(t,n),setValue:(t,n)=>{var i,s;const r=(s=(i=e.registries)==null?void 0:i.fields)==null?void 0:s[t];l.setFieldValue(e,t,r,n)},getValue:t=>{var r,i;const n=(i=(r=e.registries)==null?void 0:r.fields)==null?void 0:i[t];return l.getFieldValue(e,t,n)},setState:(t,n)=>{e.state&&(e.state[t]=n)},getState:t=>{var n;return(n=e.state)==null?void 0:n[t]},inspect:t=>t.split(".").reduce((n,r)=>n==null?void 0:n[r],window.gst),listFields:()=>{var t;return Object.keys(((t=e.registries)==null?void 0:t.fields)||{})},listButtons:()=>{var t;return Object.keys(((t=e.registries)==null?void 0:t.buttons)||{})},listFunctions:()=>{var t;return Object.keys(((t=e.registries)==null?void 0:t.functions)||{})},store:e})}function se(e){var t;typeof window<"u"&&((t=window.gst)==null?void 0:t.store)===e&&delete window.gst}function oe(e,t,n={}){const r=t.registries||{};for(const[o,a]of Object.entries(r))a&&typeof a=="object"&&!Array.isArray(a)&&a.__source&&console.error(`[gst] "__source" found in ${o} registry but won't be processed at mount time. __source only works in JSON configs loaded via loadJsonConfig(). For JS configs, use imports instead: ${o}: { ...base.${o}, ...my${o[0].toUpperCase()+o.slice(1)} }`);const i=document.createElement("form");i.addEventListener("submit",o=>o.preventDefault());for(const o of e.classList)o.startsWith("theme-")&&i.classList.add(o);const s=document.createElement("a");return s.className="skip-link",s.href="#",s.textContent="Skip to form actions",s.addEventListener("click",o=>{o.preventDefault();const a=i.querySelector(".form-actions, .group-buttons");if(a){const c=a.querySelector("button:not(:disabled)");c&&c.focus()}}),i.appendChild(s),e.appendChild(i),l.mount(i,t,{...n,initialState:{fieldErrors:{},isDirty:!1,isSubmitting:!1,isDeleted:!1,...n.initialState},onHide:Q,setupActions:ee,effects:[te,ne,re,...n.effects||[]],onMount:(o,a)=>{var m;const c=o.addGuard(()=>o.isDirty?"You have unsaved changes.":!0),f=u=>{const d=a.checkGuards();d!==!0&&(u.preventDefault(),u.returnValue=typeof d=="string"?d:"")};window.addEventListener("beforeunload",f);const p=u=>{u.key==="Enter"&&(u.ctrlKey||u.metaKey)?(u.preventDefault(),o.executeAction&&o.executeAction("save")):u.key==="Escape"&&o.isDirty&&i.contains(document.activeElement)&&(u.preventDefault(),o.executeAction&&o.executeAction("cancel"))};i.addEventListener("keydown",p),o._formGuardCleanup={removeDirtyGuard:c,onBeforeUnload:f,onKeyDown:p},(m=o._fireHook)==null||m.call(o,"onLoad"),ie(o),n.onMount&&n.onMount(o,a)},onUnmount:o=>{var a;o._formGuardCleanup&&(o._formGuardCleanup.removeDirtyGuard(),window.removeEventListener("beforeunload",o._formGuardCleanup.onBeforeUnload),i.removeEventListener("keydown",o._formGuardCleanup.onKeyDown),delete o._formGuardCleanup),(a=o._fireHook)==null||a.call(o,"onUnload"),se(o),n.onUnmount&&n.onUnmount(o)}})}const ae={key:null,data:{}},v={clearHiddenFields:!0,confirmClearFields:!1,confirmCancel:!0,confirmDelete:!0,confirmDuplicate:!0,confirmNew:!0,preventNavigationWhenDirty:!0,submitOnEnter:!0,debug:!1,recordKeyPath:"key",recordDataPath:"data",readonly:[],systemFields:[]};function ce(e={}){return{...v,...e}}function N(e,t){if(!e)return{};const n=t==null?void 0:t.recordDataPath;if(n==="")return e;const r=n??v.recordDataPath;return e[r]??{}}function V(e,t,n){if(!e)return{[v.recordDataPath]:n};const r=t==null?void 0:t.recordDataPath;if(r==="")return{...e,...n};const i=r??v.recordDataPath;return{...e,[i]:n}}function W(e,t){if(!e)return null;const n=t==null?void 0:t.recordKeyPath;if(n==="")return null;const r=n??v.recordKeyPath;return e[r]??null}function ue(e,t,n,r={}){if(!n||n.length===0)return e;const s={...N(e,t)},o=c=>{const f=(c==null?void 0:c.type)||"text",p=c==null?void 0:c.component;return c&&"value"in c?c.value:f==="checkbox"||p==="Checkbox"?!1:f==="array"||p==="ArrayField"?[]:""},a=c=>{if(!c)return[];const f=[];return Array.isArray(c.rows)&&c.rows.forEach(p=>{Array.isArray(p)&&p.forEach(m=>{typeof m=="string"&&f.push(m)})}),f};return n.forEach(c=>{a(c).forEach(p=>{if(!(p in s)){const m=r[p];s[p]=o(m)}})}),V(e,t,s)}function j(e,t={}){const{path:n,suggestion:r,details:i}=t,s=["🔴 Runtime Error:",e];return n&&s.push(`
6
+ Path: ${n}`),i&&s.push(`
7
+ Details: ${i}`),r&&s.push(`
8
+ 💡 ${r}`),s.join("")}function H(e,t={}){const{path:n,suggestion:r,details:i,available:s}=t,o=["⚠️ ",e];return n&&o.push(`
9
+ Path: ${n}`),i&&o.push(`
10
+ Details: ${i}`),(s==null?void 0:s.length)>0&&o.push(`
11
+ Available: ${s.join(", ")}`),r&&o.push(`
12
+ 💡 ${r}`),o.join("")}function z(e,t,n={}){const{path:r,available:i=[]}=n;return H(`${e} "${t}" not found`,{path:r,available:i,suggestion:i.length>0?`Check spelling or add "${t}" to your ${e.toLowerCase()} registry`:`No ${e.toLowerCase()} are registered. Add ${e.toLowerCase()} to your registries or load from file`})}function fe(e,t,n,r={}){return j(`Error evaluating ${t} function "${e}"`,{...r,details:n.message,suggestion:"Check function implementation for runtime errors"})}function le(e,t){return H(`No ${e} handler provided`,{path:`props.${e}`,suggestion:`Pass ${e} prop to Form component to handle ${t} action`})}function de(e,t,n,r={}){return j(`Error ${e} field "${t}"`,{...r,details:n.message,suggestion:`Check ${e==="parsing"?"parse":"format"} function for field "${t}"`})}function ge(e,t,n){const{isDirty:r=!1,isTouched:i=!1,isFocused:s=!1,isDisabled:o=!1,isReadonly:a=!1,isRequired:c=!1,isComputed:f=!1,hasErrors:p=!1}=e;return{"data-field":t,"data-type":n||"text","data-dirty":r.toString(),"data-touched":i.toString(),"data-focused":s.toString(),"data-disabled":o.toString(),"data-readonly":a.toString(),"data-required":c.toString(),"data-computed":f.toString(),"data-valid":(!p).toString(),"data-invalid":p.toString(),"data-has-error":p.toString()}}function pe(e,t,n,r,i){return{"data-button":e||t||"button","data-action":t||"","data-variant":n||"secondary","data-loading":r.toString(),"data-disabled":i.toString()}}function ye(e,t){return{"data-group":e||"group","data-visible":t.toString()}}function U(e,t,n="element"){var r;if(!e)return!0;if(e.show!==void 0){const i=e.show,s=t.functions||((r=t.registries)==null?void 0:r.functions)||{};if(typeof i=="string"&&i.startsWith("{{")&&i.endsWith("}}")){const o=i.slice(2,-2).trim(),a=s[o];if(a&&typeof a=="function"){const c=l.createStage(e,()=>t,n);return a.call(c,t)!==!1}return!!i}if(typeof i=="function"){const o=l.createStage(e,()=>t,n);return i.call(o,t)!==!1}return!!i}return!0}function he(e){return e?e.toString().toLowerCase().trim().replace(/\s+/g,"-").replace(/[^\w\-]+/g,"").replace(/\-\-+/g,"-"):""}function me(e,t,n,r,i,s={}){if(!r)return new Set;const o=new Set,a=new Set([...r.systemFields||[],...r.readonly||[]]),c=i||{record:t,registries:n,config:r};return e.forEach((f,p)=>{var g,b;const m=l.bindFunctions(f,c.functions||((g=c.registries)==null?void 0:g.functions)||{},((b=c.registries)==null?void 0:b.optionSets)||{},()=>c),u=U(m,c,m.name||`group[${p}]`);if(s[p]===!0&&!u&&f.rows)for(const w of f.rows)for(const y of w){const h=typeof y=="string"?y:y.name;a.has(h)||o.add(h)}}),o}function C(e){var n,r;const t=W(e.record,e.meta);return{hasKey:t!=null&&t!=="",isDirty:((n=e.form)==null?void 0:n.isDirty)??!1,isDeleted:((r=e.form)==null?void 0:r.isDeleted)??!1}}function be(e){const{hasKey:t,isDirty:n,isDeleted:r}=C(e);return r?!1:t||n}function we(e){const{hasKey:t,isDeleted:n}=C(e);return t&&!n}function Ee(e){const{hasKey:t,isDeleted:n}=C(e);return t&&!n}function Ae(e){const{isDirty:t,isDeleted:n}=C(e);return n?!1:t}function ve(){return!0}function De(e){const{hasKey:t,isDeleted:n}=C(e);return n?"Undo Delete":t?"Save":"Create"}function Se(){return"Cancel"}const ke={showNew:be,showDuplicate:we,showDelete:Ee,showCancel:Ae,showSave:ve,saveLabel:De,cancelLabel:Se},$e={save:{name:"save",label:"{{saveLabel}}",action:"save",variant:"primary",show:"{{showSave}}"},cancel:{name:"cancel",label:"Cancel",action:"cancel",variant:"secondary",show:"{{showCancel}}"},delete:{name:"delete",label:"Delete",action:"delete",variant:"danger",show:"{{showDelete}}",confirm:"Are you sure you want to delete this record?"},new:{name:"new",label:"New",action:"new",variant:"primary",show:"{{showNew}}"},duplicate:{name:"duplicate",label:"Duplicate",action:"duplicate",variant:"secondary",show:"{{showDuplicate}}"}};async function L(e,t,n){var s;const r=(s=t.hooks)==null?void 0:s[e];if(!r)return!0;const i=Array.isArray(r)?r:[r];for(const o of i){const a=o.replace(/{{|}}/g,"").trim(),c=t.functions[a];if(!c){l.logger.warn(z("Hook function",a,{path:`hooks.${e}`,available:Object.keys(t.functions)}));continue}try{if(await c(t,n)===!1)return!1}catch(f){return l.logger.error(j(`Hook "${a}" failed`,{path:`hooks.${e}`,details:f.message,suggestion:"Check hook function implementation for errors"})),!1}}return!0}function _(e){return e?e.charAt(0).toUpperCase()+e.slice(1):""}function T(e){return e==null||e===""||Array.isArray(e)&&e.length===0}function Fe(e){var i,s;const t=((i=e.registries)==null?void 0:i.fields)||{},n=((s=e.record)==null?void 0:s.data)||{};let r=!0;for(const[o,a]of Object.entries(t))if(!(!a||typeof a!="object")){if(a.fields&&typeof a.fields=="object"){const c=a.fields,f=!!a.required,p=Object.entries(c).filter(([,u])=>u&&typeof u=="object"&&u.required).map(([u])=>u),m=!f&&Object.keys(c).some(u=>!T(n[u]));if(f||m)for(const u of p)T(n[u])&&(e.form.setFieldErrors(u,["Required"]),r=!1);continue}a.required&&T(n[o])&&(e.form.setFieldErrors(o,["Required"]),r=!1)}return r}function Le(e,t){return async function(r,i){const s=r.record;if((e==="save"||e==="create"||e==="update")&&!Fe(r))return;const o=`before${_(e)}`;if(!await L(o,r,s))return;const c=await t(r,i),f=`after${_(e)}`;return await L(f,r,s),c}}function F(e,t){var i,s,o;const n=`on${_(t)}`,r=(i=e.props)==null?void 0:i[n];return r||((s=e.props)!=null&&s.onAction?e.props.onAction:(o=e.functions)==null?void 0:o[t])}const Ce={async save(e,t){const{record:n,meta:r,form:i}=e,s=!!n.key;if((i==null?void 0:i.isDeleted)&&(i!=null&&i.setDeleted)&&i.setDeleted(!1),!await L(s?"beforeUpdate":"beforeCreate",e,n))return;const f=F(e,"save");f&&await f(e),await L(s?"afterUpdate":"afterCreate",e,n)},async cancel(e,t){const{meta:n,form:r}=e,i=(n==null?void 0:n.confirmCancel)??v.confirmCancel;if(r.isDirty&&i&&!confirm(typeof i=="string"?i:"You have unsaved changes. Are you sure you want to cancel?"))return;r!=null&&r.reset&&r.reset();const s=F(e,"cancel");s&&await s(e)},async delete(e,t){const{meta:n,form:r}=e,i=(n==null?void 0:n.confirmDelete)??v.confirmDelete;if(i&&!confirm(typeof i=="string"?i:"Are you sure you want to delete this record?"))return;const s=F(e,"delete");s&&await s(e),r!=null&&r.setDeleted&&r.setDeleted(!0)},async duplicate(e,t){const{meta:n,form:r,record:i}=e,s=(n==null?void 0:n.confirmDuplicate)??v.confirmDuplicate;if(r.isDirty&&s&&!confirm(typeof s=="string"?s:"You have unsaved changes. Duplicating will discard them. Continue?"))return;r.loadRecord({key:null,data:{...i.data}});const o=F(e,"duplicate");o&&await o(e)},async new(e,t){var a;const{meta:n,form:r}=e,i=(n==null?void 0:n.confirmNew)??v.confirmNew;if(r.isDirty&&i&&!confirm(typeof i=="string"?i:"You have unsaved changes. Creating new will discard them. Continue?"))return;const s=((a=n==null?void 0:n.defaultRecord)==null?void 0:a.data)||{};r.loadRecord({key:null,data:{...s}});const o=F(e,"new");o&&await o(e)}};function Oe(e){if(!e||typeof e!="string")return null;const t={phone:/^\([0-9]{3}\) [0-9]{3}-[0-9]{4}$/,ssn:/^[0-9]{3}-[0-9]{2}-[0-9]{4}$/,ein:/^[0-9]{2}-[0-9]{7}$/,creditCard:/^[0-9]{4} [0-9]{4} [0-9]{4} [0-9]{4}$/,date:/^(0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01])\/[0-9]{4}$/,zip:/^[0-9]{5}$/,zipPlus4:/^[0-9]{5}-[0-9]{4}$/};for(const[n,r]of Object.entries(t))if(r.test(e.replace(/[#9]/g,"0")))return n;return"custom"}function Re(e,t){if(!e||!t)return e;const n=[];for(const s of e)(/\d/.test(s)||/[a-zA-Z]/.test(s))&&n.push(s);let r="",i=0;for(let s=0;s<t.length&&i<n.length;s++){const o=t[s];if(o==="#"||o==="9"){for(;i<n.length&&!/\d/.test(n[i]);)i++;if(i>=n.length)break;r+=n[i++]}else if(o==="A"){for(;i<n.length&&!/[a-zA-Z]/.test(n[i]);)i++;if(i>=n.length)break;r+=n[i++].toUpperCase()}else r+=o}return r}function q(e,t,n,r){var a;if(t==null||typeof t=="function"||typeof t!="string"||!t.startsWith("{{"))return t;const i=t.slice(2,-2).trim(),s=(a=n==null?void 0:n.functions)==null?void 0:a[i];if(!s){l.logger.warn(`Function "${i}" not found for property "${e}"`);return}const o=l.createStage(r,()=>n,r==null?void 0:r.name);if(l.isEventKey(e))return c=>{try{return s.call(o,n,c)}catch(f){l.logger.error(`Error in event handler ${e} with function ${i}:`,f);return}};try{return s.call(o,n,null)}catch(c){l.logger.error(`Error resolving ${e} with function ${i}:`,c);return}}function je(e,t){return e&&e.show!==void 0?!!q("show",e.show,t,e):!0}function Te(e,t,n){var i;if(!(t!=null&&t.format))return e;let r;if(typeof t.format=="function")r=t.format;else if(typeof t.format=="string"){const s=t.format.replace(/^\{\{|\}\}$/g,"");r=(i=n==null?void 0:n.functions)==null?void 0:i[s]}if(!r)return e;try{const s=l.createStage(t,()=>n,t.name),o=r.call(s,n,null);return o!==void 0?o:e}catch(s){return l.logger.error(`Format error for field ${t.name}:`,s),e}}function _e(e,t,n){var i;if(!(t!=null&&t.parse))return e;const r=typeof t.parse=="function"?t.parse:(i=n==null?void 0:n.functions)==null?void 0:i[t.parse];if(!r)return e;try{const o={...l.createStage(t,()=>n,t.name),value:e},a=r.call(o,n,null);return a!==void 0?a:e}catch(s){return l.logger.error(`Parse error for field ${t.name}:`,s),e}}const He=new Set(["def","name","show","options","fields","layout","rows","validate","validator","format","formatter","parse","parser","mask","component","action","handler","__source","transform","dependencies","triggers","effects","rules","meta","itemlabel","itemlayout","defaultitem","buttons","items","legend","innertext","group","title","description","confirm","variant","label","hint","clearable","_componentName","_component","_adapter"]);function Pe(e){const t={},n={};for(const[r,i]of Object.entries(e))He.has(r)||Array.isArray(i)||i!==null&&typeof i=="object"&&r!=="style"&&!r.startsWith("data-")||r==="style"&&typeof i=="string"||(r==="class"||r==="style"||r.startsWith("data-")?t[r]=i:n[r]=i);return{wrapperProps:t,inputProps:n}}function Me({meta:e={},layout:t=[],registries:n={}}){const r=[],{fields:i={},buttons:s={},optionSets:o={},functions:a={}}=n;Ne(r,e,t,i,s,a),Ve(r,i,s),We(r,t,i);const c=ze(t);return Ue(r,i,o,c),qe(r,a),Ke(r,i),r}function $(e,t=""){const n=[];if(typeof e=="string"&&e.startsWith("{{")&&e.endsWith("}}")){const r=e.slice(2,-2);n.push({name:r,path:t})}else typeof e=="object"&&e!==null?Object.entries(e).forEach(([r,i])=>{n.push(...$(i,t?`${t}.${r}`:r))}):Array.isArray(e)&&e.forEach((r,i)=>{n.push(...$(r,`${t}[${i}]`))});return n}function Ne(e,t,n,r,i,s){const o=[];o.push(...$(t,"meta")),o.push(...$(n,"layout")),Object.entries(r).forEach(([c,f])=>{o.push(...$(f,`fields.${c}`))}),Object.entries(i).forEach(([c,f])=>{o.push(...$(f,`buttons.${c}`))});const a=Object.keys(s);o.forEach(({name:c,path:f})=>{s[c]||e.push({type:"missing_function",severity:"high",path:f,message:`Function "{{${c}}}" not found in registries.functions`,suggestion:a.length>0?`Available functions: ${a.join(", ")}`:"No functions are registered. Add functions to registries.functions or load from functions.js"})})}function Ve(e,t,n){Object.entries(t).forEach(([r,i])=>{if(typeof i=="object"&&i!==null&&i["..."]){const s=i["..."];t[s]||e.push({type:"missing_spread_ref",severity:"high",path:`fields.${r}`,message:`Spread reference "...": "${s}" not found in fields registry`,suggestion:`Available fields: ${Object.keys(t).join(", ")}`})}}),Object.entries(n).forEach(([r,i])=>{if(typeof i=="object"&&i!==null&&i["..."]){const s=i["..."];n[s]||e.push({type:"missing_spread_ref",severity:"high",path:`buttons.${r}`,message:`Spread reference "...": "${s}" not found in buttons registry`,suggestion:`Available buttons: ${Object.keys(n).join(", ")}`})}})}function We(e,t,n){const r=Object.keys(n);function i(s,o){typeof s=="string"?n[s]||e.push({type:"missing_field_ref",severity:"high",path:o,message:`Layout references field "${s}" but it doesn't exist in fields registry`,suggestion:r.length>0?`Available fields: ${r.join(", ")}`:"No fields are defined. Add fields to registries.fields"}):Array.isArray(s)?s.forEach((a,c)=>{i(a,`${o}[${c}]`)}):typeof s=="object"&&s!==null&&(s.layout&&s.layout.forEach((a,c)=>{i(a,`${o}.layout[${c}]`)}),s.rows&&s.rows.forEach((a,c)=>{i(a,`${o}.rows[${c}]`)}))}t.forEach((s,o)=>{i(s,`layout[${o}]`)})}function ze(e){const t=new Set;function n(r){typeof r=="string"?t.add(r):Array.isArray(r)?r.forEach(n):typeof r=="object"&&r!==null&&(r.layout&&r.layout.forEach(n),r.rows&&r.rows.forEach(n))}return e.forEach(n),t}function Ue(e,t,n,r){const i=Object.keys(n);Object.entries(t).forEach(([s,o])=>{if(!(r&&!r.has(s))&&typeof o=="object"&&o!==null){const a=o.options;if(typeof a=="string"){if(a.startsWith("{{")&&a.endsWith("}}"))return;const c=a.startsWith("@")?a.slice(1):a;Array.isArray(n[c])||e.push({type:"missing_options_ref",severity:"high",path:`fields.${s}.options`,message:`Options reference "${a}" not found in registries.optionSets`,suggestion:i.length>0?`Available option sets: ${i.join(", ")}`:"No option sets are defined. Add options to registries.optionSets"})}}})}function qe(e,t){Object.entries(t).forEach(([n,r])=>{typeof r=="object"&&r!==null&&typeof r.value=="function"&&(r.value.prototype||e.push({type:"arrow_function_in_wrapper",severity:"high",path:`functions.${n}`,message:`Function "${n}" uses arrow function in wrapper format`,suggestion:'Arrow functions cannot access stage via "this". Use regular function: function(context, event) { ... }'}))})}function Ke(e,t){Object.entries(t).forEach(([n,r])=>{if(typeof r=="object"&&r!==null){const i=r.component,s=r.options!==void 0;(i==="Checkbox"||i==="Radio")&&!s&&e.push({type:"missing_options",severity:"medium",path:`fields.${n}`,message:`Field "${n}" uses component="${i}" but has no options defined`,suggestion:'Add options property: options: ["option1", "option2"] or options: "optionSetName"'}),i==="Select"&&!s&&e.push({type:"missing_options",severity:"medium",path:`fields.${n}`,message:`Field "${n}" uses component="Select" but has no options defined`,suggestion:'Add options property: options: ["option1", "option2"] or options: "optionSetName"'})}})}function Ge(e){if(e.length===0)return"✅ No validation errors found";const t=[`
13
+ ⚠️ Found ${e.length} validation error${e.length>1?"s":""}:
14
+ `];return e.forEach((n,r)=>{const i=n.severity==="high"?"🔴":"🟡";t.push(`${i} ${r+1}. ${n.message}`),t.push(` Path: ${n.path}`),n.suggestion&&t.push(` 💡 ${n.suggestion}`),t.push("")}),t.join(`
15
+ `)}const K=["javascript:","data:","vbscript:"],Be=new Set(["b","i","em","strong","u","s","strike","del","p","br","hr","div","span","h1","h2","h3","h4","h5","h6","ul","ol","li","a","code","pre","blockquote"]),P={a:["href","title"],span:["class"],div:["class"],"*":["id","class"]};function Ie(e){if(!e||typeof e!="string")return"";const t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;"};return e.replace(/[&<>"'/]/g,n=>t[n])}function O(e,t={}){if(typeof e!="string")return e;const{allowBasicHTML:n=!1,maxLength:r=void 0}=t;let i=e;return n?i=G(i):i=i.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,"").replace(/\s*on\w+\s*=\s*["'][^"']*["']/gi,"").replace(/javascript:/gi,"").replace(/data:text\/html[^,]*,/gi,"").replace(/<[^>]+>/g,""),r&&i.length>r&&(i=i.substring(0,r)),i.trim()}function G(e){const t=document.createElement("div");return t.innerHTML=e,t.querySelectorAll("*").forEach(r=>{const i=r.tagName.toLowerCase();if(!Be.has(i)){r.remove();return}Array.from(r.attributes).forEach(o=>{const a=o.name.toLowerCase();(!(P[i]||[]).concat(P["*"]||[]).includes(a)||a.startsWith("on"))&&r.removeAttribute(o.name),(a==="href"||a==="src")&&K.some(f=>o.value.toLowerCase().startsWith(f))&&(r.removeAttribute(o.name),l.logger.error(`Blocked dangerous protocol in ${a}: ${o.value}`))})}),t.innerHTML}function Je(e,t={}){return!e||typeof e!="string"?"":(l.logger.warn("HTML sanitization in use. Consider using plain text or markdown instead."),G(e))}function Ye(e){if(!e||typeof e!="string")return"";let t=e.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,"").replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi,"");const n=document.createElement("div");return n.innerHTML=t,(n.textContent||n.innerText||"").trim()}function Ze(e){if(!e||typeof e!="string")return!1;const t=e.toLowerCase().trim();return K.some(n=>t.startsWith(n))?(l.logger.error(`Dangerous URL protocol detected: ${e}`),!1):t.startsWith("/")||t.startsWith("#")||t.startsWith("http://")||t.startsWith("https://")||t.startsWith("mailto:")}function B(e,t={}){if(!e||typeof e!="object")return e;const{exclude:n=[],fieldOptions:r={}}=t,i={};for(const[s,o]of Object.entries(e)){if(n.includes(s)){i[s]=o;continue}const a=r[s]||{};typeof o=="string"?i[s]=O(o,a):Array.isArray(o)?i[s]=o.map(c=>typeof c=="string"?O(c,a):c):o&&typeof o=="object"?i[s]=B(o,{exclude:a.exclude,fieldOptions:a.nestedOptions}):i[s]=o}return i}function D(e={}){return(t,n={})=>O(t,{...e,...n})}const Qe={text:D({maxLength:1e3}),name:D({maxLength:100}),email:D({maxLength:254}),url:D({maxLength:2083}),phone:D({maxLength:20}),username:D({maxLength:50}),password:e=>e,richText:D({allowBasicHTML:!0,maxLength:1e4}),comment:D({maxLength:5e3}),numeric:e=>typeof e!="string"?e:e.replace(/[^\d.-]/g,"")},Xe={innerHTML:"NEVER use innerHTML with user-generated content. Use textContent for safe text rendering.",userInput:"Always validate and sanitize user input on both client and server.",externalContent:"Treat all external content as untrusted. Sanitize before rendering.",urlValidation:"Always validate URLs before using them in href or src attributes."};function xe({name:e,context:t,actionsContext:n,parentValidate:r}){var i;return{...t,registries:t.registries,functions:t.functions||((i=t.registries)==null?void 0:i.functions)||{},form:{...n,getValue:s=>(n.getValue(e)||{})[s],setValue:(s,o)=>{const a=n.getValue(e)||{};n.setValue(e,{...a,[s]:o})},getFieldErrors:s=>(n.getFieldErrors(e)||{})[s]||[],setFieldErrors:(s,o)=>{const a=n.getFieldErrors(e)||{};n.setFieldErrors(e,{...a,[s]:o})},setFieldError:(s,o)=>{const a=n.getFieldErrors(e)||{};n.setFieldErrors(e,{...a,[s]:Array.isArray(o)?o:[o]})},clearFieldError:s=>{const o=n.getFieldErrors(e)||{},{[s]:a,...c}=o;n.setFieldErrors(e,c)}},fieldPath:t.fieldPath?`${t.fieldPath}.${e}`:e,parentValidate:r}}Object.defineProperty(exports,"createTLPSchema",{enumerable:!0,get:()=>l.createTLPSchema});exports.DEFAULT_RECORD=ae;exports.FORM_TLP_DECLARATIONS=M;exports.META_DEFAULTS=v;exports.SECURITY_WARNINGS=Xe;exports.applyMask=Re;exports.applyMetaDefaults=ce;exports.coreButtonFunctions=ke;exports.createLiveConfig=J;exports.createSanitizer=D;exports.createScopedFormContext=xe;exports.defaultActions=Ce;exports.defaultButtons=$e;exports.detectMaskType=Oe;exports.escapeHTML=Ie;exports.evaluateVisibility=U;exports.executeHooks=L;exports.formSchema=R;exports.formatValidationErrors=Ge;exports.formatValue=Te;exports.functionError=fe;exports.getButtonDataAttributes=pe;exports.getFieldDataAttributes=ge;exports.getFieldsToClear=me;exports.getGroupDataAttributes=ye;exports.getRecordData=N;exports.getRecordKey=W;exports.initializeRecordFields=ue;exports.isSafeURL=Ze;exports.loadJsonConfig=I;exports.loadLiveConfig=Y;exports.missingHandler=le;exports.missingReference=z;exports.mountForm=oe;exports.parseValue=_e;exports.resolveProp=q;exports.resolveVisibility=je;exports.runtimeError=j;exports.runtimeWarning=H;exports.sanitizeHTML=Je;exports.sanitizeInput=O;exports.sanitizeRecord=B;exports.sanitizers=Qe;exports.separateProps=Pe;exports.setRecordData=V;exports.slugify=he;exports.stripHTML=Ye;exports.transformError=de;exports.validateConfig=Me;exports.wrapWithHooks=Le;
16
+ //# sourceMappingURL=index.cjs.map