@pilotiq/pilotiq 0.23.1 → 0.24.2
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/CHANGELOG.md +91 -0
- package/boost/guidelines.md +566 -0
- package/boost/skills/pilotiq-fields/SKILL.md +47 -0
- package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
- package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
- package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
- package/boost/skills/pilotiq-relations/SKILL.md +47 -0
- package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
- package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
- package/boost/skills/pilotiq-resource/SKILL.md +61 -0
- package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
- package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
- package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
- package/dist/actions/exportFactory.d.ts +10 -0
- package/dist/actions/exportFactory.d.ts.map +1 -1
- package/dist/actions/exportFactory.js +10 -0
- package/dist/actions/exportFactory.js.map +1 -1
- package/dist/react/CollabRoomContext.d.ts +5 -5
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +0 -1
- package/dist/react/index.js.map +1 -1
- package/dist/routes/helpers.d.ts.map +1 -1
- package/dist/routes/helpers.js +6 -2
- package/dist/routes/helpers.js.map +1 -1
- package/dist/routes/relations.d.ts.map +1 -1
- package/dist/routes/relations.js +12 -0
- package/dist/routes/relations.js.map +1 -1
- package/package.json +6 -1
- package/.turbo/turbo-build.log +0 -8
- package/CLAUDE.md +0 -265
- package/dist/react/useCollabSeed.d.ts +0 -23
- package/dist/react/useCollabSeed.d.ts.map +0 -1
- package/dist/react/useCollabSeed.js +0 -82
- package/dist/react/useCollabSeed.js.map +0 -1
- package/src/Cluster.test.ts +0 -283
- package/src/Cluster.ts +0 -83
- package/src/Column.test.ts +0 -199
- package/src/Column.ts +0 -710
- package/src/Global.test.ts +0 -367
- package/src/Global.ts +0 -169
- package/src/Page.test.ts +0 -114
- package/src/Page.ts +0 -208
- package/src/Pilotiq.perf.test.ts +0 -252
- package/src/Pilotiq.test.ts +0 -129
- package/src/Pilotiq.ts +0 -1158
- package/src/PilotiqRegistry.ts +0 -36
- package/src/PilotiqServiceProvider.ts +0 -121
- package/src/RelationManager.test.ts +0 -400
- package/src/RelationManager.ts +0 -527
- package/src/RenderHook.test.ts +0 -252
- package/src/RenderHook.ts +0 -242
- package/src/Resource.test.ts +0 -284
- package/src/Resource.ts +0 -526
- package/src/RightPanel.test.ts +0 -202
- package/src/RightPanel.ts +0 -132
- package/src/Tab.test.ts +0 -91
- package/src/Tab.ts +0 -156
- package/src/UserMenuItem.ts +0 -145
- package/src/actions/Action.test.ts +0 -2526
- package/src/actions/Action.ts +0 -1515
- package/src/actions/ActionGroup.test.ts +0 -112
- package/src/actions/ActionGroup.ts +0 -173
- package/src/actions/attachFactory.ts +0 -172
- package/src/actions/bulkFactories.ts +0 -168
- package/src/actions/crudFactories.ts +0 -220
- package/src/actions/exportFactory.ts +0 -215
- package/src/actions/factoryHelpers.ts +0 -177
- package/src/actions/importFactory.ts +0 -243
- package/src/actions/index.ts +0 -17
- package/src/actions/m2mFactories.ts +0 -193
- package/src/actions/relationFactories.ts +0 -372
- package/src/applyPageHooks.test.ts +0 -463
- package/src/applyPageHooks.ts +0 -330
- package/src/authorization.test.ts +0 -483
- package/src/breadcrumbs.test.ts +0 -238
- package/src/cells/coerce.test.ts +0 -85
- package/src/cells/coerce.ts +0 -84
- package/src/clusterPaths.ts +0 -35
- package/src/columns/BadgeColumn.test.ts +0 -54
- package/src/columns/BadgeColumn.ts +0 -32
- package/src/columns/BooleanColumn.test.ts +0 -41
- package/src/columns/BooleanColumn.ts +0 -18
- package/src/columns/ColorColumn.test.ts +0 -37
- package/src/columns/ColorColumn.ts +0 -38
- package/src/columns/IconColumn.test.ts +0 -54
- package/src/columns/IconColumn.ts +0 -37
- package/src/columns/ImageColumn.test.ts +0 -41
- package/src/columns/ImageColumn.ts +0 -28
- package/src/columns/SelectColumn.ts +0 -98
- package/src/columns/TextColumn.test.ts +0 -190
- package/src/columns/TextColumn.ts +0 -20
- package/src/columns/TextInputColumn.ts +0 -68
- package/src/columns/ToggleColumn.ts +0 -46
- package/src/columns/editableColumns.test.ts +0 -238
- package/src/columns/index.ts +0 -9
- package/src/defaultGlobalPages.ts +0 -95
- package/src/defaultPages.test.ts +0 -634
- package/src/defaultPages.ts +0 -617
- package/src/defaultViewPage.test.ts +0 -147
- package/src/elements/Form.test.ts +0 -223
- package/src/elements/Form.ts +0 -416
- package/src/elements/ListTabs.ts +0 -28
- package/src/elements/Table.test.ts +0 -422
- package/src/elements/Table.ts +0 -850
- package/src/elements/TableGroup.test.ts +0 -260
- package/src/elements/TableGroup.ts +0 -334
- package/src/elements/dispatchAction.test.ts +0 -463
- package/src/elements/dispatchAction.ts +0 -355
- package/src/elements/dispatchForm.test.ts +0 -477
- package/src/elements/dispatchForm.ts +0 -1993
- package/src/elements/dispatchTable.test.ts +0 -1514
- package/src/elements/dispatchTable.ts +0 -745
- package/src/elements/index.ts +0 -21
- package/src/entries/BadgeEntry.ts +0 -39
- package/src/entries/CodeEntry.test.ts +0 -40
- package/src/entries/CodeEntry.ts +0 -52
- package/src/entries/ColorEntry.ts +0 -63
- package/src/entries/ComponentEntry.test.ts +0 -173
- package/src/entries/ComponentEntry.ts +0 -95
- package/src/entries/Entry.ts +0 -304
- package/src/entries/IconEntry.ts +0 -49
- package/src/entries/ImageEntry.ts +0 -61
- package/src/entries/KeyValueEntry.ts +0 -47
- package/src/entries/RepeatableEntry.test.ts +0 -239
- package/src/entries/RepeatableEntry.ts +0 -173
- package/src/entries/TextEntry.test.ts +0 -394
- package/src/entries/TextEntry.ts +0 -60
- package/src/entries/index.ts +0 -12
- package/src/entries/leaves.test.ts +0 -306
- package/src/entries/registry.ts +0 -54
- package/src/fields/BuilderField.test.ts +0 -1188
- package/src/fields/BuilderField.ts +0 -605
- package/src/fields/BuilderRelationship.test.ts +0 -811
- package/src/fields/CheckboxField.test.ts +0 -44
- package/src/fields/CheckboxField.ts +0 -27
- package/src/fields/CheckboxListField.test.ts +0 -99
- package/src/fields/CheckboxListField.ts +0 -66
- package/src/fields/ColorPickerField.test.ts +0 -33
- package/src/fields/ColorPickerField.ts +0 -25
- package/src/fields/DateField.ts +0 -54
- package/src/fields/DateTimeField.test.ts +0 -55
- package/src/fields/EmailField.ts +0 -16
- package/src/fields/Field.test.ts +0 -654
- package/src/fields/Field.ts +0 -817
- package/src/fields/FileUploadField.test.ts +0 -143
- package/src/fields/FileUploadField.ts +0 -159
- package/src/fields/HiddenField.test.ts +0 -27
- package/src/fields/HiddenField.ts +0 -28
- package/src/fields/KeyValueField.test.ts +0 -105
- package/src/fields/KeyValueField.ts +0 -55
- package/src/fields/MarkdownField.test.ts +0 -167
- package/src/fields/MarkdownField.ts +0 -162
- package/src/fields/NumberField.ts +0 -33
- package/src/fields/RadioField.test.ts +0 -94
- package/src/fields/RadioField.ts +0 -67
- package/src/fields/RepeaterField.test.ts +0 -1806
- package/src/fields/RepeaterField.ts +0 -939
- package/src/fields/RepeaterRelationship.test.ts +0 -1923
- package/src/fields/RepeaterSimple.test.ts +0 -248
- package/src/fields/RowButton.test.ts +0 -219
- package/src/fields/RowButton.ts +0 -135
- package/src/fields/SelectField.test.ts +0 -192
- package/src/fields/SelectField.ts +0 -235
- package/src/fields/SliderField.test.ts +0 -50
- package/src/fields/SliderField.ts +0 -53
- package/src/fields/SlugField.ts +0 -24
- package/src/fields/TagsInputField.test.ts +0 -154
- package/src/fields/TagsInputField.ts +0 -133
- package/src/fields/TextField.test.ts +0 -213
- package/src/fields/TextField.ts +0 -177
- package/src/fields/TextareaField.test.ts +0 -58
- package/src/fields/TextareaField.ts +0 -59
- package/src/fields/ToggleButtonsField.test.ts +0 -106
- package/src/fields/ToggleButtonsField.ts +0 -59
- package/src/fields/ToggleField.ts +0 -16
- package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
- package/src/fields/optionsResolver.ts +0 -95
- package/src/fields/resolveField.ts +0 -28
- package/src/filters/BooleanFilter.ts +0 -35
- package/src/filters/DateRangeFilter.test.ts +0 -194
- package/src/filters/DateRangeFilter.ts +0 -148
- package/src/filters/Filter.test.ts +0 -268
- package/src/filters/Filter.ts +0 -184
- package/src/filters/FormFilter.test.ts +0 -238
- package/src/filters/FormFilter.ts +0 -215
- package/src/filters/MultiSelectFilter.test.ts +0 -119
- package/src/filters/MultiSelectFilter.ts +0 -78
- package/src/filters/QueryBuilderFilter.test.ts +0 -662
- package/src/filters/QueryBuilderFilter.ts +0 -398
- package/src/filters/SelectFilter.ts +0 -46
- package/src/filters/TernaryFilter.test.ts +0 -160
- package/src/filters/TernaryFilter.ts +0 -72
- package/src/filters/TrashedFilter.test.ts +0 -149
- package/src/filters/TrashedFilter.ts +0 -55
- package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
- package/src/filters/queryBuilder/Constraint.ts +0 -115
- package/src/filters/queryBuilder/DateConstraint.ts +0 -69
- package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
- package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
- package/src/filters/queryBuilder/TextConstraint.ts +0 -64
- package/src/filters/queryBuilder/index.ts +0 -12
- package/src/icons/index.ts +0 -2
- package/src/icons/lucide.ts +0 -204
- package/src/icons/registry.test.ts +0 -56
- package/src/icons/registry.ts +0 -41
- package/src/icons/types.ts +0 -47
- package/src/index.ts +0 -525
- package/src/io/csv.test.ts +0 -142
- package/src/io/csv.ts +0 -170
- package/src/nestedRelationManagerData.test.ts +0 -547
- package/src/notifications/Notification.test.ts +0 -210
- package/src/notifications/Notification.ts +0 -354
- package/src/notifications/broadcast.test.ts +0 -110
- package/src/notifications/broadcast.ts +0 -95
- package/src/notifications/database.test.ts +0 -383
- package/src/notifications/database.ts +0 -398
- package/src/notifications/databaseNotifications.test.ts +0 -187
- package/src/notifications/dispatchNotificationAction.test.ts +0 -341
- package/src/notifications/dispatchNotificationAction.ts +0 -142
- package/src/notifications/flash.test.ts +0 -89
- package/src/notifications/flash.ts +0 -71
- package/src/notifications/index.ts +0 -45
- package/src/notifications/registerBroadcastAuth.test.ts +0 -134
- package/src/notifications/registerBroadcastAuth.ts +0 -100
- package/src/notifications/resolveSavedNotification.test.ts +0 -82
- package/src/notifications/resolveSavedNotification.ts +0 -59
- package/src/notifications/types.ts +0 -93
- package/src/orm/m2mAccessor.ts +0 -66
- package/src/orm/modelDefaults.test.ts +0 -633
- package/src/orm/modelDefaults.ts +0 -666
- package/src/pageData/breadcrumbs.ts +0 -288
- package/src/pageData/forms.ts +0 -578
- package/src/pageData/helpers.ts +0 -857
- package/src/pageData/misc.ts +0 -347
- package/src/pageData/navigation.ts +0 -842
- package/src/pageData/relationPages.ts +0 -1248
- package/src/pageData/relationTabs.ts +0 -286
- package/src/pageData/resourcePages.ts +0 -609
- package/src/pageData.test.ts +0 -1545
- package/src/pageData.ts +0 -341
- package/src/plugins/index.ts +0 -8
- package/src/plugins/themeEditor.test.ts +0 -36
- package/src/plugins/themeEditor.ts +0 -45
- package/src/react/AppShell.tsx +0 -251
- package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
- package/src/react/CollabRoomContext.ts +0 -98
- package/src/react/CollabTextRendererRegistry.ts +0 -102
- package/src/react/CommandPalette.tsx +0 -375
- package/src/react/CurrentUserContext.tsx +0 -50
- package/src/react/CustomPageWrapperGate.tsx +0 -69
- package/src/react/CustomPageWrapperRegistry.ts +0 -45
- package/src/react/FieldFocusReporterRegistry.ts +0 -37
- package/src/react/FieldLabelSlotRegistry.ts +0 -30
- package/src/react/FieldPresenceRegistry.ts +0 -46
- package/src/react/FormCollabBindingRegistry.ts +0 -242
- package/src/react/FormStateContext.tsx +0 -591
- package/src/react/HeadHooks.tsx +0 -126
- package/src/react/MarkdownEditorRegistry.test.ts +0 -38
- package/src/react/MarkdownEditorRegistry.ts +0 -107
- package/src/react/NotificationActionStrip.tsx +0 -263
- package/src/react/NotificationBell.tsx +0 -426
- package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
- package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
- package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
- package/src/react/PendingSuggestionsContext.tsx +0 -172
- package/src/react/RecordWrapperGate.tsx +0 -58
- package/src/react/RecordWrapperRegistry.ts +0 -39
- package/src/react/RenderHookSlot.tsx +0 -32
- package/src/react/RightSidebar.tsx +0 -257
- package/src/react/RightSidebarContext.tsx +0 -234
- package/src/react/RightSidebarTrigger.tsx +0 -53
- package/src/react/RowCoordsContext.tsx +0 -23
- package/src/react/SchemaRenderer.tsx +0 -549
- package/src/react/SearchTrigger.tsx +0 -46
- package/src/react/ThemeProvider.tsx +0 -93
- package/src/react/ThemeSettingsPage.tsx +0 -579
- package/src/react/ThemeToggle.tsx +0 -20
- package/src/react/Toaster.tsx +0 -158
- package/src/react/UserMenu.tsx +0 -196
- package/src/react/WidgetDataContext.tsx +0 -157
- package/src/react/cells/EditableCell.tsx +0 -389
- package/src/react/component-slots.test.ts +0 -103
- package/src/react/component-slots.ts +0 -116
- package/src/react/fieldJsHandler.test.ts +0 -166
- package/src/react/fieldJsHandler.ts +0 -79
- package/src/react/fields/BuilderInput.tsx +0 -1078
- package/src/react/fields/CheckboxInput.tsx +0 -39
- package/src/react/fields/CheckboxListInput.tsx +0 -102
- package/src/react/fields/ColorInput.tsx +0 -71
- package/src/react/fields/DateFieldInput.tsx +0 -70
- package/src/react/fields/DateTimeInput.tsx +0 -62
- package/src/react/fields/FieldShell.tsx +0 -348
- package/src/react/fields/FileUploadInput.tsx +0 -639
- package/src/react/fields/HiddenInput.tsx +0 -17
- package/src/react/fields/KeyValueInput.tsx +0 -230
- package/src/react/fields/MarkdownInput.tsx +0 -560
- package/src/react/fields/RadioInput.tsx +0 -81
- package/src/react/fields/RepeaterInput.test.ts +0 -116
- package/src/react/fields/RepeaterInput.tsx +0 -1420
- package/src/react/fields/SelectFieldInput.tsx +0 -280
- package/src/react/fields/SliderInput.tsx +0 -81
- package/src/react/fields/TagsInput.tsx +0 -283
- package/src/react/fields/TextLikeInput.tsx +0 -256
- package/src/react/fields/ToggleButtonsInput.tsx +0 -60
- package/src/react/fields/ToggleFieldInput.tsx +0 -56
- package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
- package/src/react/fields/relationshipRenameDispatch.ts +0 -97
- package/src/react/fields/repeaterReconcile.test.ts +0 -114
- package/src/react/fields/repeaterReconcile.ts +0 -104
- package/src/react/fields/rowChromeButton.tsx +0 -336
- package/src/react/fields/rowState.ts +0 -106
- package/src/react/fields/syncRowGates.test.ts +0 -202
- package/src/react/fields/syncRowGates.ts +0 -66
- package/src/react/fields/textInputControls.tsx +0 -238
- package/src/react/fields/useRowReorderDnd.ts +0 -78
- package/src/react/formStateHelpers.test.ts +0 -508
- package/src/react/formStateHelpers.ts +0 -381
- package/src/react/hooks/use-mobile.ts +0 -19
- package/src/react/icon-context.tsx +0 -60
- package/src/react/index.ts +0 -195
- package/src/react/layouts/SidebarLayout.tsx +0 -250
- package/src/react/layouts/TopbarLayout.tsx +0 -258
- package/src/react/navigate.tsx +0 -37
- package/src/react/onProviderSynced.test.ts +0 -90
- package/src/react/parseRecordEditUrl.test.ts +0 -122
- package/src/react/parseRecordEditUrl.ts +0 -94
- package/src/react/persistedState.ts +0 -40
- package/src/react/registry.ts +0 -48
- package/src/react/right-panel-registry.tsx +0 -47
- package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
- package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
- package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
- package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
- package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
- package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
- package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
- package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
- package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
- package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
- package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
- package/src/react/schemaRenderer/action/buttons.tsx +0 -99
- package/src/react/schemaRenderer/action/helpers.ts +0 -140
- package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
- package/src/react/schemaRenderer/columnFormat.ts +0 -65
- package/src/react/schemaRenderer/constants.ts +0 -50
- package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
- package/src/react/schemaRenderer/form/renderField.tsx +0 -511
- package/src/react/schemaRenderer/helpers.tsx +0 -81
- package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
- package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
- package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
- package/src/react/schemaRenderer/table/filters.tsx +0 -1233
- package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
- package/src/react/schemaRenderer/table/links.tsx +0 -112
- package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
- package/src/react/schemaRenderer/table/url.tsx +0 -143
- package/src/react/theme-preview/apply.ts +0 -99
- package/src/react/theme-preview/build-html.ts +0 -436
- package/src/react/ui/button.tsx +0 -51
- package/src/react/ui/calendar.tsx +0 -67
- package/src/react/ui/checkbox.tsx +0 -29
- package/src/react/ui/dialog.tsx +0 -108
- package/src/react/ui/dropdown-menu.tsx +0 -97
- package/src/react/ui/input.tsx +0 -20
- package/src/react/ui/label.tsx +0 -21
- package/src/react/ui/popover.tsx +0 -50
- package/src/react/ui/select.tsx +0 -169
- package/src/react/ui/separator.tsx +0 -25
- package/src/react/ui/sheet.tsx +0 -136
- package/src/react/ui/sidebar.tsx +0 -723
- package/src/react/ui/skeleton.tsx +0 -13
- package/src/react/ui/slider.tsx +0 -34
- package/src/react/ui/switch.tsx +0 -28
- package/src/react/ui/table.tsx +0 -105
- package/src/react/ui/tabs.tsx +0 -63
- package/src/react/ui/textarea.tsx +0 -18
- package/src/react/ui/tooltip.tsx +0 -64
- package/src/react/useCollabSeed.ts +0 -86
- package/src/react/useResizableWidth.ts +0 -139
- package/src/react/utils.ts +0 -6
- package/src/react/widgetRegistry.test.ts +0 -43
- package/src/react/widgetRegistry.ts +0 -50
- package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
- package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
- package/src/react/widgets/ViewRenderer.tsx +0 -71
- package/src/relationManagerData.test.ts +0 -1595
- package/src/richtext/index.ts +0 -8
- package/src/richtext/registry.ts +0 -89
- package/src/routes/globals.ts +0 -148
- package/src/routes/guard.test.ts +0 -325
- package/src/routes/helpers.ts +0 -700
- package/src/routes/pages.ts +0 -175
- package/src/routes/panel.ts +0 -204
- package/src/routes/relations.ts +0 -1227
- package/src/routes/resources.ts +0 -781
- package/src/routes/theme.ts +0 -91
- package/src/routes-nested-relations.test.ts +0 -676
- package/src/routes-relations.test.ts +0 -972
- package/src/routes.test.ts +0 -2027
- package/src/routes.ts +0 -303
- package/src/schema/Alert.test.ts +0 -109
- package/src/schema/Alert.ts +0 -131
- package/src/schema/Block.ts +0 -169
- package/src/schema/Breadcrumbs.ts +0 -40
- package/src/schema/Card.ts +0 -35
- package/src/schema/Divider.ts +0 -20
- package/src/schema/Element.ts +0 -219
- package/src/schema/EmptyState.test.ts +0 -37
- package/src/schema/EmptyState.ts +0 -63
- package/src/schema/Fieldset.ts +0 -43
- package/src/schema/Grid.ts +0 -43
- package/src/schema/Group.ts +0 -30
- package/src/schema/Heading.ts +0 -39
- package/src/schema/Html.ts +0 -67
- package/src/schema/Icon.ts +0 -54
- package/src/schema/Image.ts +0 -57
- package/src/schema/LinkTag.ts +0 -41
- package/src/schema/Markdown.ts +0 -85
- package/src/schema/MetaTag.ts +0 -41
- package/src/schema/RelationTabs.ts +0 -71
- package/src/schema/ScriptTag.ts +0 -55
- package/src/schema/Section.ts +0 -160
- package/src/schema/ServerDataElement.test.ts +0 -140
- package/src/schema/ServerDataElement.ts +0 -156
- package/src/schema/SlotComponent.test.ts +0 -77
- package/src/schema/SlotComponent.ts +0 -71
- package/src/schema/Split.ts +0 -50
- package/src/schema/Stat.test.ts +0 -118
- package/src/schema/Stat.ts +0 -154
- package/src/schema/StatsOverview.test.ts +0 -141
- package/src/schema/StatsOverview.ts +0 -119
- package/src/schema/StyleTag.ts +0 -35
- package/src/schema/TableWidget.test.ts +0 -297
- package/src/schema/TableWidget.ts +0 -289
- package/src/schema/Tabs.ts +0 -79
- package/src/schema/Text.ts +0 -58
- package/src/schema/UnorderedList.ts +0 -49
- package/src/schema/View.test.ts +0 -111
- package/src/schema/View.ts +0 -127
- package/src/schema/Wizard.ts +0 -220
- package/src/schema/containers.test.ts +0 -564
- package/src/schema/headTags.test.ts +0 -134
- package/src/schema/index.ts +0 -40
- package/src/schema/primes.test.ts +0 -269
- package/src/schema/resolveSchema.test.ts +0 -379
- package/src/schema/resolveSchema.ts +0 -917
- package/src/schema/sanitize.ts +0 -58
- package/src/search.test.ts +0 -446
- package/src/search.ts +0 -178
- package/src/sessionFilters.test.ts +0 -375
- package/src/sessionFilters.ts +0 -143
- package/src/slot-components/index.ts +0 -10
- package/src/slot-components/registry.ts +0 -56
- package/src/styles/file-upload.css +0 -13
- package/src/summarizers/Summarizer.test.ts +0 -84
- package/src/summarizers/Summarizer.ts +0 -123
- package/src/summarizers/index.ts +0 -11
- package/src/theme/base-colors.ts +0 -68
- package/src/theme/chart-colors.ts +0 -50
- package/src/theme/colors.ts +0 -447
- package/src/theme/generate-css.test.ts +0 -139
- package/src/theme/generate-css.ts +0 -44
- package/src/theme/generate-scale.test.ts +0 -106
- package/src/theme/generate-scale.ts +0 -97
- package/src/theme/icon-map.ts +0 -42
- package/src/theme/index.ts +0 -34
- package/src/theme/migrate.test.ts +0 -178
- package/src/theme/migrate.ts +0 -81
- package/src/theme/presets.ts +0 -135
- package/src/theme/radius.ts +0 -18
- package/src/theme/resolve.test.ts +0 -238
- package/src/theme/resolve.ts +0 -96
- package/src/theme/spacing.ts +0 -18
- package/src/theme/storage.test.ts +0 -126
- package/src/theme/storage.ts +0 -106
- package/src/theme/theme-colors.ts +0 -88
- package/src/theme/types.ts +0 -125
- package/src/uploads/UploadAdapter.ts +0 -35
- package/src/uploads/index.ts +0 -2
- package/src/uploads/localUpload.test.ts +0 -70
- package/src/uploads/localUpload.ts +0 -84
- package/src/validation/Validator.ts +0 -49
- package/src/validation/index.ts +0 -28
- package/src/validation/rules.ts +0 -78
- package/src/validation/runValidators.ts +0 -435
- package/src/validation/uniqueValidator.test.ts +0 -196
- package/src/validation/uniqueValidator.ts +0 -133
- package/src/validation/validators.test.ts +0 -268
- package/src/vite.test.ts +0 -184
- package/src/vite.ts +0 -787
- package/src/widgets/index.ts +0 -10
- package/src/widgets/registry.ts +0 -45
- package/src/widgets.test.ts +0 -592
- package/tsconfig.build.json +0 -11
- package/tsconfig.json +0 -4
- package/tsconfig.test.json +0 -10
- package/views/react/Dashboard.tsx +0 -27
- package/views/react/Resources/Form.tsx +0 -102
- package/views/react/Resources/Index.tsx +0 -49
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
|
|
4
|
-
import { TagsInputField, TagsInput } from './TagsInputField.js'
|
|
5
|
-
|
|
6
|
-
describe('TagsInputField', () => {
|
|
7
|
-
it('emits fieldType "tagsInput"', async () => {
|
|
8
|
-
const meta = await TagsInputField.make('tags').toMeta()
|
|
9
|
-
assert.equal(meta.fieldType, 'tagsInput')
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('exports an alias `TagsInput`', () => {
|
|
13
|
-
assert.equal(TagsInput, TagsInputField)
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('default suggestions is an empty array', async () => {
|
|
17
|
-
const meta = await TagsInputField.make('tags').toMeta()
|
|
18
|
-
assert.deepEqual(meta['suggestions'], [])
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('omits separator/splitKeys/reorderable/maxTags by default', async () => {
|
|
22
|
-
const meta = await TagsInputField.make('tags').toMeta()
|
|
23
|
-
assert.equal('separator' in meta, false)
|
|
24
|
-
assert.equal('splitKeys' in meta, false)
|
|
25
|
-
assert.equal('reorderable' in meta, false)
|
|
26
|
-
assert.equal('maxTags' in meta, false)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
describe('suggestions(static array)', () => {
|
|
30
|
-
it('emits suggestions verbatim', async () => {
|
|
31
|
-
const meta = await TagsInputField.make('tags')
|
|
32
|
-
.suggestions(['react', 'vue', 'svelte'])
|
|
33
|
-
.toMeta()
|
|
34
|
-
assert.deepEqual(meta['suggestions'], ['react', 'vue', 'svelte'])
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('hasDynamicSuggestions is false for static arrays', () => {
|
|
38
|
-
const f = TagsInputField.make('tags').suggestions(['a'])
|
|
39
|
-
assert.equal(f.hasDynamicSuggestions(), false)
|
|
40
|
-
assert.deepEqual(f.getSuggestions(), ['a'])
|
|
41
|
-
})
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
describe('suggestions(resolver function)', () => {
|
|
45
|
-
it('runs the resolver against ctx', async () => {
|
|
46
|
-
const f = TagsInputField.make('tags').suggestions(({ $get }) => {
|
|
47
|
-
const stack = $get?.('stack') as string | undefined
|
|
48
|
-
if (stack === 'fe') return ['react', 'vue']
|
|
49
|
-
return ['node', 'rails']
|
|
50
|
-
})
|
|
51
|
-
assert.equal(f.hasDynamicSuggestions(), true)
|
|
52
|
-
const meta = await f.toMeta({
|
|
53
|
-
values: { stack: 'fe' },
|
|
54
|
-
$get: (n) => ({ stack: 'fe' } as Record<string, unknown>)[n],
|
|
55
|
-
})
|
|
56
|
-
assert.deepEqual(meta['suggestions'], ['react', 'vue'])
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('async resolver is awaited', async () => {
|
|
60
|
-
const f = TagsInputField.make('tags').suggestions(async () => {
|
|
61
|
-
await new Promise(r => setTimeout(r, 1))
|
|
62
|
-
return ['async']
|
|
63
|
-
})
|
|
64
|
-
const meta = await f.toMeta()
|
|
65
|
-
assert.deepEqual(meta['suggestions'], ['async'])
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('thrown resolver returns empty suggestions + console.warn', async () => {
|
|
69
|
-
const original = console.warn
|
|
70
|
-
const calls: unknown[] = []
|
|
71
|
-
console.warn = (...args: unknown[]) => { calls.push(args) }
|
|
72
|
-
try {
|
|
73
|
-
const f = TagsInputField.make('broken').suggestions(() => { throw new Error('boom') })
|
|
74
|
-
const meta = await f.toMeta()
|
|
75
|
-
assert.deepEqual(meta['suggestions'], [])
|
|
76
|
-
assert.equal(calls.length, 1)
|
|
77
|
-
} finally {
|
|
78
|
-
console.warn = original
|
|
79
|
-
}
|
|
80
|
-
})
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
describe('separator', () => {
|
|
84
|
-
it('emits when set to a non-default char', async () => {
|
|
85
|
-
const meta = await TagsInputField.make('tags').separator(';').toMeta()
|
|
86
|
-
assert.equal(meta['separator'], ';')
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('emits null when explicitly disabled', async () => {
|
|
90
|
-
const meta = await TagsInputField.make('tags').separator(null).toMeta()
|
|
91
|
-
assert.equal(meta['separator'], null)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('omits when left at the default ","', async () => {
|
|
95
|
-
const meta = await TagsInputField.make('tags').separator(',').toMeta()
|
|
96
|
-
assert.equal('separator' in meta, false)
|
|
97
|
-
})
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
describe('splitKeys', () => {
|
|
101
|
-
it('emits when overridden', async () => {
|
|
102
|
-
const meta = await TagsInputField.make('tags').splitKeys(['Enter', 'Tab']).toMeta()
|
|
103
|
-
assert.deepEqual(meta['splitKeys'], ['Enter', 'Tab'])
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('omits when left at the default ["Enter"]', async () => {
|
|
107
|
-
const meta = await TagsInputField.make('tags').splitKeys(['Enter']).toMeta()
|
|
108
|
-
assert.equal('splitKeys' in meta, false)
|
|
109
|
-
})
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
describe('reorderable', () => {
|
|
113
|
-
it('emits true when enabled', async () => {
|
|
114
|
-
const meta = await TagsInputField.make('tags').reorderable().toMeta()
|
|
115
|
-
assert.equal(meta['reorderable'], true)
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it('omits when explicitly disabled', async () => {
|
|
119
|
-
const meta = await TagsInputField.make('tags').reorderable(false).toMeta()
|
|
120
|
-
assert.equal('reorderable' in meta, false)
|
|
121
|
-
})
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
describe('maxTags', () => {
|
|
125
|
-
it('emits when set', async () => {
|
|
126
|
-
const meta = await TagsInputField.make('tags').maxTags(5).toMeta()
|
|
127
|
-
assert.equal(meta['maxTags'], 5)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('clamps to >= 1 and floors fractional values', () => {
|
|
131
|
-
const a = TagsInputField.make('a').maxTags(0)
|
|
132
|
-
const b = TagsInputField.make('b').maxTags(3.7)
|
|
133
|
-
assert.equal(a.getMaxTags(), 1)
|
|
134
|
-
assert.equal(b.getMaxTags(), 3)
|
|
135
|
-
})
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('participates in cross-field plumbing (default / required / live / helperText)', async () => {
|
|
139
|
-
const f = TagsInputField.make('tags')
|
|
140
|
-
.label('Tags')
|
|
141
|
-
.required()
|
|
142
|
-
.default(['draft'])
|
|
143
|
-
.helperText('Press Enter to add')
|
|
144
|
-
.live()
|
|
145
|
-
.suggestions(['draft', 'published'])
|
|
146
|
-
const meta = await f.toMeta()
|
|
147
|
-
assert.equal(meta.fieldType, 'tagsInput')
|
|
148
|
-
assert.equal(meta.required, true)
|
|
149
|
-
assert.equal(meta.label, 'Tags')
|
|
150
|
-
assert.equal(meta['helperText'], 'Press Enter to add')
|
|
151
|
-
assert.equal(meta['live'], true)
|
|
152
|
-
assert.deepEqual(meta['defaultValue'], ['draft'])
|
|
153
|
-
})
|
|
154
|
-
})
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { Field, type FieldMeta } from './Field.js'
|
|
2
|
-
import type { RenderContext } from '../schema/resolveSchema.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Suggestions resolver. Simpler shape than `OptionsResolver` because
|
|
6
|
-
* a tag is its own label — the suggestion list is `string[]`, not
|
|
7
|
-
* `{value,label}[]`. Layer SelectOption-shaped suggestions later if
|
|
8
|
-
* a real consumer needs label / value to diverge.
|
|
9
|
-
*/
|
|
10
|
-
export type TagsSuggestionsResolver = (ctx: {
|
|
11
|
-
$get?: (name: string) => unknown
|
|
12
|
-
values?: Record<string, unknown>
|
|
13
|
-
record?: unknown
|
|
14
|
-
user?: unknown
|
|
15
|
-
}) => string[] | Promise<string[]>
|
|
16
|
-
|
|
17
|
-
async function resolveSuggestions(
|
|
18
|
-
source: string[] | TagsSuggestionsResolver,
|
|
19
|
-
ctx: RenderContext | undefined,
|
|
20
|
-
fieldName: string,
|
|
21
|
-
): Promise<string[]> {
|
|
22
|
-
if (Array.isArray(source)) return source
|
|
23
|
-
try {
|
|
24
|
-
return await source({
|
|
25
|
-
...(ctx?.$get ? { $get: ctx.$get } : {}),
|
|
26
|
-
...(ctx?.values ? { values: ctx.values } : {}),
|
|
27
|
-
...(ctx?.record !== undefined ? { record: ctx.record } : {}),
|
|
28
|
-
...(ctx?.user !== undefined ? { user: ctx.user } : {}),
|
|
29
|
-
})
|
|
30
|
-
} catch (err) {
|
|
31
|
-
console.warn(`[pilotiq] suggestions() resolver for "${fieldName}" threw:`, err)
|
|
32
|
-
return []
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Free-text multi-tag input. Value is `string[]`. The client renders
|
|
38
|
-
* pill-shaped chips per committed tag plus a single inline text input;
|
|
39
|
-
* pressing Enter (or any of `splitKeys`) commits the current draft to
|
|
40
|
-
* the chip set. Pasting a string containing `separator` splits into
|
|
41
|
-
* multiple chips at once. Backspace on an empty draft removes the last
|
|
42
|
-
* chip.
|
|
43
|
-
*
|
|
44
|
-
* Wire format mirrors `KeyValue` — the client serializes the chip set
|
|
45
|
-
* as a JSON string in a single hidden input and `coerceFormValues`
|
|
46
|
-
* parses it back into `string[]`. Avoids the multi-input-with-same-name
|
|
47
|
-
* footguns across `application/x-www-form-urlencoded` and
|
|
48
|
-
* `multipart/form-data` bodies.
|
|
49
|
-
*/
|
|
50
|
-
export class TagsInputField extends Field {
|
|
51
|
-
private _suggestions: string[] | TagsSuggestionsResolver = []
|
|
52
|
-
private _separator: string | null = ','
|
|
53
|
-
private _splitKeys: string[] = ['Enter']
|
|
54
|
-
private _reorderable = false
|
|
55
|
-
private _maxTags: number | null = null
|
|
56
|
-
|
|
57
|
-
private constructor(name: string) {
|
|
58
|
-
super(name, 'tagsInput')
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
static make(name: string): TagsInputField {
|
|
62
|
-
return new TagsInputField(name)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
suggestions(opts: string[] | TagsSuggestionsResolver): this {
|
|
66
|
-
this._suggestions = opts
|
|
67
|
-
return this
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Single character that splits a pasted string into multiple tags.
|
|
72
|
-
* Pass `null` to disable paste-splitting; only `splitKeys` then
|
|
73
|
-
* commits a draft.
|
|
74
|
-
*/
|
|
75
|
-
separator(char: string | null): this {
|
|
76
|
-
this._separator = char
|
|
77
|
-
return this
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Keys that commit the current draft to the chip set. Defaults to
|
|
82
|
-
* `['Enter']`. Common addition: `['Enter', 'Tab', ',']`. The
|
|
83
|
-
* separator char is automatically treated as a split key as well.
|
|
84
|
-
*/
|
|
85
|
-
splitKeys(keys: string[]): this {
|
|
86
|
-
this._splitKeys = keys
|
|
87
|
-
return this
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** Allow chip reorder via native HTML5 drag-and-drop. Off by default — most
|
|
91
|
-
* tag sets are insertion-ordered or alphabetized, and the chip strip is small
|
|
92
|
-
* enough that drag affordances are noisy when unused. */
|
|
93
|
-
reorderable(value: boolean = true): this {
|
|
94
|
-
this._reorderable = value
|
|
95
|
-
return this
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** Cap the chip count. Surfaces to validation-renderer + client guard. */
|
|
99
|
-
maxTags(n: number): this {
|
|
100
|
-
this._maxTags = Math.max(1, Math.floor(n))
|
|
101
|
-
return this
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
hasDynamicSuggestions(): boolean {
|
|
105
|
-
return typeof this._suggestions === 'function'
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
getSuggestions(): string[] {
|
|
109
|
-
return Array.isArray(this._suggestions) ? this._suggestions : []
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
getSeparator(): string | null { return this._separator }
|
|
113
|
-
getSplitKeys(): string[] { return this._splitKeys }
|
|
114
|
-
isReorderable(): boolean { return this._reorderable }
|
|
115
|
-
getMaxTags(): number | null { return this._maxTags }
|
|
116
|
-
|
|
117
|
-
override async toMeta(ctx?: RenderContext): Promise<FieldMeta> {
|
|
118
|
-
const base = this.buildMeta(ctx)
|
|
119
|
-
const suggestions = await resolveSuggestions(this._suggestions, ctx, this.name)
|
|
120
|
-
return {
|
|
121
|
-
...base,
|
|
122
|
-
suggestions,
|
|
123
|
-
...(this._separator !== ',' ? { separator: this._separator } : {}),
|
|
124
|
-
...(this._splitKeys.length !== 1 || this._splitKeys[0] !== 'Enter'
|
|
125
|
-
? { splitKeys: this._splitKeys }
|
|
126
|
-
: {}),
|
|
127
|
-
...(this._reorderable ? { reorderable: true } : {}),
|
|
128
|
-
...(this._maxTags ? { maxTags: this._maxTags } : {}),
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export const TagsInput = TagsInputField
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
|
|
4
|
-
import { TextField } from './TextField.js'
|
|
5
|
-
import { Action } from '../actions/Action.js'
|
|
6
|
-
import { resolveSchema } from '../schema/resolveSchema.js'
|
|
7
|
-
import { coerceFormValues } from '../elements/dispatchForm.js'
|
|
8
|
-
import { formatWithMask } from '../react/fields/textInputControls.js'
|
|
9
|
-
|
|
10
|
-
describe('TextField rich affordances (audit gap #3)', () => {
|
|
11
|
-
describe('password / revealable', () => {
|
|
12
|
-
it('emits password + revealable flags only when set', () => {
|
|
13
|
-
const a = TextField.make('p').password().revealable().toMeta()
|
|
14
|
-
assert.equal(a['password'], true)
|
|
15
|
-
assert.equal(a['revealable'], true)
|
|
16
|
-
|
|
17
|
-
const b = TextField.make('p').toMeta()
|
|
18
|
-
assert.equal(b['password'], undefined)
|
|
19
|
-
assert.equal(b['revealable'], undefined)
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('disarms with explicit false', () => {
|
|
23
|
-
const a = TextField.make('p').password(false).toMeta()
|
|
24
|
-
assert.equal(a['password'], undefined)
|
|
25
|
-
})
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
describe('copyable', () => {
|
|
29
|
-
it('flag emits and message defaults are sparse', () => {
|
|
30
|
-
const a = TextField.make('x').copyable().toMeta()
|
|
31
|
-
assert.equal(a['copyable'], true)
|
|
32
|
-
assert.equal(a['copyMessage'], undefined)
|
|
33
|
-
|
|
34
|
-
const b = TextField.make('x').copyable('Got it').toMeta()
|
|
35
|
-
assert.equal(b['copyable'], true)
|
|
36
|
-
assert.equal(b['copyMessage'], 'Got it')
|
|
37
|
-
})
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
describe('mask', () => {
|
|
41
|
-
it('emits the pattern verbatim', () => {
|
|
42
|
-
const a = TextField.make('phone').mask('(999) 999-9999').toMeta()
|
|
43
|
-
assert.equal(a['mask'], '(999) 999-9999')
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
describe('datalist', () => {
|
|
48
|
-
it('emits a defensive copy of the values array', () => {
|
|
49
|
-
const values = ['gmail.com', 'outlook.com']
|
|
50
|
-
const a = TextField.make('email').datalist(values).toMeta()
|
|
51
|
-
const out = a['datalist'] as string[]
|
|
52
|
-
assert.deepEqual(out, values)
|
|
53
|
-
// Mutating the original after the fact must not leak in.
|
|
54
|
-
values.push('yahoo.com')
|
|
55
|
-
assert.deepEqual(a['datalist'], ['gmail.com', 'outlook.com'])
|
|
56
|
-
})
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
describe('stripCharacters', () => {
|
|
60
|
-
it('accepts a string of single chars', () => {
|
|
61
|
-
const a = TextField.make('phone').stripCharacters('()- ').toMeta()
|
|
62
|
-
assert.deepEqual(a['stripCharacters'], ['(', ')', '-', ' '])
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('accepts an explicit array', () => {
|
|
66
|
-
const a = TextField.make('phone').stripCharacters(['(', ')']).toMeta()
|
|
67
|
-
assert.deepEqual(a['stripCharacters'], ['(', ')'])
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('omits when empty', () => {
|
|
71
|
-
const a = TextField.make('x').stripCharacters('').toMeta()
|
|
72
|
-
assert.equal(a['stripCharacters'], undefined)
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('strips configured chars during coerce', () => {
|
|
76
|
-
const f = TextField.make('phone').stripCharacters('()- ')
|
|
77
|
-
const out = coerceFormValues([f], { phone: '(415) 555-1212' })
|
|
78
|
-
assert.equal(out['phone'], '4155551212')
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('coerce no-ops when not configured', () => {
|
|
82
|
-
const f = TextField.make('plain')
|
|
83
|
-
const out = coerceFormValues([f], { plain: 'a-b-c' })
|
|
84
|
-
assert.equal(out['plain'], 'a-b-c')
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('coerce skips non-string values', () => {
|
|
88
|
-
const f = TextField.make('plain').stripCharacters('-')
|
|
89
|
-
const out = coerceFormValues([f], { plain: 42 as unknown as string })
|
|
90
|
-
assert.equal(out['plain'], 42)
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
describe('trim', () => {
|
|
95
|
-
it('emits the flag only when set', () => {
|
|
96
|
-
const a = TextField.make('x').trim().toMeta()
|
|
97
|
-
assert.equal(a['trim'], true)
|
|
98
|
-
|
|
99
|
-
const b = TextField.make('x').toMeta()
|
|
100
|
-
assert.equal(b['trim'], undefined)
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it('disarms with explicit false', () => {
|
|
104
|
-
const a = TextField.make('x').trim(false).toMeta()
|
|
105
|
-
assert.equal(a['trim'], undefined)
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('strips leading and trailing whitespace during coerce', () => {
|
|
109
|
-
const f = TextField.make('email').trim()
|
|
110
|
-
const out = coerceFormValues([f], { email: ' user@example.com\n' })
|
|
111
|
-
assert.equal(out['email'], 'user@example.com')
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('coerce no-ops when not configured', () => {
|
|
115
|
-
const f = TextField.make('plain')
|
|
116
|
-
const out = coerceFormValues([f], { plain: ' spaced ' })
|
|
117
|
-
assert.equal(out['plain'], ' spaced ')
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('coerce skips non-string values', () => {
|
|
121
|
-
const f = TextField.make('x').trim()
|
|
122
|
-
const out = coerceFormValues([f], { x: 42 as unknown as string })
|
|
123
|
-
assert.equal(out['x'], 42)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('runs before stripCharacters when both are set', () => {
|
|
127
|
-
const f = TextField.make('phone').trim().stripCharacters('()- ')
|
|
128
|
-
const out = coerceFormValues([f], { phone: ' (415) 555-1212 ' })
|
|
129
|
-
assert.equal(out['phone'], '4155551212')
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('preserves empty strings', () => {
|
|
133
|
-
const f = TextField.make('x').trim()
|
|
134
|
-
const out = coerceFormValues([f], { x: ' ' })
|
|
135
|
-
assert.equal(out['x'], '')
|
|
136
|
-
})
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
describe('inputMode + autocapitalize', () => {
|
|
140
|
-
it('emits each attribute when set', () => {
|
|
141
|
-
const a = TextField.make('q').inputMode('search').autocapitalize('off').toMeta()
|
|
142
|
-
assert.equal(a['inputMode'], 'search')
|
|
143
|
-
assert.equal(a['autocapitalize'], 'off')
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('omits each attribute when unset', () => {
|
|
147
|
-
const a = TextField.make('q').toMeta()
|
|
148
|
-
assert.equal(a['inputMode'], undefined)
|
|
149
|
-
assert.equal(a['autocapitalize'], undefined)
|
|
150
|
-
})
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
describe('prefixAction / suffixAction', () => {
|
|
154
|
-
it('resolves bound Actions through resolveSchema as ActionMetas', async () => {
|
|
155
|
-
const result = await resolveSchema([
|
|
156
|
-
TextField.make('apiKey')
|
|
157
|
-
.prefixAction(Action.make('generate').icon('plus'))
|
|
158
|
-
.suffixAction(Action.make('rotate').icon('refresh')),
|
|
159
|
-
])
|
|
160
|
-
const meta = result[0]!
|
|
161
|
-
const pre = meta['prefixAction'] as Record<string, unknown> | undefined
|
|
162
|
-
const suf = meta['suffixAction'] as Record<string, unknown> | undefined
|
|
163
|
-
assert.equal(pre?.['type'], 'action')
|
|
164
|
-
assert.equal(pre?.['name'], 'generate')
|
|
165
|
-
assert.equal(suf?.['type'], 'action')
|
|
166
|
-
assert.equal(suf?.['name'], 'rotate')
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
it('drops a hidden Action from the slot', async () => {
|
|
170
|
-
const result = await resolveSchema([
|
|
171
|
-
TextField.make('q').prefixAction(Action.make('hide').visible(false)),
|
|
172
|
-
])
|
|
173
|
-
assert.equal(result[0]!['prefixAction'], undefined)
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('omits the slots when not configured', async () => {
|
|
177
|
-
const result = await resolveSchema([TextField.make('q')])
|
|
178
|
-
assert.equal(result[0]!['prefixAction'], undefined)
|
|
179
|
-
assert.equal(result[0]!['suffixAction'], undefined)
|
|
180
|
-
})
|
|
181
|
-
})
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
describe('formatWithMask (client mask helper)', () => {
|
|
185
|
-
it('formats a US phone via the documented alphabet', () => {
|
|
186
|
-
assert.equal(formatWithMask('4155551212', '(999) 999-9999'), '(415) 555-1212')
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
it('emits literals even with no remaining input', () => {
|
|
190
|
-
assert.equal(formatWithMask('415', '(999) 999-9999'), '(415) ')
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
it('skips characters that do not match the token kind', () => {
|
|
194
|
-
assert.equal(formatWithMask('a4b1c5', '999'), '415')
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('handles alpha tokens', () => {
|
|
198
|
-
assert.equal(formatWithMask('xy12', 'aa-99'), 'xy-12')
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
it('any-token (*) accepts any character', () => {
|
|
202
|
-
assert.equal(formatWithMask('a1b2', '****'), 'a1b2')
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
it('does NOT double-emit literals already typed by the user', () => {
|
|
206
|
-
assert.equal(formatWithMask('(415)5551212', '(999) 999-9999'), '(415) 555-1212')
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
it('returns input unchanged when mask is empty', () => {
|
|
210
|
-
assert.equal(formatWithMask('hello', ''), '')
|
|
211
|
-
})
|
|
212
|
-
})
|
|
213
|
-
|
package/src/fields/TextField.ts
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { Field, type FieldMeta } from './Field.js'
|
|
2
|
-
import type { RenderContext } from '../schema/resolveSchema.js'
|
|
3
|
-
import type { Action } from '../actions/Action.js'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* HTML5 `inputmode` values — drive the on-screen keyboard mobile browsers
|
|
7
|
-
* pop up. `'text'` is the default; the others map 1:1 to spec values.
|
|
8
|
-
*/
|
|
9
|
-
export type TextInputMode =
|
|
10
|
-
| 'none' | 'text' | 'numeric' | 'tel' | 'email' | 'decimal' | 'search' | 'url'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* HTML5 `autocapitalize` values — control which characters mobile virtual
|
|
14
|
-
* keyboards capitalize automatically. `'off'` and `'none'` are aliases per
|
|
15
|
-
* spec; we surface only `'off'` for clarity.
|
|
16
|
-
*/
|
|
17
|
-
export type TextAutocapitalize = 'off' | 'sentences' | 'words' | 'characters'
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Mask alphabet documented for `mask(pattern)`:
|
|
21
|
-
* `9` — digit (0-9)
|
|
22
|
-
* `a` — alpha (A-Za-z)
|
|
23
|
-
* `*` — alphanumeric or any character
|
|
24
|
-
* anything else — literal (rendered verbatim, skipped on input)
|
|
25
|
-
*
|
|
26
|
-
* Examples: `'(999) 999-9999'` (US phone), `'9999-9999-9999-9999'` (card),
|
|
27
|
-
* `'aaa-9999'` (custom). The renderer formats values keystroke-by-keystroke
|
|
28
|
-
* and strips literals from the submitted value so the persisted column
|
|
29
|
-
* stores the raw digits/letters.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
export class TextField extends Field {
|
|
33
|
-
private _maxLength?: number
|
|
34
|
-
private _password = false
|
|
35
|
-
private _revealable = false
|
|
36
|
-
private _copyable = false
|
|
37
|
-
private _copyMessage?: string
|
|
38
|
-
private _mask?: string
|
|
39
|
-
private _datalist?: string[]
|
|
40
|
-
private _stripCharacters?: string[]
|
|
41
|
-
private _trim = false
|
|
42
|
-
private _inputMode?: TextInputMode
|
|
43
|
-
private _autocapitalize?: TextAutocapitalize
|
|
44
|
-
private _prefixAction?: Action
|
|
45
|
-
private _suffixAction?: Action
|
|
46
|
-
|
|
47
|
-
private constructor(name: string) {
|
|
48
|
-
super(name, 'text')
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
static make(name: string): TextField {
|
|
52
|
-
return new TextField(name)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
maxLength(n: number): this { this._maxLength = n; return this }
|
|
56
|
-
getMaxLength(): number | undefined { return this._maxLength }
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Render the input as `type="password"`. Pair with `revealable()` to
|
|
60
|
-
* surface an eye-icon toggle. Pure presentation — the column type +
|
|
61
|
-
* value handling stays string-shaped.
|
|
62
|
-
*/
|
|
63
|
-
password(v: boolean = true): this { this._password = v; return this }
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Mount an eye-icon toggle in the suffix slot that flips the input
|
|
67
|
-
* type between `password` and `text`. No-op when the field is not in
|
|
68
|
-
* `password()` mode (the toggle hides itself client-side so a stray
|
|
69
|
-
* `revealable()` on a non-password input doesn't render a useless
|
|
70
|
-
* button).
|
|
71
|
-
*/
|
|
72
|
-
revealable(v: boolean = true): this { this._revealable = v; return this }
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Mount a copy button in the suffix slot that writes the current input
|
|
76
|
-
* value to the clipboard. The optional message overrides the default
|
|
77
|
-
* `'Copied!'` toast text — same convention as
|
|
78
|
-
* `Column.copyMessage()`.
|
|
79
|
-
*/
|
|
80
|
-
copyable(message?: string): this {
|
|
81
|
-
this._copyable = true
|
|
82
|
-
if (message !== undefined) this._copyMessage = message
|
|
83
|
-
return this
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Format the input keystroke-by-keystroke against the supplied mask
|
|
88
|
-
* pattern. See `TextField` doc-block for the alphabet (`9` = digit,
|
|
89
|
-
* `a` = alpha, `*` = any, literals passthrough). Submitted values are
|
|
90
|
-
* stripped of literal characters before the form body lands on the
|
|
91
|
-
* server — the persisted column stores the raw chars only.
|
|
92
|
-
*/
|
|
93
|
-
mask(pattern: string): this { this._mask = pattern; return this }
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Suggest values via a native HTML5 `<datalist>` attached to the
|
|
97
|
-
* input. Browsers render an autocomplete dropdown; users can still
|
|
98
|
-
* type a value not on the list. Useful for canonical-but-not-exclusive
|
|
99
|
-
* sets (countries, departments, common email domains).
|
|
100
|
-
*/
|
|
101
|
-
datalist(values: string[]): this { this._datalist = values.slice(); return this }
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Strip the listed characters from the submitted value before
|
|
105
|
-
* validation runs. Pass a single string (each char becomes a strip
|
|
106
|
-
* token) or an array of strings. Useful for input-mask-style fields
|
|
107
|
-
* where the persisted column should not carry the mask literals
|
|
108
|
-
* (`'(' / ')' / '-' / ' '` for phone numbers, `' '` for credit cards).
|
|
109
|
-
* The strip applies on both create and edit — server-side authority,
|
|
110
|
-
* so a tampered client still gets cleaned values.
|
|
111
|
-
*/
|
|
112
|
-
stripCharacters(chars: string | string[]): this {
|
|
113
|
-
const list = typeof chars === 'string' ? Array.from(chars) : [...chars]
|
|
114
|
-
if (list.length === 0) delete this._stripCharacters
|
|
115
|
-
else this._stripCharacters = list
|
|
116
|
-
return this
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
getStripCharacters(): string[] | undefined { return this._stripCharacters }
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Strip leading and trailing whitespace from the submitted value
|
|
123
|
-
* before validation runs. Mirrors Laravel's `TrimStrings` middleware
|
|
124
|
-
* — server-side authority, so a tampered client still gets trimmed
|
|
125
|
-
* values. Composes with `stripCharacters()` (trim runs first). Empty
|
|
126
|
-
* strings remain empty; non-string values pass through.
|
|
127
|
-
*/
|
|
128
|
-
trim(v: boolean = true): this { this._trim = v; return this }
|
|
129
|
-
|
|
130
|
-
getTrim(): boolean { return this._trim }
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Set the HTML `inputmode` attribute — drives the virtual-keyboard
|
|
134
|
-
* layout on mobile. Distinct from `type=` (a `text` field with
|
|
135
|
-
* `inputMode('numeric')` still accepts non-digit pastes; for strict
|
|
136
|
-
* numeric-only, use `NumberField` instead).
|
|
137
|
-
*/
|
|
138
|
-
inputMode(mode: TextInputMode): this { this._inputMode = mode; return this }
|
|
139
|
-
|
|
140
|
-
/** Set the HTML `autocapitalize` attribute. */
|
|
141
|
-
autocapitalize(mode: TextAutocapitalize): this { this._autocapitalize = mode; return this }
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Mount a clickable Action button in the prefix slot of the input
|
|
145
|
-
* shell. Distinct from the passive `prefix()` decoration (which is a
|
|
146
|
-
* string or icon descriptor only). Use this for an in-input affordance
|
|
147
|
-
* — e.g. an OAuth-style "Generate" button next to an API key field.
|
|
148
|
-
* The Action retains its full chrome (`.icon() / .color() / .visible()
|
|
149
|
-
* / .modal*()`); visibility rules evaluate through the standard schema
|
|
150
|
-
* walker the same way they do anywhere else.
|
|
151
|
-
*/
|
|
152
|
-
prefixAction(action: Action): this { this._prefixAction = action; return this }
|
|
153
|
-
|
|
154
|
-
/** Suffix-slot variant of `prefixAction`. Same semantics. */
|
|
155
|
-
suffixAction(action: Action): this { this._suffixAction = action; return this }
|
|
156
|
-
|
|
157
|
-
/** Read-only access for the resolver. */
|
|
158
|
-
getPrefixAction(): Action | undefined { return this._prefixAction }
|
|
159
|
-
getSuffixAction(): Action | undefined { return this._suffixAction }
|
|
160
|
-
|
|
161
|
-
override toMeta(ctx?: RenderContext): FieldMeta {
|
|
162
|
-
return {
|
|
163
|
-
...this.buildMeta(ctx),
|
|
164
|
-
...(this._maxLength !== undefined ? { maxLength: this._maxLength } : {}),
|
|
165
|
-
...(this._password ? { password: true } : {}),
|
|
166
|
-
...(this._revealable ? { revealable: true } : {}),
|
|
167
|
-
...(this._copyable ? { copyable: true } : {}),
|
|
168
|
-
...(this._copyMessage !== undefined ? { copyMessage: this._copyMessage } : {}),
|
|
169
|
-
...(this._mask !== undefined ? { mask: this._mask } : {}),
|
|
170
|
-
...(this._datalist !== undefined ? { datalist: this._datalist } : {}),
|
|
171
|
-
...(this._stripCharacters!== undefined ? { stripCharacters:this._stripCharacters} : {}),
|
|
172
|
-
...(this._trim ? { trim: true } : {}),
|
|
173
|
-
...(this._inputMode !== undefined ? { inputMode: this._inputMode } : {}),
|
|
174
|
-
...(this._autocapitalize !== undefined ? { autocapitalize: this._autocapitalize } : {}),
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|