@pilotiq/pilotiq 0.7.2 → 0.8.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 +2 -2
- package/CHANGELOG.md +208 -0
- package/CLAUDE.md +59 -3
- package/dist/Pilotiq.d.ts +83 -0
- package/dist/Pilotiq.d.ts.map +1 -1
- package/dist/Pilotiq.js +39 -0
- package/dist/Pilotiq.js.map +1 -1
- package/dist/actions/Action.d.ts +27 -99
- package/dist/actions/Action.d.ts.map +1 -1
- package/dist/actions/Action.js +52 -754
- package/dist/actions/Action.js.map +1 -1
- package/dist/actions/bulkFactories.d.ts +46 -0
- package/dist/actions/bulkFactories.d.ts.map +1 -0
- package/dist/actions/bulkFactories.js +144 -0
- package/dist/actions/bulkFactories.js.map +1 -0
- package/dist/actions/crudFactories.d.ts +94 -0
- package/dist/actions/crudFactories.d.ts.map +1 -0
- package/dist/actions/crudFactories.js +209 -0
- package/dist/actions/crudFactories.js.map +1 -0
- package/dist/actions/factoryHelpers.d.ts +108 -0
- package/dist/actions/factoryHelpers.d.ts.map +1 -0
- package/dist/actions/factoryHelpers.js +138 -0
- package/dist/actions/factoryHelpers.js.map +1 -0
- package/dist/actions/m2mFactories.d.ts +47 -0
- package/dist/actions/m2mFactories.d.ts.map +1 -0
- package/dist/actions/m2mFactories.js +173 -0
- package/dist/actions/m2mFactories.js.map +1 -0
- package/dist/actions/relationFactories.d.ts +93 -0
- package/dist/actions/relationFactories.d.ts.map +1 -0
- package/dist/actions/relationFactories.js +321 -0
- package/dist/actions/relationFactories.js.map +1 -0
- package/dist/elements/dispatchForm.js +1 -1
- package/dist/elements/dispatchForm.js.map +1 -1
- package/dist/elements/dispatchTable.js +1 -1
- package/dist/elements/dispatchTable.js.map +1 -1
- package/dist/fields/Field.d.ts +31 -0
- package/dist/fields/Field.d.ts.map +1 -1
- package/dist/fields/Field.js +25 -0
- package/dist/fields/Field.js.map +1 -1
- package/dist/pageData/breadcrumbs.d.ts +42 -0
- package/dist/pageData/breadcrumbs.d.ts.map +1 -0
- package/dist/pageData/breadcrumbs.js +172 -0
- package/dist/pageData/breadcrumbs.js.map +1 -0
- package/dist/pageData/forms.d.ts +137 -0
- package/dist/pageData/forms.d.ts.map +1 -0
- package/dist/pageData/forms.js +427 -0
- package/dist/pageData/forms.js.map +1 -0
- package/dist/pageData/helpers.d.ts +239 -0
- package/dist/pageData/helpers.d.ts.map +1 -0
- package/dist/pageData/helpers.js +703 -0
- package/dist/pageData/helpers.js.map +1 -0
- package/dist/pageData/misc.d.ts +76 -0
- package/dist/pageData/misc.d.ts.map +1 -0
- package/dist/pageData/misc.js +263 -0
- package/dist/pageData/misc.js.map +1 -0
- package/dist/pageData/navigation.d.ts +292 -0
- package/dist/pageData/navigation.d.ts.map +1 -0
- package/dist/pageData/navigation.js +591 -0
- package/dist/pageData/navigation.js.map +1 -0
- package/dist/pageData/relationPages.d.ts +172 -0
- package/dist/pageData/relationPages.d.ts.map +1 -0
- package/dist/pageData/relationPages.js +867 -0
- package/dist/pageData/relationPages.js.map +1 -0
- package/dist/pageData/relationTabs.d.ts +65 -0
- package/dist/pageData/relationTabs.d.ts.map +1 -0
- package/dist/pageData/relationTabs.js +258 -0
- package/dist/pageData/relationTabs.js.map +1 -0
- package/dist/pageData/resourcePages.d.ts +48 -0
- package/dist/pageData/resourcePages.d.ts.map +1 -0
- package/dist/pageData/resourcePages.js +504 -0
- package/dist/pageData/resourcePages.js.map +1 -0
- package/dist/pageData.d.ts +12 -792
- package/dist/pageData.d.ts.map +1 -1
- package/dist/pageData.js +24 -3797
- package/dist/pageData.js.map +1 -1
- package/dist/react/AppShell.d.ts +8 -0
- package/dist/react/AppShell.d.ts.map +1 -1
- package/dist/react/AppShell.js +11 -1
- package/dist/react/AppShell.js.map +1 -1
- package/dist/react/CollabExtensionFactoryRegistry.d.ts +47 -0
- package/dist/react/CollabExtensionFactoryRegistry.d.ts.map +1 -0
- package/dist/react/CollabExtensionFactoryRegistry.js +14 -0
- package/dist/react/CollabExtensionFactoryRegistry.js.map +1 -0
- package/dist/react/CollabRoomContext.d.ts +37 -0
- package/dist/react/CollabRoomContext.d.ts.map +1 -0
- package/dist/react/CollabRoomContext.js +12 -0
- package/dist/react/CollabRoomContext.js.map +1 -0
- package/dist/react/FormCollabBindingRegistry.d.ts +62 -0
- package/dist/react/FormCollabBindingRegistry.d.ts.map +1 -0
- package/dist/react/FormCollabBindingRegistry.js +14 -0
- package/dist/react/FormCollabBindingRegistry.js.map +1 -0
- package/dist/react/FormStateContext.d.ts.map +1 -1
- package/dist/react/FormStateContext.js +87 -0
- package/dist/react/FormStateContext.js.map +1 -1
- package/dist/react/RecordWrapperGate.d.ts +25 -0
- package/dist/react/RecordWrapperGate.d.ts.map +1 -0
- package/dist/react/RecordWrapperGate.js +30 -0
- package/dist/react/RecordWrapperGate.js.map +1 -0
- package/dist/react/RecordWrapperRegistry.d.ts +31 -0
- package/dist/react/RecordWrapperRegistry.d.ts.map +1 -0
- package/dist/react/RecordWrapperRegistry.js +15 -0
- package/dist/react/RecordWrapperRegistry.js.map +1 -0
- package/dist/react/SchemaRenderer.d.ts +17 -23
- package/dist/react/SchemaRenderer.d.ts.map +1 -1
- package/dist/react/SchemaRenderer.js +71 -3647
- package/dist/react/SchemaRenderer.js.map +1 -1
- package/dist/react/component-slots.d.ts +103 -0
- package/dist/react/component-slots.d.ts.map +1 -0
- package/dist/react/component-slots.js +18 -0
- package/dist/react/component-slots.js.map +1 -0
- package/dist/react/fields/BuilderInput.d.ts.map +1 -1
- package/dist/react/fields/BuilderInput.js +21 -117
- package/dist/react/fields/BuilderInput.js.map +1 -1
- package/dist/react/fields/MarkdownInput.d.ts.map +1 -1
- package/dist/react/fields/MarkdownInput.js +1 -3
- package/dist/react/fields/MarkdownInput.js.map +1 -1
- package/dist/react/fields/RepeaterInput.d.ts.map +1 -1
- package/dist/react/fields/RepeaterInput.js +22 -127
- package/dist/react/fields/RepeaterInput.js.map +1 -1
- package/dist/react/fields/rowState.d.ts +40 -0
- package/dist/react/fields/rowState.d.ts.map +1 -0
- package/dist/react/fields/rowState.js +60 -0
- package/dist/react/fields/rowState.js.map +1 -0
- package/dist/react/fields/useRowReorderDnd.d.ts +28 -0
- package/dist/react/fields/useRowReorderDnd.d.ts.map +1 -0
- package/dist/react/fields/useRowReorderDnd.js +51 -0
- package/dist/react/fields/useRowReorderDnd.js.map +1 -0
- package/dist/react/index.d.ts +9 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +8 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/layouts/SidebarLayout.d.ts +1 -1
- package/dist/react/layouts/SidebarLayout.d.ts.map +1 -1
- package/dist/react/layouts/SidebarLayout.js +10 -2
- package/dist/react/layouts/SidebarLayout.js.map +1 -1
- package/dist/react/layouts/TopbarLayout.d.ts +1 -1
- package/dist/react/layouts/TopbarLayout.d.ts.map +1 -1
- package/dist/react/layouts/TopbarLayout.js +19 -11
- package/dist/react/layouts/TopbarLayout.js.map +1 -1
- package/dist/react/parseRecordEditUrl.d.ts +29 -0
- package/dist/react/parseRecordEditUrl.d.ts.map +1 -0
- package/dist/react/parseRecordEditUrl.js +25 -0
- package/dist/react/parseRecordEditUrl.js.map +1 -0
- package/dist/react/persistedState.d.ts +19 -0
- package/dist/react/persistedState.d.ts.map +1 -0
- package/dist/react/persistedState.js +51 -0
- package/dist/react/persistedState.js.map +1 -0
- package/dist/react/schemaRenderer/AlertRenderer.d.ts +12 -0
- package/dist/react/schemaRenderer/AlertRenderer.d.ts.map +1 -0
- package/dist/react/schemaRenderer/AlertRenderer.js +61 -0
- package/dist/react/schemaRenderer/AlertRenderer.js.map +1 -0
- package/dist/react/schemaRenderer/EntryRenderer.d.ts +13 -0
- package/dist/react/schemaRenderer/EntryRenderer.d.ts.map +1 -0
- package/dist/react/schemaRenderer/EntryRenderer.js +277 -0
- package/dist/react/schemaRenderer/EntryRenderer.js.map +1 -0
- package/dist/react/schemaRenderer/SectionRenderer.d.ts +16 -0
- package/dist/react/schemaRenderer/SectionRenderer.d.ts.map +1 -0
- package/dist/react/schemaRenderer/SectionRenderer.js +62 -0
- package/dist/react/schemaRenderer/SectionRenderer.js.map +1 -0
- package/dist/react/schemaRenderer/SimpleElements.d.ts +25 -0
- package/dist/react/schemaRenderer/SimpleElements.d.ts.map +1 -0
- package/dist/react/schemaRenderer/SimpleElements.js +147 -0
- package/dist/react/schemaRenderer/SimpleElements.js.map +1 -0
- package/dist/react/schemaRenderer/TabsRenderer.d.ts +17 -0
- package/dist/react/schemaRenderer/TabsRenderer.d.ts.map +1 -0
- package/dist/react/schemaRenderer/TabsRenderer.js +31 -0
- package/dist/react/schemaRenderer/TabsRenderer.js.map +1 -0
- package/dist/react/schemaRenderer/WizardRenderer.d.ts +34 -0
- package/dist/react/schemaRenderer/WizardRenderer.d.ts.map +1 -0
- package/dist/react/schemaRenderer/WizardRenderer.js +208 -0
- package/dist/react/schemaRenderer/WizardRenderer.js.map +1 -0
- package/dist/react/schemaRenderer/action/ActionGroupTrigger.d.ts +21 -0
- package/dist/react/schemaRenderer/action/ActionGroupTrigger.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/ActionGroupTrigger.js +82 -0
- package/dist/react/schemaRenderer/action/ActionGroupTrigger.js.map +1 -0
- package/dist/react/schemaRenderer/action/ActionModalDialog.d.ts +30 -0
- package/dist/react/schemaRenderer/action/ActionModalDialog.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/ActionModalDialog.js +182 -0
- package/dist/react/schemaRenderer/action/ActionModalDialog.js.map +1 -0
- package/dist/react/schemaRenderer/action/ConfirmActionDialog.d.ts +17 -0
- package/dist/react/schemaRenderer/action/ConfirmActionDialog.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/ConfirmActionDialog.js +19 -0
- package/dist/react/schemaRenderer/action/ConfirmActionDialog.js.map +1 -0
- package/dist/react/schemaRenderer/action/HandlerActionButton.d.ts +16 -0
- package/dist/react/schemaRenderer/action/HandlerActionButton.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/HandlerActionButton.js +16 -0
- package/dist/react/schemaRenderer/action/HandlerActionButton.js.map +1 -0
- package/dist/react/schemaRenderer/action/MethodActionButton.d.ts +22 -0
- package/dist/react/schemaRenderer/action/MethodActionButton.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/MethodActionButton.js +26 -0
- package/dist/react/schemaRenderer/action/MethodActionButton.js.map +1 -0
- package/dist/react/schemaRenderer/action/buttons.d.ts +18 -0
- package/dist/react/schemaRenderer/action/buttons.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/buttons.js +74 -0
- package/dist/react/schemaRenderer/action/buttons.js.map +1 -0
- package/dist/react/schemaRenderer/action/helpers.d.ts +26 -0
- package/dist/react/schemaRenderer/action/helpers.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/helpers.js +126 -0
- package/dist/react/schemaRenderer/action/helpers.js.map +1 -0
- package/dist/react/schemaRenderer/action/renderAction.d.ts +21 -0
- package/dist/react/schemaRenderer/action/renderAction.d.ts.map +1 -0
- package/dist/react/schemaRenderer/action/renderAction.js +102 -0
- package/dist/react/schemaRenderer/action/renderAction.js.map +1 -0
- package/dist/react/schemaRenderer/columnFormat.d.ts +10 -0
- package/dist/react/schemaRenderer/columnFormat.d.ts.map +1 -0
- package/dist/react/schemaRenderer/columnFormat.js +76 -0
- package/dist/react/schemaRenderer/columnFormat.js.map +1 -0
- package/dist/react/schemaRenderer/constants.d.ts +8 -0
- package/dist/react/schemaRenderer/constants.d.ts.map +1 -0
- package/dist/react/schemaRenderer/constants.js +45 -0
- package/dist/react/schemaRenderer/constants.js.map +1 -0
- package/dist/react/schemaRenderer/form/FormRenderer.d.ts +29 -0
- package/dist/react/schemaRenderer/form/FormRenderer.d.ts.map +1 -0
- package/dist/react/schemaRenderer/form/FormRenderer.js +163 -0
- package/dist/react/schemaRenderer/form/FormRenderer.js.map +1 -0
- package/dist/react/schemaRenderer/form/renderField.d.ts +6 -0
- package/dist/react/schemaRenderer/form/renderField.d.ts.map +1 -0
- package/dist/react/schemaRenderer/form/renderField.js +239 -0
- package/dist/react/schemaRenderer/form/renderField.js.map +1 -0
- package/dist/react/schemaRenderer/helpers.d.ts +32 -0
- package/dist/react/schemaRenderer/helpers.d.ts.map +1 -0
- package/dist/react/schemaRenderer/helpers.js +52 -0
- package/dist/react/schemaRenderer/helpers.js.map +1 -0
- package/dist/react/schemaRenderer/table/CardsLayoutBody.d.ts +60 -0
- package/dist/react/schemaRenderer/table/CardsLayoutBody.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/CardsLayoutBody.js +189 -0
- package/dist/react/schemaRenderer/table/CardsLayoutBody.js.map +1 -0
- package/dist/react/schemaRenderer/table/TableRenderer.d.ts +29 -0
- package/dist/react/schemaRenderer/table/TableRenderer.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/TableRenderer.js +85 -0
- package/dist/react/schemaRenderer/table/TableRenderer.js.map +1 -0
- package/dist/react/schemaRenderer/table/TableRendererBody.d.ts +18 -0
- package/dist/react/schemaRenderer/table/TableRendererBody.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/TableRendererBody.js +555 -0
- package/dist/react/schemaRenderer/table/TableRendererBody.js.map +1 -0
- package/dist/react/schemaRenderer/table/filters.d.ts +263 -0
- package/dist/react/schemaRenderer/table/filters.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/filters.js +497 -0
- package/dist/react/schemaRenderer/table/filters.js.map +1 -0
- package/dist/react/schemaRenderer/table/formatCell.d.ts +11 -0
- package/dist/react/schemaRenderer/table/formatCell.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/formatCell.js +172 -0
- package/dist/react/schemaRenderer/table/formatCell.js.map +1 -0
- package/dist/react/schemaRenderer/table/links.d.ts +42 -0
- package/dist/react/schemaRenderer/table/links.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/links.js +55 -0
- package/dist/react/schemaRenderer/table/links.js.map +1 -0
- package/dist/react/schemaRenderer/table/renderRowActions.d.ts +13 -0
- package/dist/react/schemaRenderer/table/renderRowActions.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/renderRowActions.js +25 -0
- package/dist/react/schemaRenderer/table/renderRowActions.js.map +1 -0
- package/dist/react/schemaRenderer/table/url.d.ts +41 -0
- package/dist/react/schemaRenderer/table/url.d.ts.map +1 -0
- package/dist/react/schemaRenderer/table/url.js +114 -0
- package/dist/react/schemaRenderer/table/url.js.map +1 -0
- package/dist/routes/globals.d.ts +13 -0
- package/dist/routes/globals.d.ts.map +1 -0
- package/dist/routes/globals.js +131 -0
- package/dist/routes/globals.js.map +1 -0
- package/dist/routes/helpers.d.ts +217 -0
- package/dist/routes/helpers.d.ts.map +1 -0
- package/dist/routes/helpers.js +498 -0
- package/dist/routes/helpers.js.map +1 -0
- package/dist/routes/pages.d.ts +15 -0
- package/dist/routes/pages.d.ts.map +1 -0
- package/dist/routes/pages.js +145 -0
- package/dist/routes/pages.js.map +1 -0
- package/dist/routes/panel.d.ts +19 -0
- package/dist/routes/panel.d.ts.map +1 -0
- package/dist/routes/panel.js +191 -0
- package/dist/routes/panel.js.map +1 -0
- package/dist/routes/relations.d.ts +21 -0
- package/dist/routes/relations.d.ts.map +1 -0
- package/dist/routes/relations.js +1239 -0
- package/dist/routes/relations.js.map +1 -0
- package/dist/routes/resources.d.ts +28 -0
- package/dist/routes/resources.d.ts.map +1 -0
- package/dist/routes/resources.js +741 -0
- package/dist/routes/resources.js.map +1 -0
- package/dist/routes/theme.d.ts +12 -0
- package/dist/routes/theme.d.ts.map +1 -0
- package/dist/routes/theme.js +82 -0
- package/dist/routes/theme.js.map +1 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +64 -3078
- package/dist/routes.js.map +1 -1
- package/dist/vite.d.ts +1 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +26 -5
- package/dist/vite.js.map +1 -1
- package/package.json +2 -1
- package/src/Pilotiq.ts +95 -0
- package/src/actions/Action.ts +79 -723
- package/src/actions/bulkFactories.ts +168 -0
- package/src/actions/crudFactories.ts +220 -0
- package/src/actions/factoryHelpers.ts +177 -0
- package/src/actions/m2mFactories.ts +193 -0
- package/src/actions/relationFactories.ts +372 -0
- package/src/elements/dispatchForm.ts +1 -1
- package/src/elements/dispatchTable.ts +1 -1
- package/src/fields/Field.ts +39 -0
- package/src/pageData/breadcrumbs.ts +288 -0
- package/src/pageData/forms.ts +578 -0
- package/src/pageData/helpers.ts +764 -0
- package/src/pageData/misc.ts +347 -0
- package/src/pageData/navigation.ts +779 -0
- package/src/pageData/relationPages.ts +1246 -0
- package/src/pageData/relationTabs.ts +286 -0
- package/src/pageData/resourcePages.ts +593 -0
- package/src/pageData.ts +122 -4731
- package/src/react/AppShell.tsx +27 -1
- package/src/react/CollabExtensionFactoryRegistry.ts +55 -0
- package/src/react/CollabRoomContext.ts +42 -0
- package/src/react/FormCollabBindingRegistry.ts +72 -0
- package/src/react/FormStateContext.tsx +91 -0
- package/src/react/RecordWrapperGate.tsx +40 -0
- package/src/react/RecordWrapperRegistry.ts +39 -0
- package/src/react/SchemaRenderer.tsx +230 -6479
- package/src/react/component-slots.test.ts +103 -0
- package/src/react/component-slots.ts +116 -0
- package/src/react/fields/BuilderInput.tsx +29 -117
- package/src/react/fields/MarkdownInput.tsx +0 -1
- package/src/react/fields/RepeaterInput.tsx +29 -130
- package/src/react/fields/rowState.ts +106 -0
- package/src/react/fields/useRowReorderDnd.ts +78 -0
- package/src/react/index.ts +38 -0
- package/src/react/layouts/SidebarLayout.tsx +39 -28
- package/src/react/layouts/TopbarLayout.tsx +70 -57
- package/src/react/parseRecordEditUrl.test.ts +75 -0
- package/src/react/parseRecordEditUrl.ts +55 -0
- package/src/react/persistedState.ts +40 -0
- package/src/react/schemaRenderer/AlertRenderer.tsx +112 -0
- package/src/react/schemaRenderer/EntryRenderer.tsx +501 -0
- package/src/react/schemaRenderer/SectionRenderer.tsx +120 -0
- package/src/react/schemaRenderer/SimpleElements.tsx +306 -0
- package/src/react/schemaRenderer/TabsRenderer.tsx +62 -0
- package/src/react/schemaRenderer/WizardRenderer.tsx +338 -0
- package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +177 -0
- package/src/react/schemaRenderer/action/ActionModalDialog.tsx +273 -0
- package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +61 -0
- package/src/react/schemaRenderer/action/HandlerActionButton.tsx +43 -0
- package/src/react/schemaRenderer/action/MethodActionButton.tsx +64 -0
- package/src/react/schemaRenderer/action/buttons.tsx +99 -0
- package/src/react/schemaRenderer/action/helpers.ts +140 -0
- package/src/react/schemaRenderer/action/renderAction.tsx +245 -0
- package/src/react/schemaRenderer/columnFormat.ts +65 -0
- package/src/react/schemaRenderer/constants.ts +50 -0
- package/src/react/schemaRenderer/form/FormRenderer.tsx +245 -0
- package/src/react/schemaRenderer/form/renderField.tsx +511 -0
- package/src/react/schemaRenderer/helpers.tsx +81 -0
- package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +308 -0
- package/src/react/schemaRenderer/table/TableRenderer.tsx +123 -0
- package/src/react/schemaRenderer/table/TableRendererBody.tsx +974 -0
- package/src/react/schemaRenderer/table/filters.tsx +1233 -0
- package/src/react/schemaRenderer/table/formatCell.tsx +264 -0
- package/src/react/schemaRenderer/table/links.tsx +112 -0
- package/src/react/schemaRenderer/table/renderRowActions.tsx +52 -0
- package/src/react/schemaRenderer/table/url.tsx +143 -0
- package/src/routes/globals.ts +154 -0
- package/src/routes/helpers.ts +668 -0
- package/src/routes/pages.ts +173 -0
- package/src/routes/panel.ts +204 -0
- package/src/routes/relations.ts +1219 -0
- package/src/routes/resources.ts +786 -0
- package/src/routes/theme.ts +109 -0
- package/src/routes.test.ts +1 -1
- package/src/routes.ts +64 -3176
- package/src/schema/TableWidget.test.ts +2 -2
- package/src/theme/migrate.test.ts +178 -0
- package/src/vite.test.ts +184 -0
- package/src/vite.ts +26 -4
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared row-state helpers consumed by both `RepeaterInput` and
|
|
3
|
+
* `BuilderInput`. The two fields keep parallel storage namespaces
|
|
4
|
+
* (`pilotiq.repeater.…` vs `pilotiq.builder.…`) so users with both on
|
|
5
|
+
* the same page can collapse them independently — the namespace is the
|
|
6
|
+
* only thing that varies between the two callers.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
readStoredString, removeStoredString, writeStoredString,
|
|
11
|
+
} from '../persistedState.js'
|
|
12
|
+
|
|
13
|
+
let _rowSeqFallback = 0
|
|
14
|
+
|
|
15
|
+
export function generateRowId(): string {
|
|
16
|
+
type CryptoLike = { randomUUID?: () => string }
|
|
17
|
+
const c = (globalThis as { crypto?: CryptoLike }).crypto
|
|
18
|
+
if (c?.randomUUID) return c.randomUUID()
|
|
19
|
+
return `row-${Date.now()}-${++_rowSeqFallback}`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type RowStateNamespace = 'repeater' | 'builder'
|
|
23
|
+
|
|
24
|
+
export interface CollapsedStorage {
|
|
25
|
+
key: (formId: string, name: string, rowId: string) => string
|
|
26
|
+
read: (formId: string, name: string, rowId: string, defaultValue: boolean) => boolean
|
|
27
|
+
write: (formId: string, name: string, rowId: string, value: boolean) => void
|
|
28
|
+
remove: (formId: string, name: string, rowId: string) => void
|
|
29
|
+
seed: (
|
|
30
|
+
rows: { id: string }[],
|
|
31
|
+
formId: string,
|
|
32
|
+
name: string,
|
|
33
|
+
defaultValue: boolean,
|
|
34
|
+
collapsible: boolean,
|
|
35
|
+
) => Record<string, boolean>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build a namespaced per-row collapse-state store. Uses `'true'` /
|
|
40
|
+
* `'false'` encoding (predates the `'1'` / `'0'` flag helper — kept for
|
|
41
|
+
* back-compat with already-persisted state).
|
|
42
|
+
*/
|
|
43
|
+
export function makeCollapsedStorage(namespace: RowStateNamespace): CollapsedStorage {
|
|
44
|
+
const key = (formId: string, name: string, rowId: string): string =>
|
|
45
|
+
`pilotiq.${namespace}.${formId}.${name}.${rowId}`
|
|
46
|
+
|
|
47
|
+
const read = (formId: string, name: string, rowId: string, defaultValue: boolean): boolean => {
|
|
48
|
+
const raw = readStoredString(key(formId, name, rowId))
|
|
49
|
+
if (raw === null) return defaultValue
|
|
50
|
+
return raw === 'true'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const write = (formId: string, name: string, rowId: string, value: boolean): void => {
|
|
54
|
+
writeStoredString(key(formId, name, rowId), String(value))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const remove = (formId: string, name: string, rowId: string): void => {
|
|
58
|
+
removeStoredString(key(formId, name, rowId))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const seed = (
|
|
62
|
+
rows: { id: string }[],
|
|
63
|
+
formId: string,
|
|
64
|
+
name: string,
|
|
65
|
+
defaultValue: boolean,
|
|
66
|
+
collapsible: boolean,
|
|
67
|
+
): Record<string, boolean> => {
|
|
68
|
+
if (!collapsible) return {}
|
|
69
|
+
const out: Record<string, boolean> = {}
|
|
70
|
+
for (const row of rows) out[row.id] = read(formId, name, row.id, defaultValue)
|
|
71
|
+
return out
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { key, read, write, remove, seed }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface AccordionStorage {
|
|
78
|
+
key: (formId: string, name: string) => string
|
|
79
|
+
/**
|
|
80
|
+
* `undefined` = no value stored (caller falls back to default-open
|
|
81
|
+
* heuristic). Empty string = user explicitly closed every row (caller
|
|
82
|
+
* maps to `null` openId). Any other string = the open row id.
|
|
83
|
+
*/
|
|
84
|
+
read: (formId: string, name: string) => string | undefined
|
|
85
|
+
write: (formId: string, name: string, openId: string | null) => void
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Build a namespaced accordion-open-row store. Always one slot per
|
|
90
|
+
* (formId, name) pair regardless of row count.
|
|
91
|
+
*/
|
|
92
|
+
export function makeAccordionStorage(namespace: RowStateNamespace): AccordionStorage {
|
|
93
|
+
const key = (formId: string, name: string): string =>
|
|
94
|
+
`pilotiq.${namespace}.${formId}.${name}.accordion`
|
|
95
|
+
|
|
96
|
+
const read = (formId: string, name: string): string | undefined => {
|
|
97
|
+
const raw = readStoredString(key(formId, name))
|
|
98
|
+
return raw === null ? undefined : raw
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const write = (formId: string, name: string, openId: string | null): void => {
|
|
102
|
+
writeStoredString(key(formId, name), openId ?? '')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { key, read, write }
|
|
106
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared HTML5 drag-and-drop wiring for any list of rows that the user
|
|
5
|
+
* can reorder. Consumed by `TableRendererBody` (POSTs the new order
|
|
6
|
+
* to the server with rollback), `RepeaterInput`, and `BuilderInput`
|
|
7
|
+
* (both mutate local row state in place).
|
|
8
|
+
*
|
|
9
|
+
* Generic on `HTMLElement` so the same handlers wire onto a `<div>`
|
|
10
|
+
* row (card / grid layouts) AND a `<tr>` row (table layout) — the
|
|
11
|
+
* consumer cast happens at the JSX boundary.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface RowReorderDnd {
|
|
15
|
+
dragId: string | null
|
|
16
|
+
/** The boundary slot the cursor is over (0..rows.length); null when no drag is active. */
|
|
17
|
+
dropAt: number | null
|
|
18
|
+
onDragStart: (id: string) => (e: React.DragEvent<HTMLElement>) => void
|
|
19
|
+
onDragOver: (idx: number) => (e: React.DragEvent<HTMLElement>) => void
|
|
20
|
+
onDrop: (e: React.DragEvent<HTMLElement>) => void
|
|
21
|
+
onDragEnd: () => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UseRowReorderDndOptions {
|
|
25
|
+
/** When false, every handler short-circuits without firing `onDrop`. */
|
|
26
|
+
enabled: boolean
|
|
27
|
+
/** Fires once per successful drop, after the hook has cleared its drag state. */
|
|
28
|
+
onDrop: (fromId: string, dropAt: number) => void | Promise<void>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useRowReorderDnd({
|
|
32
|
+
enabled,
|
|
33
|
+
onDrop,
|
|
34
|
+
}: UseRowReorderDndOptions): RowReorderDnd {
|
|
35
|
+
const [dragId, setDragId] = useState<string | null>(null)
|
|
36
|
+
const [dropAt, setDropAt] = useState<number | null>(null)
|
|
37
|
+
|
|
38
|
+
const handleDragStart = (id: string) => (e: React.DragEvent<HTMLElement>): void => {
|
|
39
|
+
if (!enabled) return
|
|
40
|
+
setDragId(id)
|
|
41
|
+
// dataTransfer needs *something* to register the drag in Firefox.
|
|
42
|
+
e.dataTransfer.effectAllowed = 'move'
|
|
43
|
+
try { e.dataTransfer.setData('text/plain', id) } catch { /* IE quirk; ignore */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const handleDragOver = (idx: number) => (e: React.DragEvent<HTMLElement>): void => {
|
|
47
|
+
if (!enabled || dragId === null) return
|
|
48
|
+
e.preventDefault()
|
|
49
|
+
e.dataTransfer.dropEffect = 'move'
|
|
50
|
+
// Drop above this row when cursor is in its top half, below when in its bottom half.
|
|
51
|
+
const rect = e.currentTarget.getBoundingClientRect()
|
|
52
|
+
const aboveHalf = e.clientY < rect.top + rect.height / 2
|
|
53
|
+
setDropAt(aboveHalf ? idx : idx + 1)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const handleDrop = (e: React.DragEvent<HTMLElement>): void => {
|
|
57
|
+
if (!enabled || dragId === null || dropAt === null) {
|
|
58
|
+
setDragId(null); setDropAt(null); return
|
|
59
|
+
}
|
|
60
|
+
e.preventDefault()
|
|
61
|
+
const fromId = dragId
|
|
62
|
+
const at = dropAt
|
|
63
|
+
setDragId(null); setDropAt(null)
|
|
64
|
+
void onDrop(fromId, at)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const handleDragEnd = (): void => {
|
|
68
|
+
setDragId(null); setDropAt(null)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
dragId, dropAt,
|
|
73
|
+
onDragStart: handleDragStart,
|
|
74
|
+
onDragOver: handleDragOver,
|
|
75
|
+
onDrop: handleDrop,
|
|
76
|
+
onDragEnd: handleDragEnd,
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/react/index.ts
CHANGED
|
@@ -32,6 +32,34 @@ export {
|
|
|
32
32
|
getPendingSuggestionApplier,
|
|
33
33
|
type PendingSuggestionApplier,
|
|
34
34
|
} from './PendingSuggestionApplierRegistry.js'
|
|
35
|
+
export {
|
|
36
|
+
CollabRoomContext,
|
|
37
|
+
useCollabRoom,
|
|
38
|
+
type CollabRoom,
|
|
39
|
+
} from './CollabRoomContext.js'
|
|
40
|
+
export {
|
|
41
|
+
registerCollabExtensions,
|
|
42
|
+
getCollabExtensions,
|
|
43
|
+
type CollabExtensionFactory,
|
|
44
|
+
type CollabExtensionFactoryArgs,
|
|
45
|
+
} from './CollabExtensionFactoryRegistry.js'
|
|
46
|
+
export {
|
|
47
|
+
registerFormCollabBinding,
|
|
48
|
+
getFormCollabBinding,
|
|
49
|
+
type FormCollabBinding,
|
|
50
|
+
type FormCollabBindingFactory,
|
|
51
|
+
type FormCollabBindingFactoryArgs,
|
|
52
|
+
} from './FormCollabBindingRegistry.js'
|
|
53
|
+
export {
|
|
54
|
+
registerRecordWrapper,
|
|
55
|
+
getRecordWrapper,
|
|
56
|
+
type RecordWrapperProps,
|
|
57
|
+
} from './RecordWrapperRegistry.js'
|
|
58
|
+
export {
|
|
59
|
+
RecordWrapperGate,
|
|
60
|
+
type RecordWrapperGateProps,
|
|
61
|
+
} from './RecordWrapperGate.js'
|
|
62
|
+
export { parseRecordEditUrl, type RecordEditIdentity } from './parseRecordEditUrl.js'
|
|
35
63
|
export {
|
|
36
64
|
registerWidgetRenderer,
|
|
37
65
|
getWidgetRenderer,
|
|
@@ -81,6 +109,7 @@ export {
|
|
|
81
109
|
type RightSidebarProps,
|
|
82
110
|
} from './RightSidebar.js'
|
|
83
111
|
export { RightSidebarTrigger } from './RightSidebarTrigger.js'
|
|
112
|
+
export { SearchTrigger } from './SearchTrigger.js'
|
|
84
113
|
export {
|
|
85
114
|
useResizableWidth,
|
|
86
115
|
clampPanelWidth,
|
|
@@ -96,6 +125,15 @@ export { NotificationBell } from './NotificationBell.js'
|
|
|
96
125
|
export { RenderHookSlot } from './RenderHookSlot.js'
|
|
97
126
|
export { HeadHooks } from './HeadHooks.js'
|
|
98
127
|
|
|
128
|
+
export {
|
|
129
|
+
isNavItemActive,
|
|
130
|
+
type NavComponentProps,
|
|
131
|
+
type HeaderComponentProps,
|
|
132
|
+
type FooterComponentProps,
|
|
133
|
+
type ComponentSlotRegistry,
|
|
134
|
+
} from './component-slots.js'
|
|
135
|
+
export type { NavItem } from '../pageData.js'
|
|
136
|
+
|
|
99
137
|
// Re-export pure theme functions for client-safe usage (avoids importing main barrel which has server-only code)
|
|
100
138
|
export { generateThemeCSS } from '../theme/generate-css.js'
|
|
101
139
|
export { resolveTheme } from '../theme/resolve.js'
|
|
@@ -140,13 +140,17 @@ function NavTree({
|
|
|
140
140
|
)
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
export function SidebarLayout({ panel, basePath, currentPath, children }: AppShellProps) {
|
|
143
|
+
export function SidebarLayout({ panel, basePath, currentPath, children, componentSlotRegistry }: AppShellProps) {
|
|
144
144
|
const title = panel.branding?.title ?? panel.name
|
|
145
145
|
const groups = groupItems(panel.navigation ?? [])
|
|
146
146
|
const hooks = panel.renderHooks
|
|
147
147
|
const dn = panel.databaseNotifications
|
|
148
148
|
const bellInTopbar = dn && dn.position === 'topbar'
|
|
149
149
|
const bellInSidebar = dn && dn.position === 'sidebar'
|
|
150
|
+
const NavSlot = componentSlotRegistry?.nav
|
|
151
|
+
const HeaderSlot = componentSlotRegistry?.header
|
|
152
|
+
const FooterSlot = componentSlotRegistry?.footer
|
|
153
|
+
const slotProps = currentPath !== undefined ? { currentPath } : {}
|
|
150
154
|
|
|
151
155
|
return (
|
|
152
156
|
<SidebarProvider>
|
|
@@ -172,14 +176,17 @@ export function SidebarLayout({ panel, basePath, currentPath, children }: AppShe
|
|
|
172
176
|
|
|
173
177
|
<SidebarContent>
|
|
174
178
|
<RenderHookSlot name="panels::sidebar.nav.start" hooks={hooks} />
|
|
175
|
-
{
|
|
176
|
-
<
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
179
|
+
{NavSlot
|
|
180
|
+
? <NavSlot navigation={panel.navigation ?? []} basePath={basePath} {...slotProps} />
|
|
181
|
+
: groups.map((g, idx) => (
|
|
182
|
+
<SidebarGroup key={g.group ?? `__top__${idx}`}>
|
|
183
|
+
{g.group !== undefined && <SidebarGroupLabel>{g.group}</SidebarGroupLabel>}
|
|
184
|
+
<SidebarGroupContent>
|
|
185
|
+
<NavTree items={g.items} pathname={currentPath} basePath={basePath} />
|
|
186
|
+
</SidebarGroupContent>
|
|
187
|
+
</SidebarGroup>
|
|
188
|
+
))
|
|
189
|
+
}
|
|
183
190
|
<RenderHookSlot name="panels::sidebar.nav.end" hooks={hooks} />
|
|
184
191
|
</SidebarContent>
|
|
185
192
|
|
|
@@ -210,29 +217,33 @@ export function SidebarLayout({ panel, basePath, currentPath, children }: AppShe
|
|
|
210
217
|
</Sidebar>
|
|
211
218
|
|
|
212
219
|
<SidebarInset>
|
|
213
|
-
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
220
|
+
{HeaderSlot
|
|
221
|
+
? <HeaderSlot navigation={panel.navigation ?? []} basePath={basePath} {...slotProps} />
|
|
222
|
+
: <header className="sticky top-0 z-10 flex h-14 shrink-0 items-center gap-2">
|
|
223
|
+
<div className="flex flex-1 items-center gap-2 px-3">
|
|
224
|
+
<SidebarTrigger />
|
|
225
|
+
<Separator orientation="vertical" className="me-2 data-[orientation=vertical]:h-4" />
|
|
226
|
+
<RenderHookSlot name="panels::topbar.start" hooks={hooks} />
|
|
227
|
+
<SearchTrigger />
|
|
228
|
+
</div>
|
|
229
|
+
<div className="flex items-center gap-1 px-3">
|
|
230
|
+
<ThemeToggle />
|
|
231
|
+
{bellInTopbar && <NotificationBell meta={dn} />}
|
|
232
|
+
<RightSidebarTrigger />
|
|
233
|
+
<UserMenu
|
|
234
|
+
userMenu={panel.userMenu}
|
|
235
|
+
before={<RenderHookSlot name="panels::user-menu.before" hooks={hooks} />}
|
|
236
|
+
after={<RenderHookSlot name="panels::user-menu.after" hooks={hooks} />}
|
|
237
|
+
/>
|
|
238
|
+
<RenderHookSlot name="panels::topbar.end" hooks={hooks} />
|
|
239
|
+
</div>
|
|
240
|
+
</header>
|
|
241
|
+
}
|
|
232
242
|
<div className="flex flex-1 flex-col px-4 pb-4">
|
|
233
243
|
{children}
|
|
234
244
|
<RenderHookSlot name="panels::footer" hooks={hooks} />
|
|
235
245
|
</div>
|
|
246
|
+
{FooterSlot && <FooterSlot basePath={basePath} {...slotProps} />}
|
|
236
247
|
</SidebarInset>
|
|
237
248
|
</SidebarProvider>
|
|
238
249
|
)
|
|
@@ -166,7 +166,7 @@ function groupItems(items: NavItem[]): Array<{ group: string | undefined; items:
|
|
|
166
166
|
return order.map(g => ({ group: g, items: buckets.get(g)! }))
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
export function TopbarLayout({ panel, basePath, currentPath, children }: AppShellProps) {
|
|
169
|
+
export function TopbarLayout({ panel, basePath, currentPath, children, componentSlotRegistry }: AppShellProps) {
|
|
170
170
|
const title = panel.branding?.title ?? panel.name
|
|
171
171
|
const groups = groupItems(panel.navigation ?? [])
|
|
172
172
|
const hooks = panel.renderHooks
|
|
@@ -174,72 +174,85 @@ export function TopbarLayout({ panel, basePath, currentPath, children }: AppShel
|
|
|
174
174
|
// no sidebar exists in this layout, so the bell rides in the topbar
|
|
175
175
|
// chrome regardless of the configured position.
|
|
176
176
|
const dn = panel.databaseNotifications
|
|
177
|
+
const NavSlot = componentSlotRegistry?.nav
|
|
178
|
+
const HeaderSlot = componentSlotRegistry?.header
|
|
179
|
+
const FooterSlot = componentSlotRegistry?.footer
|
|
180
|
+
const slotProps = currentPath !== undefined ? { currentPath } : {}
|
|
177
181
|
|
|
178
182
|
return (
|
|
179
183
|
<div className="flex flex-col h-screen bg-background text-foreground overflow-hidden">
|
|
180
|
-
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
184
|
+
{HeaderSlot
|
|
185
|
+
? <HeaderSlot navigation={panel.navigation ?? []} basePath={basePath} {...slotProps} />
|
|
186
|
+
: <header className="h-14 shrink-0 border-b bg-card flex items-center gap-4 px-6">
|
|
187
|
+
<div className="flex items-center gap-2 me-2">
|
|
188
|
+
{panel.branding?.logo
|
|
189
|
+
? <>
|
|
190
|
+
<img src={panel.branding.logo} alt={title} className="h-6 w-6" />
|
|
191
|
+
<span className="text-sm font-semibold">{title}</span>
|
|
192
|
+
</>
|
|
193
|
+
: <span className="text-sm font-semibold">{title}</span>
|
|
194
|
+
}
|
|
195
|
+
</div>
|
|
196
|
+
<Separator orientation="vertical" className="h-4" />
|
|
197
|
+
<RenderHookSlot name="panels::topbar.start" hooks={hooks} />
|
|
198
|
+
{NavSlot
|
|
199
|
+
? <div className="flex items-center gap-1 flex-1 overflow-x-auto">
|
|
200
|
+
<NavSlot navigation={panel.navigation ?? []} basePath={basePath} {...slotProps} />
|
|
201
|
+
</div>
|
|
202
|
+
: <nav className="flex items-center gap-1 flex-1 overflow-x-auto">
|
|
203
|
+
<a
|
|
204
|
+
href={basePath}
|
|
205
|
+
className={cn(linkBase, currentPath === basePath ? linkActive : linkIdle)}
|
|
206
|
+
>
|
|
207
|
+
Dashboard
|
|
208
|
+
</a>
|
|
209
|
+
{groups.map((g, idx) => {
|
|
210
|
+
if (g.group === undefined) {
|
|
211
|
+
return g.items.map(it => (
|
|
212
|
+
it.children && it.children.length > 0
|
|
213
|
+
? <ParentDropdown key={it.name} item={it} pathname={currentPath} basePath={basePath} />
|
|
214
|
+
: <FlatLink key={it.name} item={it} pathname={currentPath} basePath={basePath} />
|
|
215
|
+
))
|
|
216
|
+
}
|
|
217
|
+
return (
|
|
218
|
+
<GroupDropdown
|
|
219
|
+
key={`${g.group}__${idx}`}
|
|
220
|
+
label={g.group}
|
|
221
|
+
items={g.items}
|
|
222
|
+
pathname={currentPath}
|
|
223
|
+
basePath={basePath}
|
|
224
|
+
/>
|
|
225
|
+
)
|
|
226
|
+
})}
|
|
227
|
+
{panel.themeEditor && (
|
|
228
|
+
<a
|
|
229
|
+
href={`${basePath}/theme`}
|
|
230
|
+
className={cn(linkBase, currentPath === `${basePath}/theme` ? linkActive : linkIdle)}
|
|
231
|
+
>
|
|
232
|
+
Theme
|
|
233
|
+
</a>
|
|
234
|
+
)}
|
|
235
|
+
</nav>
|
|
206
236
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
/>
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
href={`${basePath}/theme`}
|
|
220
|
-
className={cn(linkBase, currentPath === `${basePath}/theme` ? linkActive : linkIdle)}
|
|
221
|
-
>
|
|
222
|
-
Theme
|
|
223
|
-
</a>
|
|
224
|
-
)}
|
|
225
|
-
</nav>
|
|
226
|
-
<SearchTrigger />
|
|
227
|
-
<ThemeToggle />
|
|
228
|
-
{dn && <NotificationBell meta={dn} />}
|
|
229
|
-
<RightSidebarTrigger />
|
|
230
|
-
<UserMenu
|
|
231
|
-
userMenu={panel.userMenu}
|
|
232
|
-
before={<RenderHookSlot name="panels::user-menu.before" hooks={hooks} />}
|
|
233
|
-
after={<RenderHookSlot name="panels::user-menu.after" hooks={hooks} />}
|
|
234
|
-
/>
|
|
235
|
-
<RenderHookSlot name="panels::topbar.end" hooks={hooks} />
|
|
236
|
-
</header>
|
|
237
|
+
<SearchTrigger />
|
|
238
|
+
<ThemeToggle />
|
|
239
|
+
{dn && <NotificationBell meta={dn} />}
|
|
240
|
+
<RightSidebarTrigger />
|
|
241
|
+
<UserMenu
|
|
242
|
+
userMenu={panel.userMenu}
|
|
243
|
+
before={<RenderHookSlot name="panels::user-menu.before" hooks={hooks} />}
|
|
244
|
+
after={<RenderHookSlot name="panels::user-menu.after" hooks={hooks} />}
|
|
245
|
+
/>
|
|
246
|
+
<RenderHookSlot name="panels::topbar.end" hooks={hooks} />
|
|
247
|
+
</header>
|
|
248
|
+
}
|
|
237
249
|
<div className="flex flex-1 overflow-hidden">
|
|
238
250
|
<main className="flex-1 overflow-y-auto p-6">
|
|
239
251
|
{children}
|
|
240
252
|
<RenderHookSlot name="panels::footer" hooks={hooks} />
|
|
241
253
|
</main>
|
|
242
254
|
</div>
|
|
255
|
+
{FooterSlot && <FooterSlot basePath={basePath} {...slotProps} />}
|
|
243
256
|
</div>
|
|
244
257
|
)
|
|
245
258
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { parseRecordEditUrl } from './parseRecordEditUrl.js'
|
|
4
|
+
|
|
5
|
+
test('parseRecordEditUrl: bare resource edit', () => {
|
|
6
|
+
assert.deepEqual(
|
|
7
|
+
parseRecordEditUrl('/admin/articles/123/edit', '/admin'),
|
|
8
|
+
{ resourceSlug: 'articles', recordId: '123' },
|
|
9
|
+
)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('parseRecordEditUrl: cluster-prefixed resource edit', () => {
|
|
13
|
+
assert.deepEqual(
|
|
14
|
+
parseRecordEditUrl('/admin/blog/articles/123/edit', '/admin'),
|
|
15
|
+
{ resourceSlug: 'blog/articles', recordId: '123' },
|
|
16
|
+
)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('parseRecordEditUrl: nested-relation edit picks child id', () => {
|
|
20
|
+
assert.deepEqual(
|
|
21
|
+
parseRecordEditUrl('/admin/articles/123/comments/456/edit', '/admin'),
|
|
22
|
+
{ resourceSlug: 'articles/123/comments', recordId: '456' },
|
|
23
|
+
)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('parseRecordEditUrl: list page returns null', () => {
|
|
27
|
+
assert.equal(parseRecordEditUrl('/admin/articles', '/admin'), null)
|
|
28
|
+
assert.equal(parseRecordEditUrl('/admin/articles/123', '/admin'), null)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('parseRecordEditUrl: create page returns null', () => {
|
|
32
|
+
assert.equal(parseRecordEditUrl('/admin/articles/create', '/admin'), null)
|
|
33
|
+
assert.equal(parseRecordEditUrl('/admin/articles/123/comments/create', '/admin'), null)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('parseRecordEditUrl: basePath mismatch returns null', () => {
|
|
37
|
+
assert.equal(parseRecordEditUrl('/site/articles/123/edit', '/admin'), null)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('parseRecordEditUrl: trailing slashes tolerated on URL', () => {
|
|
41
|
+
assert.deepEqual(
|
|
42
|
+
parseRecordEditUrl('/admin/articles/123/edit/', '/admin'),
|
|
43
|
+
{ resourceSlug: 'articles', recordId: '123' },
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('parseRecordEditUrl: trailing slashes tolerated on basePath', () => {
|
|
48
|
+
assert.deepEqual(
|
|
49
|
+
parseRecordEditUrl('/admin/articles/123/edit', '/admin/'),
|
|
50
|
+
{ resourceSlug: 'articles', recordId: '123' },
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('parseRecordEditUrl: root basePath', () => {
|
|
55
|
+
assert.deepEqual(
|
|
56
|
+
parseRecordEditUrl('/articles/123/edit', ''),
|
|
57
|
+
{ resourceSlug: 'articles', recordId: '123' },
|
|
58
|
+
)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('parseRecordEditUrl: empty path returns null', () => {
|
|
62
|
+
assert.equal(parseRecordEditUrl('', '/admin'), null)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('parseRecordEditUrl: too-short path returns null', () => {
|
|
66
|
+
assert.equal(parseRecordEditUrl('/admin/edit', '/admin'), null)
|
|
67
|
+
// 'edit' alone after basePath isn't enough — needs slug + id + 'edit'.
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('parseRecordEditUrl: slug-only edit (no record id) returns null', () => {
|
|
71
|
+
// '/admin/123/edit' would technically match parts.length===3 with
|
|
72
|
+
// 'edit' last and '123' as recordId — but with empty slug after the
|
|
73
|
+
// slice. Defensive: reject when slugParts is empty.
|
|
74
|
+
assert.equal(parseRecordEditUrl('/admin/edit', '/admin'), null)
|
|
75
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a pilotiq URL into a record-edit identity, or returns `null`
|
|
3
|
+
* for any URL that isn't a record-bound edit page.
|
|
4
|
+
*
|
|
5
|
+
* A URL matches when:
|
|
6
|
+
* 1. it starts with the panel's `basePath`
|
|
7
|
+
* 2. after stripping the prefix it ends with `/edit`
|
|
8
|
+
* 3. there are at least three remaining segments (resource slug,
|
|
9
|
+
* record id, `edit`)
|
|
10
|
+
*
|
|
11
|
+
* The `resourceSlug` is the slash-joined chain of every segment up to
|
|
12
|
+
* the record id — this gives clustered resources (`${base}/blog/articles/123/edit`)
|
|
13
|
+
* and nested-relation edits (`${base}/articles/123/comments/456/edit`)
|
|
14
|
+
* distinct slugs so two URLs that target different records always
|
|
15
|
+
* produce different room names downstream.
|
|
16
|
+
*
|
|
17
|
+
* `/admin/articles/123/edit` → { resourceSlug: 'articles', recordId: '123' }
|
|
18
|
+
* `/admin/blog/articles/123/edit` → { resourceSlug: 'blog/articles', recordId: '123' }
|
|
19
|
+
* `/admin/articles/123/comments/456/edit` → { resourceSlug: 'articles/123/comments', recordId: '456' }
|
|
20
|
+
* `/admin/articles/123/comments` → null (no trailing /edit)
|
|
21
|
+
* `/admin/articles/123/comments/create` → null (no record id)
|
|
22
|
+
* `/site/articles/123/edit` → null (basePath mismatch when basePath='/admin')
|
|
23
|
+
*/
|
|
24
|
+
export interface RecordEditIdentity {
|
|
25
|
+
resourceSlug: string
|
|
26
|
+
recordId: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function parseRecordEditUrl(
|
|
30
|
+
currentPath: string,
|
|
31
|
+
basePath: string,
|
|
32
|
+
): RecordEditIdentity | null {
|
|
33
|
+
if (!currentPath) return null
|
|
34
|
+
// Normalise — trailing slashes on the URL or trailing slashes on
|
|
35
|
+
// basePath would otherwise reject perfectly valid matches.
|
|
36
|
+
const trimmedPath = currentPath.replace(/\/+$/, '')
|
|
37
|
+
const trimmedBase = basePath.replace(/\/+$/, '')
|
|
38
|
+
|
|
39
|
+
if (trimmedBase !== '' && !trimmedPath.startsWith(trimmedBase)) return null
|
|
40
|
+
|
|
41
|
+
const tail = trimmedPath.slice(trimmedBase.length).replace(/^\/+/, '')
|
|
42
|
+
const parts = tail.split('/').filter(Boolean)
|
|
43
|
+
|
|
44
|
+
if (parts.length < 3) return null
|
|
45
|
+
if (parts[parts.length - 1] !== 'edit') return null
|
|
46
|
+
|
|
47
|
+
const recordId = parts[parts.length - 2]!
|
|
48
|
+
const slugParts = parts.slice(0, parts.length - 2)
|
|
49
|
+
if (slugParts.length === 0) return null
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
resourceSlug: slugParts.join('/'),
|
|
53
|
+
recordId,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin localStorage wrappers shared by every persistable surface
|
|
3
|
+
* (collapsible sections, dismissible alerts, wizard step, hidden table
|
|
4
|
+
* columns, group fold state, filter strip toggle, Repeater / Builder
|
|
5
|
+
* row collapse + accordion).
|
|
6
|
+
*
|
|
7
|
+
* Every helper silently no-ops on SSR / private mode / quota — callers
|
|
8
|
+
* already treat all three the same way.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export function readStoredString(key: string): string | null {
|
|
12
|
+
if (typeof window === 'undefined') return null
|
|
13
|
+
try { return window.localStorage.getItem(key) }
|
|
14
|
+
catch { return null }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function writeStoredString(key: string, value: string): void {
|
|
18
|
+
if (typeof window === 'undefined') return
|
|
19
|
+
try { window.localStorage.setItem(key, value) } catch { /* quota / blocked */ }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function removeStoredString(key: string): void {
|
|
23
|
+
if (typeof window === 'undefined') return
|
|
24
|
+
try { window.localStorage.removeItem(key) } catch { /* */ }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Boolean flag using `'1'` / `'0'` encoding. Returns `defaultValue`
|
|
29
|
+
* when the key is unset, SSR, or storage is blocked.
|
|
30
|
+
*/
|
|
31
|
+
export function readStoredFlag(key: string, defaultValue: boolean): boolean {
|
|
32
|
+
const raw = readStoredString(key)
|
|
33
|
+
if (raw === '1') return true
|
|
34
|
+
if (raw === '0') return false
|
|
35
|
+
return defaultValue
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function writeStoredFlag(key: string, value: boolean): void {
|
|
39
|
+
writeStoredString(key, value ? '1' : '0')
|
|
40
|
+
}
|