@pilotiq/pilotiq 0.6.2 → 0.7.1
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 +6 -2
- package/CHANGELOG.md +614 -0
- package/CLAUDE.md +6 -5
- package/dist/Column.d.ts +35 -0
- package/dist/Column.d.ts.map +1 -1
- package/dist/Column.js +41 -0
- package/dist/Column.js.map +1 -1
- package/dist/Page.d.ts +13 -4
- package/dist/Page.d.ts.map +1 -1
- package/dist/Page.js +9 -2
- package/dist/Page.js.map +1 -1
- package/dist/Pilotiq.d.ts +84 -0
- package/dist/Pilotiq.d.ts.map +1 -1
- package/dist/Pilotiq.js +66 -0
- package/dist/Pilotiq.js.map +1 -1
- package/dist/Resource.d.ts +26 -0
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +9 -0
- package/dist/Resource.js.map +1 -1
- package/dist/actions/exportFactory.js +1 -1
- package/dist/actions/exportFactory.js.map +1 -1
- package/dist/columns/SelectColumn.d.ts +32 -5
- package/dist/columns/SelectColumn.d.ts.map +1 -1
- package/dist/columns/SelectColumn.js +37 -7
- package/dist/columns/SelectColumn.js.map +1 -1
- package/dist/defaultPages.d.ts.map +1 -1
- package/dist/defaultPages.js +3 -0
- package/dist/defaultPages.js.map +1 -1
- package/dist/elements/Form.d.ts +17 -0
- package/dist/elements/Form.d.ts.map +1 -1
- package/dist/elements/Form.js +17 -0
- package/dist/elements/Form.js.map +1 -1
- package/dist/elements/Table.d.ts +26 -0
- package/dist/elements/Table.d.ts.map +1 -1
- package/dist/elements/Table.js +15 -1
- package/dist/elements/Table.js.map +1 -1
- package/dist/elements/TableGroup.d.ts +84 -0
- package/dist/elements/TableGroup.d.ts.map +1 -1
- package/dist/elements/TableGroup.js +103 -0
- package/dist/elements/TableGroup.js.map +1 -1
- package/dist/elements/dispatchForm.d.ts.map +1 -1
- package/dist/elements/dispatchForm.js +36 -6
- package/dist/elements/dispatchForm.js.map +1 -1
- package/dist/elements/dispatchTable.d.ts +12 -0
- package/dist/elements/dispatchTable.d.ts.map +1 -1
- package/dist/elements/dispatchTable.js +103 -28
- package/dist/elements/dispatchTable.js.map +1 -1
- package/dist/fields/Field.d.ts +7 -2
- package/dist/fields/Field.d.ts.map +1 -1
- package/dist/fields/Field.js +8 -3
- package/dist/fields/Field.js.map +1 -1
- package/dist/fields/RepeaterField.d.ts +65 -0
- package/dist/fields/RepeaterField.d.ts.map +1 -1
- package/dist/fields/RepeaterField.js +48 -0
- package/dist/fields/RepeaterField.js.map +1 -1
- package/dist/orm/modelDefaults.d.ts.map +1 -1
- package/dist/orm/modelDefaults.js +19 -0
- package/dist/orm/modelDefaults.js.map +1 -1
- package/dist/pageData.d.ts +20 -0
- package/dist/pageData.d.ts.map +1 -1
- package/dist/pageData.js +242 -34
- package/dist/pageData.js.map +1 -1
- package/dist/react/AppShell.d.ts +17 -1
- package/dist/react/AppShell.d.ts.map +1 -1
- package/dist/react/AppShell.js +34 -3
- package/dist/react/AppShell.js.map +1 -1
- package/dist/react/PendingSuggestionApplierRegistry.d.ts +34 -0
- package/dist/react/PendingSuggestionApplierRegistry.d.ts.map +1 -0
- package/dist/react/PendingSuggestionApplierRegistry.js +51 -0
- package/dist/react/PendingSuggestionApplierRegistry.js.map +1 -0
- package/dist/react/PendingSuggestionOverlayRegistry.d.ts +46 -0
- package/dist/react/PendingSuggestionOverlayRegistry.d.ts.map +1 -0
- package/dist/react/PendingSuggestionOverlayRegistry.js +16 -0
- package/dist/react/PendingSuggestionOverlayRegistry.js.map +1 -0
- package/dist/react/PendingSuggestionsContext.d.ts +153 -0
- package/dist/react/PendingSuggestionsContext.d.ts.map +1 -0
- package/dist/react/PendingSuggestionsContext.js +46 -0
- package/dist/react/PendingSuggestionsContext.js.map +1 -0
- package/dist/react/SchemaRenderer.d.ts.map +1 -1
- package/dist/react/SchemaRenderer.js +312 -39
- package/dist/react/SchemaRenderer.js.map +1 -1
- package/dist/react/cells/EditableCell.d.ts +8 -0
- package/dist/react/cells/EditableCell.d.ts.map +1 -1
- package/dist/react/cells/EditableCell.js +6 -2
- package/dist/react/cells/EditableCell.js.map +1 -1
- package/dist/react/fields/CheckboxListInput.d.ts.map +1 -1
- package/dist/react/fields/CheckboxListInput.js +29 -2
- package/dist/react/fields/CheckboxListInput.js.map +1 -1
- package/dist/react/fields/ColorInput.d.ts.map +1 -1
- package/dist/react/fields/ColorInput.js +28 -2
- package/dist/react/fields/ColorInput.js.map +1 -1
- package/dist/react/fields/DateTimeInput.d.ts.map +1 -1
- package/dist/react/fields/DateTimeInput.js +28 -2
- package/dist/react/fields/DateTimeInput.js.map +1 -1
- package/dist/react/fields/FieldShell.d.ts.map +1 -1
- package/dist/react/fields/FieldShell.js +161 -3
- package/dist/react/fields/FieldShell.js.map +1 -1
- package/dist/react/fields/FileUploadInput.d.ts.map +1 -1
- package/dist/react/fields/FileUploadInput.js +27 -2
- package/dist/react/fields/FileUploadInput.js.map +1 -1
- package/dist/react/fields/KeyValueInput.d.ts.map +1 -1
- package/dist/react/fields/KeyValueInput.js +33 -2
- package/dist/react/fields/KeyValueInput.js.map +1 -1
- package/dist/react/fields/RadioInput.d.ts.map +1 -1
- package/dist/react/fields/RadioInput.js +28 -2
- package/dist/react/fields/RadioInput.js.map +1 -1
- package/dist/react/fields/SelectFieldInput.d.ts.map +1 -1
- package/dist/react/fields/SelectFieldInput.js +31 -2
- package/dist/react/fields/SelectFieldInput.js.map +1 -1
- package/dist/react/fields/SliderInput.d.ts.map +1 -1
- package/dist/react/fields/SliderInput.js +26 -2
- package/dist/react/fields/SliderInput.js.map +1 -1
- package/dist/react/fields/TagsInput.d.ts.map +1 -1
- package/dist/react/fields/TagsInput.js +26 -2
- package/dist/react/fields/TagsInput.js.map +1 -1
- package/dist/react/fields/ToggleFieldInput.d.ts.map +1 -1
- package/dist/react/fields/ToggleFieldInput.js +29 -2
- package/dist/react/fields/ToggleFieldInput.js.map +1 -1
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +3 -0
- package/dist/react/index.js.map +1 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +55 -2
- package/dist/routes.js.map +1 -1
- package/dist/schema/Section.d.ts +16 -0
- package/dist/schema/Section.d.ts.map +1 -1
- package/dist/schema/Section.js +16 -0
- package/dist/schema/Section.js.map +1 -1
- package/dist/schema/Wizard.d.ts +45 -0
- package/dist/schema/Wizard.d.ts.map +1 -1
- package/dist/schema/Wizard.js +50 -0
- package/dist/schema/Wizard.js.map +1 -1
- package/dist/schema/resolveSchema.d.ts +8 -0
- package/dist/schema/resolveSchema.d.ts.map +1 -1
- package/dist/schema/resolveSchema.js +70 -1
- package/dist/schema/resolveSchema.js.map +1 -1
- package/dist/sessionFilters.d.ts.map +1 -1
- package/dist/sessionFilters.js +12 -1
- package/dist/sessionFilters.js.map +1 -1
- package/dist/styles/file-upload.css +13 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +19 -12
- package/dist/vite.js.map +1 -1
- package/package.json +6 -4
- package/src/Column.test.ts +36 -0
- package/src/Column.ts +54 -0
- package/src/Page.ts +13 -4
- package/src/Pilotiq.ts +109 -0
- package/src/Resource.ts +29 -0
- package/src/actions/exportFactory.ts +1 -1
- package/src/columns/SelectColumn.ts +46 -8
- package/src/columns/editableColumns.test.ts +45 -0
- package/src/defaultPages.ts +3 -0
- package/src/elements/Form.ts +19 -0
- package/src/elements/Table.ts +35 -1
- package/src/elements/TableGroup.test.ts +111 -0
- package/src/elements/TableGroup.ts +135 -0
- package/src/elements/dispatchForm.ts +34 -7
- package/src/elements/dispatchTable.test.ts +267 -0
- package/src/elements/dispatchTable.ts +111 -32
- package/src/fields/Field.test.ts +15 -0
- package/src/fields/Field.ts +8 -3
- package/src/fields/RepeaterField.ts +104 -0
- package/src/fields/RepeaterRelationship.test.ts +173 -0
- package/src/nestedRelationManagerData.test.ts +21 -0
- package/src/orm/modelDefaults.ts +21 -0
- package/src/pageData.ts +267 -47
- package/src/react/AppShell.tsx +55 -4
- package/src/react/PendingSuggestionApplierRegistry.ts +80 -0
- package/src/react/PendingSuggestionOverlayRegistry.ts +54 -0
- package/src/react/PendingSuggestionsContext.tsx +172 -0
- package/src/react/SchemaRenderer.tsx +504 -95
- package/src/react/cells/EditableCell.tsx +11 -2
- package/src/react/fields/CheckboxListInput.tsx +23 -2
- package/src/react/fields/ColorInput.tsx +22 -2
- package/src/react/fields/DateTimeInput.tsx +22 -2
- package/src/react/fields/FieldShell.tsx +167 -3
- package/src/react/fields/FileUploadInput.tsx +21 -2
- package/src/react/fields/KeyValueInput.tsx +32 -2
- package/src/react/fields/RadioInput.tsx +23 -2
- package/src/react/fields/SelectFieldInput.tsx +25 -2
- package/src/react/fields/SliderInput.tsx +20 -2
- package/src/react/fields/TagsInput.tsx +20 -2
- package/src/react/fields/ToggleFieldInput.tsx +23 -2
- package/src/react/index.ts +18 -0
- package/src/relationManagerData.test.ts +451 -2
- package/src/routes.ts +58 -2
- package/src/schema/Section.ts +17 -0
- package/src/schema/Wizard.ts +67 -0
- package/src/schema/containers.test.ts +90 -0
- package/src/schema/resolveSchema.test.ts +50 -0
- package/src/schema/resolveSchema.ts +79 -1
- package/src/sessionFilters.test.ts +23 -0
- package/src/sessionFilters.ts +11 -1
- package/src/styles/file-upload.css +13 -0
- package/src/vite.ts +19 -12
package/src/schema/Wizard.ts
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { Element } from './Element.js'
|
|
2
|
+
import type { Action } from '../actions/Action.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Customizer for the wizard's built-in nav buttons (`submitAction`,
|
|
6
|
+
* `nextAction`, `previousAction`). Pass an `Action` to replace the
|
|
7
|
+
* default outright, or a function that receives the framework-built
|
|
8
|
+
* default and returns a customized clone.
|
|
9
|
+
*
|
|
10
|
+
* Only chrome carries through to the renderer (label / icon / color /
|
|
11
|
+
* size / outlined / iconOnly / tooltip / disabled rules). The button's
|
|
12
|
+
* click behavior stays hardwired to advance / recede / submit-form —
|
|
13
|
+
* dispatch overrides (`.handler()`, `.action()`, `.href()`) are
|
|
14
|
+
* intentionally ignored so the wizard chrome never breaks navigation.
|
|
15
|
+
*/
|
|
16
|
+
export type WizardActionCustomizer = Action | ((defaultAction: Action) => Action)
|
|
2
17
|
|
|
3
18
|
/**
|
|
4
19
|
* Context handed to `Step.beforeValidation` / `afterValidation` hooks.
|
|
@@ -108,6 +123,10 @@ export class Wizard extends Element {
|
|
|
108
123
|
private _skippable = false
|
|
109
124
|
private _startOnStep = 0
|
|
110
125
|
private _persist = true
|
|
126
|
+
private _persistStepInQueryString: string | undefined = undefined
|
|
127
|
+
private _submitAction?: WizardActionCustomizer
|
|
128
|
+
private _nextAction?: WizardActionCustomizer
|
|
129
|
+
private _previousAction?: WizardActionCustomizer
|
|
111
130
|
|
|
112
131
|
private constructor() { super() }
|
|
113
132
|
|
|
@@ -140,6 +159,51 @@ export class Wizard extends Element {
|
|
|
140
159
|
*/
|
|
141
160
|
persist(v: boolean): this { this._persist = v; return this }
|
|
142
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Sync the active step index to the URL as `?<key>=N` (1-based for
|
|
164
|
+
* human-friendly URLs). When set, the URL value wins over localStorage
|
|
165
|
+
* on initial mount, so deep-linking to a specific step works. Bare
|
|
166
|
+
* `persistStepInQueryString()` uses the default key `'step'`; pass a
|
|
167
|
+
* string to override (multi-wizard pages should use distinct keys to
|
|
168
|
+
* avoid collisions). Pass `false` to disable.
|
|
169
|
+
*/
|
|
170
|
+
persistStepInQueryString(key: string | boolean = true): this {
|
|
171
|
+
if (key === false) { this._persistStepInQueryString = undefined; return this }
|
|
172
|
+
this._persistStepInQueryString = key === true ? 'step' : key
|
|
173
|
+
return this
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Customize the chrome of the built-in Submit button shown on the
|
|
178
|
+
* wizard's final step. By default the wizard renders a hint pointing
|
|
179
|
+
* at the surrounding form's Save button — calling this method instead
|
|
180
|
+
* mounts a real `<button type="submit">` inside the wizard chrome so
|
|
181
|
+
* the wizard becomes self-contained. Pair with `CreatePage.getFormActions(R)`
|
|
182
|
+
* returning `[]` to suppress the page-level Save when you don't want
|
|
183
|
+
* two submits on the same page.
|
|
184
|
+
*/
|
|
185
|
+
submitAction(action: WizardActionCustomizer): this {
|
|
186
|
+
this._submitAction = action
|
|
187
|
+
return this
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Customize the chrome of the built-in Next button. */
|
|
191
|
+
nextAction(action: WizardActionCustomizer): this {
|
|
192
|
+
this._nextAction = action
|
|
193
|
+
return this
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Customize the chrome of the built-in Back / Previous button. */
|
|
197
|
+
previousAction(action: WizardActionCustomizer): this {
|
|
198
|
+
this._previousAction = action
|
|
199
|
+
return this
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getPersistStepInQueryString(): string | undefined { return this._persistStepInQueryString }
|
|
203
|
+
getSubmitAction(): WizardActionCustomizer | undefined { return this._submitAction }
|
|
204
|
+
getNextAction(): WizardActionCustomizer | undefined { return this._nextAction }
|
|
205
|
+
getPreviousAction(): WizardActionCustomizer | undefined { return this._previousAction }
|
|
206
|
+
|
|
143
207
|
getType(): string { return 'wizard' }
|
|
144
208
|
|
|
145
209
|
toMeta(): Record<string, unknown> {
|
|
@@ -148,6 +212,9 @@ export class Wizard extends Element {
|
|
|
148
212
|
skippable: this._skippable,
|
|
149
213
|
startOnStep: this._startOnStep,
|
|
150
214
|
persist: this._persist,
|
|
215
|
+
...(this._persistStepInQueryString
|
|
216
|
+
? { persistStepInQueryString: this._persistStepInQueryString }
|
|
217
|
+
: {}),
|
|
151
218
|
}
|
|
152
219
|
}
|
|
153
220
|
}
|
|
@@ -427,6 +427,96 @@ describe('Wizard / Step (Plan #8)', () => {
|
|
|
427
427
|
})
|
|
428
428
|
})
|
|
429
429
|
|
|
430
|
+
describe('persistStepInQueryString', () => {
|
|
431
|
+
it('omits the meta key by default', async () => {
|
|
432
|
+
const tree = [Wizard.make().steps([Step.make('a').schema([])])]
|
|
433
|
+
const result = await resolveSchema(tree)
|
|
434
|
+
assert.equal(result[0]!['persistStepInQueryString'], undefined)
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
it('bare call defaults to "step"', async () => {
|
|
438
|
+
const tree = [
|
|
439
|
+
Wizard.make().persistStepInQueryString().steps([Step.make('a').schema([])]),
|
|
440
|
+
]
|
|
441
|
+
const result = await resolveSchema(tree)
|
|
442
|
+
assert.equal(result[0]!['persistStepInQueryString'], 'step')
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
it('explicit string overrides the key', async () => {
|
|
446
|
+
const tree = [
|
|
447
|
+
Wizard.make().persistStepInQueryString('checkout').steps([Step.make('a').schema([])]),
|
|
448
|
+
]
|
|
449
|
+
const result = await resolveSchema(tree)
|
|
450
|
+
assert.equal(result[0]!['persistStepInQueryString'], 'checkout')
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
it('false clears a previously-set key', async () => {
|
|
454
|
+
const tree = [
|
|
455
|
+
Wizard.make().persistStepInQueryString('foo').persistStepInQueryString(false)
|
|
456
|
+
.steps([Step.make('a').schema([])]),
|
|
457
|
+
]
|
|
458
|
+
const result = await resolveSchema(tree)
|
|
459
|
+
assert.equal(result[0]!['persistStepInQueryString'], undefined)
|
|
460
|
+
})
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
describe('submitAction / nextAction / previousAction customizers', () => {
|
|
464
|
+
it('omits the meta keys when no customizer is set', async () => {
|
|
465
|
+
const tree = [Wizard.make().steps([Step.make('a').schema([])])]
|
|
466
|
+
const result = await resolveSchema(tree)
|
|
467
|
+
assert.equal(result[0]!['submitAction'], undefined)
|
|
468
|
+
assert.equal(result[0]!['nextAction'], undefined)
|
|
469
|
+
assert.equal(result[0]!['previousAction'], undefined)
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
it('customizer receives a default Action with sensible chrome', async () => {
|
|
473
|
+
let receivedSubmit: string | undefined
|
|
474
|
+
let receivedNext: string | undefined
|
|
475
|
+
let receivedPrevious: string | undefined
|
|
476
|
+
const tree = [
|
|
477
|
+
Wizard.make()
|
|
478
|
+
.submitAction(a => { receivedSubmit = a.getLabel(); return a })
|
|
479
|
+
.nextAction(a => { receivedNext = a.getLabel(); return a })
|
|
480
|
+
.previousAction(a => { receivedPrevious = a.getLabel(); return a })
|
|
481
|
+
.steps([Step.make('a').schema([])]),
|
|
482
|
+
]
|
|
483
|
+
await resolveSchema(tree)
|
|
484
|
+
assert.equal(receivedSubmit, 'Submit')
|
|
485
|
+
assert.equal(receivedNext, 'Next')
|
|
486
|
+
assert.equal(receivedPrevious, 'Back')
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
it('customizer chrome flows through to the resolved meta', async () => {
|
|
490
|
+
const tree = [
|
|
491
|
+
Wizard.make()
|
|
492
|
+
.submitAction(a => a.label('Create campaign').size('lg'))
|
|
493
|
+
.nextAction(a => a.label('Continue').icon('arrow-right'))
|
|
494
|
+
.previousAction(Action.make('back').label('Go back').color('ghost'))
|
|
495
|
+
.steps([Step.make('a').schema([])]),
|
|
496
|
+
]
|
|
497
|
+
const result = await resolveSchema(tree)
|
|
498
|
+
const submit = result[0]!['submitAction'] as Record<string, unknown>
|
|
499
|
+
const next = result[0]!['nextAction'] as Record<string, unknown>
|
|
500
|
+
const previous = result[0]!['previousAction'] as Record<string, unknown>
|
|
501
|
+
assert.equal(submit['label'], 'Create campaign')
|
|
502
|
+
assert.equal(submit['size'], 'lg')
|
|
503
|
+
assert.equal(next['label'], 'Continue')
|
|
504
|
+
assert.equal(next['icon'], 'arrow-right')
|
|
505
|
+
assert.equal(previous['label'], 'Go back')
|
|
506
|
+
assert.equal(previous['color'], 'ghost')
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
it('customizer that hides the action drops the slot from meta', async () => {
|
|
510
|
+
const tree = [
|
|
511
|
+
Wizard.make()
|
|
512
|
+
.nextAction(a => a.visible(false))
|
|
513
|
+
.steps([Step.make('a').schema([])]),
|
|
514
|
+
]
|
|
515
|
+
const result = await resolveSchema(tree)
|
|
516
|
+
assert.equal(result[0]!['nextAction'], undefined)
|
|
517
|
+
})
|
|
518
|
+
})
|
|
519
|
+
|
|
430
520
|
it('all step children resolve so cross-step $get works', async () => {
|
|
431
521
|
// Step 2 has a Section that hides based on a value entered in Step 0;
|
|
432
522
|
// both steps must be resolved on every cycle for the predicate to fire.
|
|
@@ -10,6 +10,9 @@ import {
|
|
|
10
10
|
import { Text } from './Text.js'
|
|
11
11
|
import { Heading } from './Heading.js'
|
|
12
12
|
import { Card } from './Card.js'
|
|
13
|
+
import { Section } from './Section.js'
|
|
14
|
+
import { Form } from '../elements/Form.js'
|
|
15
|
+
import { TextField } from '../fields/TextField.js'
|
|
13
16
|
|
|
14
17
|
beforeEach(() => _resetResolverRegistry())
|
|
15
18
|
|
|
@@ -326,4 +329,51 @@ describe('resolveSchema', () => {
|
|
|
326
329
|
assert.equal(result[0]!._layout, undefined)
|
|
327
330
|
})
|
|
328
331
|
})
|
|
332
|
+
|
|
333
|
+
describe('inlineLabel cascade (Form / Section)', () => {
|
|
334
|
+
it('Form.inlineLabel(true) cascades to descendant fields', async () => {
|
|
335
|
+
const tree = [Form.make().inlineLabel().schema([TextField.make('a'), TextField.make('b')])]
|
|
336
|
+
const [form] = await resolveSchema(tree)
|
|
337
|
+
const [a, b] = form!.children! as ElementMeta[]
|
|
338
|
+
assert.equal((a as { inlineLabel?: boolean }).inlineLabel, true)
|
|
339
|
+
assert.equal((b as { inlineLabel?: boolean }).inlineLabel, true)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it('Field.inlineLabel(false) overrides the Form cascade', async () => {
|
|
343
|
+
const tree = [Form.make().inlineLabel().schema([
|
|
344
|
+
TextField.make('a'),
|
|
345
|
+
TextField.make('b').inlineLabel(false),
|
|
346
|
+
])]
|
|
347
|
+
const [form] = await resolveSchema(tree)
|
|
348
|
+
const [a, b] = form!.children! as ElementMeta[]
|
|
349
|
+
assert.equal((a as { inlineLabel?: boolean }).inlineLabel, true)
|
|
350
|
+
assert.equal('inlineLabel' in (b as object), false)
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it('nested Section.inlineLabel(false) overrides the Form cascade for its subtree', async () => {
|
|
354
|
+
const tree = [Form.make().inlineLabel().schema([
|
|
355
|
+
TextField.make('above'),
|
|
356
|
+
Section.make('inner').inlineLabel(false).schema([TextField.make('inside')]),
|
|
357
|
+
])]
|
|
358
|
+
const [form] = await resolveSchema(tree)
|
|
359
|
+
const [above, section] = form!.children! as ElementMeta[]
|
|
360
|
+
const [inside] = (section as ElementMeta).children! as ElementMeta[]
|
|
361
|
+
assert.equal((above as { inlineLabel?: boolean }).inlineLabel, true)
|
|
362
|
+
assert.equal('inlineLabel' in (inside as object), false)
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('Section.inlineLabel(true) cascades without an outer Form', async () => {
|
|
366
|
+
const tree = [Section.make('s').inlineLabel().schema([TextField.make('a')])]
|
|
367
|
+
const [section] = await resolveSchema(tree)
|
|
368
|
+
const [a] = section!.children! as ElementMeta[]
|
|
369
|
+
assert.equal((a as { inlineLabel?: boolean }).inlineLabel, true)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
it('default (no setter calls) emits no inlineLabel anywhere', async () => {
|
|
373
|
+
const tree = [Form.make().schema([TextField.make('a')])]
|
|
374
|
+
const [form] = await resolveSchema(tree)
|
|
375
|
+
const [a] = form!.children! as ElementMeta[]
|
|
376
|
+
assert.equal('inlineLabel' in (a as object), false)
|
|
377
|
+
})
|
|
378
|
+
})
|
|
329
379
|
})
|
|
@@ -22,7 +22,9 @@ import {
|
|
|
22
22
|
type RepeatableEntryRowMeta,
|
|
23
23
|
} from '../entries/RepeatableEntry.js'
|
|
24
24
|
import { Section } from './Section.js'
|
|
25
|
+
import { Wizard, type WizardActionCustomizer } from './Wizard.js'
|
|
25
26
|
import { TextField } from '../fields/TextField.js'
|
|
27
|
+
import { Form } from '../elements/Form.js'
|
|
26
28
|
|
|
27
29
|
export interface SchemaContext {
|
|
28
30
|
user?: { name?: string; email?: string; [key: string]: unknown }
|
|
@@ -113,6 +115,14 @@ export interface RenderContext extends SchemaContext {
|
|
|
113
115
|
*/
|
|
114
116
|
blockType?: string
|
|
115
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Cascading `inlineLabel` default set by an ancestor `Form` or
|
|
120
|
+
* `Section` via `.inlineLabel(true)`. Read by `Field.buildMeta` when the
|
|
121
|
+
* field hasn't called `inlineLabel()` itself; explicit field-level
|
|
122
|
+
* setting always wins. A nested container's `.inlineLabel(false)`
|
|
123
|
+
* overrides an outer container's `.inlineLabel(true)` for its subtree.
|
|
124
|
+
*/
|
|
125
|
+
inlineLabelDefault?: boolean
|
|
116
126
|
}
|
|
117
127
|
|
|
118
128
|
export type SchemaDefinition =
|
|
@@ -305,9 +315,15 @@ async function resolveOne(el: Element, ctx: RenderContext): Promise<ElementMeta
|
|
|
305
315
|
return meta
|
|
306
316
|
}
|
|
307
317
|
|
|
318
|
+
// Push `inlineLabelDefault` into the child ctx when this element is a
|
|
319
|
+
// `Form` or `Section` whose `.inlineLabel(...)` was set. Children inherit
|
|
320
|
+
// until another container resets the flag. Field-level `inlineLabel(...)`
|
|
321
|
+
// calls always win on read (see `Field.buildMeta`).
|
|
322
|
+
const childCtx = deriveChildContext(el, ctx)
|
|
323
|
+
|
|
308
324
|
const children = el.getChildren()
|
|
309
325
|
if (children && children.length > 0) {
|
|
310
|
-
meta.children = await resolveAll(children,
|
|
326
|
+
meta.children = await resolveAll(children, childCtx)
|
|
311
327
|
}
|
|
312
328
|
|
|
313
329
|
// Filament v5 — `Section.afterHeader([Action…])` resolves through the
|
|
@@ -335,6 +351,30 @@ async function resolveOne(el: Element, ctx: RenderContext): Promise<ElementMeta
|
|
|
335
351
|
}
|
|
336
352
|
}
|
|
337
353
|
|
|
354
|
+
// `Wizard.submitAction() / nextAction() / previousAction()` — build a
|
|
355
|
+
// framework default Action for each slot, run the user's customizer
|
|
356
|
+
// (or take the explicit Action), then resolve through the standard
|
|
357
|
+
// walker so `.visible() / .disabled()` rules evaluate the same way
|
|
358
|
+
// as anywhere else. Stamped under `meta.submitAction / nextAction /
|
|
359
|
+
// previousAction`; sparse — absent when the user hasn't customized.
|
|
360
|
+
if (el instanceof Wizard) {
|
|
361
|
+
const submitC = el.getSubmitAction()
|
|
362
|
+
const nextC = el.getNextAction()
|
|
363
|
+
const previousC = el.getPreviousAction()
|
|
364
|
+
if (submitC) {
|
|
365
|
+
const [resolved] = await resolveAll([resolveWizardAction(submitC, 'submit')], ctx)
|
|
366
|
+
if (resolved) meta['submitAction'] = resolved
|
|
367
|
+
}
|
|
368
|
+
if (nextC) {
|
|
369
|
+
const [resolved] = await resolveAll([resolveWizardAction(nextC, 'next')], ctx)
|
|
370
|
+
if (resolved) meta['nextAction'] = resolved
|
|
371
|
+
}
|
|
372
|
+
if (previousC) {
|
|
373
|
+
const [resolved] = await resolveAll([resolveWizardAction(previousC, 'previous')], ctx)
|
|
374
|
+
if (resolved) meta['previousAction'] = resolved
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
338
378
|
// `TextField.prefixAction(Action) / suffixAction(Action)` — resolve the
|
|
339
379
|
// bound Actions through `resolveAll` so visibility / disabled rules
|
|
340
380
|
// evaluate the same way as anywhere else. Hidden Actions are dropped
|
|
@@ -827,6 +867,23 @@ async function evalItemCan(
|
|
|
827
867
|
* Mirrors the `Field.buildConditionContext` shape so layout `visible(fn)`
|
|
828
868
|
* callbacks can destructure the same way as `Field.showWhen` callbacks.
|
|
829
869
|
*/
|
|
870
|
+
/**
|
|
871
|
+
* Resolve a `WizardActionCustomizer` against the framework default for
|
|
872
|
+
* the given slot. The default carries a stable `name` (`wizardSubmit /
|
|
873
|
+
* wizardNext / wizardPrevious`) and sensible chrome — the customizer
|
|
874
|
+
* may pass through verbatim, mutate, or replace entirely. Returned
|
|
875
|
+
* `Action` is then resolved through the standard walker so visibility
|
|
876
|
+
* and disabled rules evaluate the same way as any other Action.
|
|
877
|
+
*/
|
|
878
|
+
function resolveWizardAction(customizer: WizardActionCustomizer, slot: 'submit' | 'next' | 'previous'): Action {
|
|
879
|
+
const def = slot === 'submit'
|
|
880
|
+
? Action.make('wizardSubmit').label('Submit').color('primary')
|
|
881
|
+
: slot === 'next'
|
|
882
|
+
? Action.make('wizardNext').label('Next').color('primary')
|
|
883
|
+
: Action.make('wizardPrevious').label('Back').color('ghost')
|
|
884
|
+
return typeof customizer === 'function' ? customizer(def) : customizer
|
|
885
|
+
}
|
|
886
|
+
|
|
830
887
|
function buildLayoutContext(ctx: RenderContext): LayoutContext {
|
|
831
888
|
const out: LayoutContext = {}
|
|
832
889
|
if (ctx.record !== undefined) out.record = ctx.record
|
|
@@ -837,3 +894,24 @@ function buildLayoutContext(ctx: RenderContext): LayoutContext {
|
|
|
837
894
|
if (ctx.row !== undefined) out.row = ctx.row
|
|
838
895
|
return out
|
|
839
896
|
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Derive the render context that this element's children should see.
|
|
900
|
+
* Currently only handles the `inlineLabelDefault` cascade — `Form` /
|
|
901
|
+
* `Section` with `.inlineLabel(true|false)` push the value into the
|
|
902
|
+
* descendant context so every nested `Field.buildMeta` can read it.
|
|
903
|
+
* A nested container that re-sets the flag overrides the outer value
|
|
904
|
+
* for its subtree (the current ctx's value is replaced, not merged).
|
|
905
|
+
*
|
|
906
|
+
* Returns the same `ctx` reference when nothing needs to change so
|
|
907
|
+
* call sites that pass it down skip a fresh object allocation.
|
|
908
|
+
*/
|
|
909
|
+
function deriveChildContext(el: Element, ctx: RenderContext): RenderContext {
|
|
910
|
+
if (el instanceof Form || el instanceof Section) {
|
|
911
|
+
const v = (el as Form | Section).getInlineLabel?.()
|
|
912
|
+
if (v !== undefined && v !== ctx.inlineLabelDefault) {
|
|
913
|
+
return { ...ctx, inlineLabelDefault: v }
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return ctx
|
|
917
|
+
}
|
|
@@ -104,6 +104,29 @@ describe('writePersistedListQuery', () => {
|
|
|
104
104
|
assert.deepEqual(session.data[key], { orders_status: 'draft' })
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
+
it('skips groupKey (drill-in is page-state, not filter-state)', () => {
|
|
108
|
+
const session = makeSession()
|
|
109
|
+
const req = { session }
|
|
110
|
+
const key = listFiltersKey('/admin', 'posts')
|
|
111
|
+
writePersistedListQuery(req, key, {
|
|
112
|
+
status: 'draft',
|
|
113
|
+
groupKey: 'archive-2025',
|
|
114
|
+
})
|
|
115
|
+
assert.deepEqual(session.data[key], { status: 'draft' })
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('skips Tier-3 prefixed groupKey keys', () => {
|
|
119
|
+
const session = makeSession()
|
|
120
|
+
const req = { session }
|
|
121
|
+
const key = listFiltersKey('/admin', 'posts')
|
|
122
|
+
writePersistedListQuery(req, key, {
|
|
123
|
+
orders_status: 'draft',
|
|
124
|
+
orders_groupKey: 'old',
|
|
125
|
+
groupKey: 'older', // bare also dropped
|
|
126
|
+
})
|
|
127
|
+
assert.deepEqual(session.data[key], { orders_status: 'draft' })
|
|
128
|
+
})
|
|
129
|
+
|
|
107
130
|
it('preserves empty-string values (explicit-clear marker)', () => {
|
|
108
131
|
const session = makeSession()
|
|
109
132
|
const req = { session }
|
package/src/sessionFilters.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const PREFIX = 'pilotiq:filters:'
|
|
6
6
|
|
|
7
|
-
const EXCLUDED_KEYS = new Set(['page', 'tab'])
|
|
7
|
+
const EXCLUDED_KEYS = new Set(['page', 'tab', 'groupKey'])
|
|
8
8
|
|
|
9
9
|
// Heuristic also catches `<prefix>_page` from `Table.queryStringIdentifier`.
|
|
10
10
|
// A filter literally named `something_page` would be dropped too, but
|
|
@@ -14,6 +14,15 @@ function isPageKey(key: string): boolean {
|
|
|
14
14
|
return key.endsWith('_page')
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// Sibling heuristic for `<prefix>_groupKey`. Group drill-in is page-
|
|
18
|
+
// state (click-to-drill, × to clear), not filter-state — restoring it
|
|
19
|
+
// on a bare visit would land the user on the bucket they last drilled
|
|
20
|
+
// into instead of the banded list they probably expect.
|
|
21
|
+
function isGroupKeyKey(key: string): boolean {
|
|
22
|
+
if (key === 'groupKey') return true
|
|
23
|
+
return key.endsWith('_groupKey')
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
interface StorableSession {
|
|
18
27
|
get<T>(key: string, fallback?: T): T | undefined
|
|
19
28
|
put(key: string, value: unknown): void
|
|
@@ -73,6 +82,7 @@ export function writePersistedListQuery(
|
|
|
73
82
|
for (const [k, v] of Object.entries(query)) {
|
|
74
83
|
if (EXCLUDED_KEYS.has(k)) continue
|
|
75
84
|
if (isPageKey(k)) continue
|
|
85
|
+
if (isGroupKeyKey(k)) continue
|
|
76
86
|
if (typeof v !== 'string') continue
|
|
77
87
|
slice[k] = v
|
|
78
88
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* Stylesheet for `FileUploadField`'s image-cropping affordance.
|
|
2
|
+
*
|
|
3
|
+
* `react-image-crop` is a declared dep of `@pilotiq/pilotiq` and its
|
|
4
|
+
* CSS is required for the crop UI to render correctly. Consumers
|
|
5
|
+
* pull in the styles by importing this subpath from their app's CSS
|
|
6
|
+
* (Tailwind / global stylesheet):
|
|
7
|
+
*
|
|
8
|
+
* @import "@pilotiq/pilotiq/styles/file-upload.css";
|
|
9
|
+
*
|
|
10
|
+
* Importing through this subpath means consumers don't need to
|
|
11
|
+
* declare `react-image-crop` as a direct dep — the CSS @import below
|
|
12
|
+
* resolves through pilotiq's own node_modules. */
|
|
13
|
+
@import "react-image-crop/dist/ReactCrop.css";
|
package/src/vite.ts
CHANGED
|
@@ -157,7 +157,7 @@ export const route: RouteSync = (pageContext) => {
|
|
|
157
157
|
const parts = pageContext.urlPathname.split('/').filter(Boolean)
|
|
158
158
|
if (parts.length !== 1) return false
|
|
159
159
|
if (import.meta.env.SSR && !PilotiqRegistry.findByPath('/' + parts[0])) return false
|
|
160
|
-
return { routeParams: { basePath: parts[0] } }
|
|
160
|
+
return { routeParams: { basePath: parts[0]! } }
|
|
161
161
|
}
|
|
162
162
|
`)
|
|
163
163
|
writeIfChanged(path.join(outDir, 'dashboard', '+data.ts'), dataStub)
|
|
@@ -206,7 +206,7 @@ export const route: RouteSync = (pageContext) => {
|
|
|
206
206
|
// Don't match built-in slugs (theme editor, etc.)
|
|
207
207
|
if (parts[1 + off] === 'theme') return false
|
|
208
208
|
if (import.meta.env.SSR && !PilotiqRegistry.findByPath('/' + parts[0])) return false
|
|
209
|
-
return { routeParams: { basePath: parts[0]
|
|
209
|
+
return { routeParams: { basePath: parts[0]!, slug: parts[1 + off]! } }
|
|
210
210
|
}
|
|
211
211
|
`)
|
|
212
212
|
writeIfChanged(path.join(outDir, 'slug', '+data.ts'), dataStub)
|
|
@@ -232,7 +232,7 @@ export const route: RouteSync = (pageContext) => {
|
|
|
232
232
|
const off = clusterOffset(parts)
|
|
233
233
|
if (parts.length !== 3 + off || parts[2 + off] !== 'create') return false
|
|
234
234
|
if (import.meta.env.SSR && !PilotiqRegistry.findByPath('/' + parts[0])) return false
|
|
235
|
-
return { routeParams: { basePath: parts[0]
|
|
235
|
+
return { routeParams: { basePath: parts[0]!, slug: parts[1 + off]! } }
|
|
236
236
|
}
|
|
237
237
|
`)
|
|
238
238
|
writeIfChanged(path.join(outDir, 'resource-create', '+data.ts'), dataStub)
|
|
@@ -248,7 +248,7 @@ export const route: RouteSync = (pageContext) => {
|
|
|
248
248
|
const off = clusterOffset(parts)
|
|
249
249
|
if (parts.length !== 4 + off || parts[3 + off] !== 'edit') return false
|
|
250
250
|
if (import.meta.env.SSR && !PilotiqRegistry.findByPath('/' + parts[0])) return false
|
|
251
|
-
return { routeParams: { basePath: parts[0]
|
|
251
|
+
return { routeParams: { basePath: parts[0]!, slug: parts[1 + off]!, id: parts[2 + off]! } }
|
|
252
252
|
}
|
|
253
253
|
`)
|
|
254
254
|
writeIfChanged(path.join(outDir, 'resource-edit', '+data.ts'), dataStub)
|
|
@@ -282,7 +282,7 @@ export const route: RouteSync = (pageContext) => {
|
|
|
282
282
|
if (parts[2 + off] === 'create') return false
|
|
283
283
|
if (parts[1 + off] === 'theme') return false
|
|
284
284
|
if (import.meta.env.SSR && !PilotiqRegistry.findByPath('/' + parts[0])) return false
|
|
285
|
-
return { routeParams: { basePath: parts[0]
|
|
285
|
+
return { routeParams: { basePath: parts[0]!, slug: parts[1 + off]!, id: parts[2 + off]! } }
|
|
286
286
|
}
|
|
287
287
|
`)
|
|
288
288
|
writeIfChanged(path.join(outDir, 'resource-view', '+data.ts'), dataStub)
|
|
@@ -311,7 +311,7 @@ export const route: RouteSync = (pageContext) => {
|
|
|
311
311
|
if (parts.length !== 4 + off) return false
|
|
312
312
|
if (parts[3 + off] === 'edit') return false
|
|
313
313
|
if (import.meta.env.SSR && !PilotiqRegistry.findByPath('/' + parts[0])) return false
|
|
314
|
-
return { routeParams: { basePath: parts[0]
|
|
314
|
+
return { routeParams: { basePath: parts[0]!, slug: parts[1 + off]!, id: parts[2 + off]!, relationship: parts[3 + off]! } }
|
|
315
315
|
}
|
|
316
316
|
`)
|
|
317
317
|
writeIfChanged(path.join(outDir, 'relation-list', '+data.ts'), dataStub)
|
|
@@ -327,7 +327,7 @@ export const route: RouteSync = (pageContext) => {
|
|
|
327
327
|
const off = clusterOffset(parts)
|
|
328
328
|
if (parts.length !== 5 + off || parts[4 + off] !== 'create') return false
|
|
329
329
|
if (import.meta.env.SSR && !PilotiqRegistry.findByPath('/' + parts[0])) return false
|
|
330
|
-
return { routeParams: { basePath: parts[0]
|
|
330
|
+
return { routeParams: { basePath: parts[0]!, slug: parts[1 + off]!, id: parts[2 + off]!, relationship: parts[3 + off]! } }
|
|
331
331
|
}
|
|
332
332
|
`)
|
|
333
333
|
writeIfChanged(path.join(outDir, 'relation-create', '+data.ts'), dataStub)
|
|
@@ -344,7 +344,7 @@ export const route: RouteSync = (pageContext) => {
|
|
|
344
344
|
if (parts.length !== 5 + off) return false
|
|
345
345
|
if (parts[4 + off] === 'create') return false
|
|
346
346
|
if (import.meta.env.SSR && !PilotiqRegistry.findByPath('/' + parts[0])) return false
|
|
347
|
-
return { routeParams: { basePath: parts[0]
|
|
347
|
+
return { routeParams: { basePath: parts[0]!, slug: parts[1 + off]!, id: parts[2 + off]!, relationship: parts[3 + off]!, childId: parts[4 + off]! } }
|
|
348
348
|
}
|
|
349
349
|
`)
|
|
350
350
|
writeIfChanged(path.join(outDir, 'relation-view', '+data.ts'), dataStub)
|
|
@@ -360,7 +360,7 @@ export const route: RouteSync = (pageContext) => {
|
|
|
360
360
|
const off = clusterOffset(parts)
|
|
361
361
|
if (parts.length !== 6 + off || parts[5 + off] !== 'edit') return false
|
|
362
362
|
if (import.meta.env.SSR && !PilotiqRegistry.findByPath('/' + parts[0])) return false
|
|
363
|
-
return { routeParams: { basePath: parts[0]
|
|
363
|
+
return { routeParams: { basePath: parts[0]!, slug: parts[1 + off]!, id: parts[2 + off]!, relationship: parts[3 + off]!, childId: parts[4 + off]! } }
|
|
364
364
|
}
|
|
365
365
|
`)
|
|
366
366
|
writeIfChanged(path.join(outDir, 'relation-edit', '+data.ts'), dataStub)
|
|
@@ -488,7 +488,7 @@ export const route: RouteSync = (pageContext) => {
|
|
|
488
488
|
// Only match if themeEditor is enabled
|
|
489
489
|
if (!panel.getConfig().themeEditor) return false
|
|
490
490
|
}
|
|
491
|
-
return { routeParams: { basePath: parts[0] } }
|
|
491
|
+
return { routeParams: { basePath: parts[0]! } }
|
|
492
492
|
}
|
|
493
493
|
`)
|
|
494
494
|
writeIfChanged(path.join(outDir, 'theme', '+data.ts'), dataStub)
|
|
@@ -632,6 +632,7 @@ export function clusterOffset(parts: string[]): number {
|
|
|
632
632
|
lines.push('const _all: Record<string, unknown> = {}')
|
|
633
633
|
lines.push('const _clusters: Record<string, string[]> = {}')
|
|
634
634
|
lines.push('const _rightPanels: Record<string, unknown> = {}')
|
|
635
|
+
lines.push('const _layoutProviders: unknown[] = []')
|
|
635
636
|
lines.push('function _add(c: any) { if (typeof c === \'function\' && c.name) _all[c.name] = c }')
|
|
636
637
|
lines.push('function _walk(p: any) {')
|
|
637
638
|
lines.push(' const cfg = p?.getConfig?.()')
|
|
@@ -643,6 +644,11 @@ export function clusterOffset(parts: string[]): number {
|
|
|
643
644
|
lines.push(' if (_c && typeof _c.id === \'string\' && _c.render) _rightPanels[_c.id] = _c.render')
|
|
644
645
|
lines.push(' }')
|
|
645
646
|
lines.push(' }')
|
|
647
|
+
lines.push(' if (Array.isArray(cfg?.layoutProviders)) {')
|
|
648
|
+
lines.push(' for (const _C of cfg.layoutProviders) {')
|
|
649
|
+
lines.push(' if (typeof _C === \'function\') _layoutProviders.push(_C)')
|
|
650
|
+
lines.push(' }')
|
|
651
|
+
lines.push(' }')
|
|
646
652
|
lines.push(' if (cfg?.path && Array.isArray(cfg?.clusters)) {')
|
|
647
653
|
lines.push(' const slugs = cfg.clusters.map((C: any) => (typeof C?.getSlug === \'function\' ? C.getSlug() : \'\')).filter(Boolean)')
|
|
648
654
|
lines.push(' if (slugs.length > 0) _clusters[cfg.path] = slugs')
|
|
@@ -653,6 +659,7 @@ export function clusterOffset(parts: string[]): number {
|
|
|
653
659
|
lines.push('export const componentRegistry: Record<string, unknown> = _all')
|
|
654
660
|
lines.push('export const clusterSlugsByBasePath: Record<string, string[]> = _clusters')
|
|
655
661
|
lines.push('export const rightPanelRegistry: Record<string, unknown> = _rightPanels')
|
|
662
|
+
lines.push('export const layoutProviderRegistry: unknown[] = _layoutProviders')
|
|
656
663
|
lines.push('')
|
|
657
664
|
|
|
658
665
|
writeIfChanged(path.join(outDir, '_components.ts'), lines.join('\n'))
|
|
@@ -668,7 +675,7 @@ function writeLayoutWithManifest(pagesRoot: string): void {
|
|
|
668
675
|
import { usePageContext } from 'vike-react/usePageContext'
|
|
669
676
|
import { AppShell, ThemeProvider, generateThemeCSS, NavigateProvider } from '@pilotiq/pilotiq/react'
|
|
670
677
|
import { navigate as vikeNavigate } from 'vike/client/router'
|
|
671
|
-
import { componentRegistry, rightPanelRegistry } from './_components.js'
|
|
678
|
+
import { componentRegistry, rightPanelRegistry, layoutProviderRegistry } from './_components.js'
|
|
672
679
|
import type { ReactNode } from 'react'
|
|
673
680
|
|
|
674
681
|
// Wrap vike's async navigate so the NavigateProvider's fire-and-forget
|
|
@@ -691,7 +698,7 @@ export default function PilotiqLayout({ children }: { children: ReactNode }) {
|
|
|
691
698
|
<NavigateProvider navigate={navigate}>
|
|
692
699
|
<ThemeProvider theme={panel.theme}>
|
|
693
700
|
{themeCss && <style dangerouslySetInnerHTML={{ __html: themeCss }} />}
|
|
694
|
-
<AppShell panel={panel} basePath={basePath} layout={layout} notifications={notifications} currentPath={currentPath} componentRegistry={componentRegistry as any} rightPanelRegistry={rightPanelRegistry as any}>
|
|
701
|
+
<AppShell panel={panel} basePath={basePath} layout={layout} notifications={notifications} currentPath={currentPath} componentRegistry={componentRegistry as any} rightPanelRegistry={rightPanelRegistry as any} layoutProviderRegistry={layoutProviderRegistry as any}>
|
|
695
702
|
{children}
|
|
696
703
|
</AppShell>
|
|
697
704
|
</ThemeProvider>
|