@atmos.build/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/dist/components/alert-dialog.d.ts +14 -0
- package/dist/components/alert-dialog.js +40 -0
- package/dist/components/at-search.d.ts +35 -0
- package/dist/components/at-search.js +57 -0
- package/dist/components/attachment-thumbnail-stack.d.ts +13 -0
- package/dist/components/attachment-thumbnail-stack.js +23 -0
- package/dist/components/audio-preview.d.ts +33 -0
- package/dist/components/audio-preview.js +176 -0
- package/dist/components/avatar-uploader.d.ts +29 -0
- package/dist/components/avatar-uploader.js +67 -0
- package/dist/components/avatar.d.ts +12 -0
- package/dist/components/avatar.js +38 -0
- package/dist/components/badge.d.ts +9 -0
- package/dist/components/badge.js +25 -0
- package/dist/components/button-group.d.ts +12 -0
- package/dist/components/button-group.js +29 -0
- package/dist/components/button.d.ts +10 -0
- package/dist/components/button.js +42 -0
- package/dist/components/calendar.d.ts +26 -0
- package/dist/components/calendar.js +154 -0
- package/dist/components/channel-list.d.ts +33 -0
- package/dist/components/channel-list.js +40 -0
- package/dist/components/charts/area-chart.d.ts +2 -0
- package/dist/components/charts/area-chart.js +5 -0
- package/dist/components/charts/bar-chart.d.ts +2 -0
- package/dist/components/charts/bar-chart.js +58 -0
- package/dist/components/charts/colors.d.ts +2 -0
- package/dist/components/charts/colors.js +42 -0
- package/dist/components/charts/hover.d.ts +20 -0
- package/dist/components/charts/hover.js +31 -0
- package/dist/components/charts/index.d.ts +10 -0
- package/dist/components/charts/index.js +9 -0
- package/dist/components/charts/layout.d.ts +35 -0
- package/dist/components/charts/layout.js +101 -0
- package/dist/components/charts/legend.d.ts +6 -0
- package/dist/components/charts/legend.js +7 -0
- package/dist/components/charts/line-area-chart.d.ts +4 -0
- package/dist/components/charts/line-area-chart.js +86 -0
- package/dist/components/charts/line-chart.d.ts +2 -0
- package/dist/components/charts/line-chart.js +5 -0
- package/dist/components/charts/pie-chart.d.ts +2 -0
- package/dist/components/charts/pie-chart.js +22 -0
- package/dist/components/charts/radar-chart.d.ts +2 -0
- package/dist/components/charts/radar-chart.js +29 -0
- package/dist/components/charts/radial-chart.d.ts +2 -0
- package/dist/components/charts/radial-chart.js +19 -0
- package/dist/components/charts/scatter-chart.d.ts +2 -0
- package/dist/components/charts/scatter-chart.js +16 -0
- package/dist/components/charts/story-data.d.ts +24 -0
- package/dist/components/charts/story-data.js +48 -0
- package/dist/components/charts/types.d.ts +43 -0
- package/dist/components/charts/types.js +1 -0
- package/dist/components/checkbox.d.ts +4 -0
- package/dist/components/checkbox.js +10 -0
- package/dist/components/chip.d.ts +25 -0
- package/dist/components/chip.js +36 -0
- package/dist/components/code-editor-theme.d.ts +19 -0
- package/dist/components/code-editor-theme.js +100 -0
- package/dist/components/collapsible-section-header.d.ts +82 -0
- package/dist/components/collapsible-section-header.js +82 -0
- package/dist/components/collection-view/collection-view-group-settings.d.ts +73 -0
- package/dist/components/collection-view/collection-view-group-settings.js +55 -0
- package/dist/components/collection-view/collection-view-settings.d.ts +115 -0
- package/dist/components/collection-view/collection-view-settings.js +146 -0
- package/dist/components/collection-view/collection-view.d.ts +87 -0
- package/dist/components/collection-view/collection-view.js +95 -0
- package/dist/components/collection-view/index.d.ts +6 -0
- package/dist/components/collection-view/index.js +6 -0
- package/dist/components/collection-view/use-collection-detail-navigation.d.ts +11 -0
- package/dist/components/collection-view/use-collection-detail-navigation.js +12 -0
- package/dist/components/collection-view/use-collection-escape.d.ts +9 -0
- package/dist/components/collection-view/use-collection-escape.js +20 -0
- package/dist/components/collection-view/use-collection-selection.d.ts +42 -0
- package/dist/components/collection-view/use-collection-selection.js +203 -0
- package/dist/components/color-picker.d.ts +18 -0
- package/dist/components/color-picker.js +77 -0
- package/dist/components/color-swatch-list.d.ts +16 -0
- package/dist/components/color-swatch-list.js +60 -0
- package/dist/components/combobox/combobox.d.ts +4 -0
- package/dist/components/combobox/combobox.js +70 -0
- package/dist/components/combobox/compound.d.ts +68 -0
- package/dist/components/combobox/compound.js +151 -0
- package/dist/components/combobox/context.d.ts +34 -0
- package/dist/components/combobox/context.js +123 -0
- package/dist/components/combobox/create-step.d.ts +12 -0
- package/dist/components/combobox/create-step.js +8 -0
- package/dist/components/combobox/index.d.ts +10 -0
- package/dist/components/combobox/index.js +5 -0
- package/dist/components/combobox/inline-create-form.d.ts +24 -0
- package/dist/components/combobox/inline-create-form.js +43 -0
- package/dist/components/combobox/types.d.ts +57 -0
- package/dist/components/combobox/types.js +10 -0
- package/dist/components/command.d.ts +35 -0
- package/dist/components/command.js +76 -0
- package/dist/components/comment-pin.d.ts +13 -0
- package/dist/components/comment-pin.js +33 -0
- package/dist/components/confidence-meter.d.ts +11 -0
- package/dist/components/confidence-meter.js +24 -0
- package/dist/components/context-menu.d.ts +31 -0
- package/dist/components/context-menu.js +54 -0
- package/dist/components/copy-button.d.ts +12 -0
- package/dist/components/copy-button.js +32 -0
- package/dist/components/date-picker.d.ts +29 -0
- package/dist/components/date-picker.js +86 -0
- package/dist/components/date-tile.d.ts +11 -0
- package/dist/components/date-tile.js +16 -0
- package/dist/components/date-time-picker.d.ts +25 -0
- package/dist/components/date-time-picker.js +153 -0
- package/dist/components/detail-breadcrumb-utils.d.ts +2 -0
- package/dist/components/detail-breadcrumb-utils.js +18 -0
- package/dist/components/detail-nav-bar.d.ts +28 -0
- package/dist/components/detail-nav-bar.js +28 -0
- package/dist/components/detail-top-bar.d.ts +49 -0
- package/dist/components/detail-top-bar.js +41 -0
- package/dist/components/dialog.d.ts +22 -0
- package/dist/components/dialog.js +51 -0
- package/dist/components/dropdown-menu.d.ts +29 -0
- package/dist/components/dropdown-menu.js +52 -0
- package/dist/components/editable-doc-title.d.ts +17 -0
- package/dist/components/editable-doc-title.js +147 -0
- package/dist/components/empty-state.d.ts +12 -0
- package/dist/components/empty-state.js +6 -0
- package/dist/components/filter-condition-editor.d.ts +18 -0
- package/dist/components/filter-condition-editor.js +102 -0
- package/dist/components/form-dialog.d.ts +30 -0
- package/dist/components/form-dialog.js +52 -0
- package/dist/components/formula-editor.d.ts +18 -0
- package/dist/components/formula-editor.js +154 -0
- package/dist/components/hover-card.d.ts +52 -0
- package/dist/components/hover-card.js +51 -0
- package/dist/components/icon-button.d.ts +15 -0
- package/dist/components/icon-button.js +18 -0
- package/dist/components/inline-alert.d.ts +11 -0
- package/dist/components/inline-alert.js +17 -0
- package/dist/components/input.d.ts +3 -0
- package/dist/components/input.js +7 -0
- package/dist/components/kanban-board.d.ts +27 -0
- package/dist/components/kanban-board.js +145 -0
- package/dist/components/kbd.d.ts +3 -0
- package/dist/components/kbd.js +8 -0
- package/dist/components/label.d.ts +4 -0
- package/dist/components/label.js +9 -0
- package/dist/components/markdown-editor/index.d.ts +4 -0
- package/dist/components/markdown-editor/index.js +2 -0
- package/dist/components/markdown-editor/markdown-editor.d.ts +16 -0
- package/dist/components/markdown-editor/markdown-editor.js +53 -0
- package/dist/components/markdown-editor/serialize.d.ts +12 -0
- package/dist/components/markdown-editor/serialize.js +122 -0
- package/dist/components/markdown-editor/vim.d.ts +8 -0
- package/dist/components/markdown-editor/vim.js +394 -0
- package/dist/components/metric-grid.d.ts +41 -0
- package/dist/components/metric-grid.js +39 -0
- package/dist/components/microphone-priority.d.ts +29 -0
- package/dist/components/microphone-priority.js +15 -0
- package/dist/components/middle-truncate.d.ts +6 -0
- package/dist/components/middle-truncate.js +15 -0
- package/dist/components/multi-combobox.d.ts +48 -0
- package/dist/components/multi-combobox.js +221 -0
- package/dist/components/naut-avatar-engine.d.ts +83 -0
- package/dist/components/naut-avatar-engine.js +803 -0
- package/dist/components/naut-avatar.d.ts +31 -0
- package/dist/components/naut-avatar.js +108 -0
- package/dist/components/navigable-list.d.ts +4 -0
- package/dist/components/navigable-list.js +20 -0
- package/dist/components/note-icon-picker.d.ts +20 -0
- package/dist/components/note-icon-picker.js +84 -0
- package/dist/components/note-icon.d.ts +690 -0
- package/dist/components/note-icon.js +353 -0
- package/dist/components/note-row.d.ts +33 -0
- package/dist/components/note-row.js +29 -0
- package/dist/components/overlapping-dots.d.ts +10 -0
- package/dist/components/overlapping-dots.js +13 -0
- package/dist/components/overview-card.d.ts +12 -0
- package/dist/components/overview-card.js +9 -0
- package/dist/components/panel-header.d.ts +8 -0
- package/dist/components/panel-header.js +6 -0
- package/dist/components/panel-section-states.d.ts +9 -0
- package/dist/components/panel-section-states.js +10 -0
- package/dist/components/person-avatar.d.ts +10 -0
- package/dist/components/person-avatar.js +23 -0
- package/dist/components/photo-avatar.d.ts +11 -0
- package/dist/components/photo-avatar.js +24 -0
- package/dist/components/picker-field.d.ts +23 -0
- package/dist/components/picker-field.js +24 -0
- package/dist/components/popover-stack.d.ts +26 -0
- package/dist/components/popover-stack.js +89 -0
- package/dist/components/popover.d.ts +12 -0
- package/dist/components/popover.js +31 -0
- package/dist/components/portal-login-barrier.d.ts +12 -0
- package/dist/components/portal-login-barrier.js +41 -0
- package/dist/components/presence-dot.d.ts +8 -0
- package/dist/components/presence-dot.js +15 -0
- package/dist/components/presence-overlay.d.ts +27 -0
- package/dist/components/presence-overlay.js +34 -0
- package/dist/components/property-list.d.ts +64 -0
- package/dist/components/property-list.js +313 -0
- package/dist/components/property-panel.d.ts +54 -0
- package/dist/components/property-panel.js +87 -0
- package/dist/components/property-selector.d.ts +36 -0
- package/dist/components/property-selector.js +141 -0
- package/dist/components/provenance-chip.d.ts +12 -0
- package/dist/components/provenance-chip.js +17 -0
- package/dist/components/qr-code.d.ts +13 -0
- package/dist/components/qr-code.js +42 -0
- package/dist/components/qr-encode.d.ts +2 -0
- package/dist/components/qr-encode.js +469 -0
- package/dist/components/quantity-stepper.d.ts +25 -0
- package/dist/components/quantity-stepper.js +43 -0
- package/dist/components/radio-group.d.ts +5 -0
- package/dist/components/radio-group.js +13 -0
- package/dist/components/rename-dialog.d.ts +19 -0
- package/dist/components/rename-dialog.js +35 -0
- package/dist/components/row-surface.d.ts +1 -0
- package/dist/components/row-surface.js +5 -0
- package/dist/components/row-trailing.d.ts +30 -0
- package/dist/components/row-trailing.js +59 -0
- package/dist/components/run-status-badge.d.ts +16 -0
- package/dist/components/run-status-badge.js +43 -0
- package/dist/components/scroll-area.d.ts +5 -0
- package/dist/components/scroll-area.js +12 -0
- package/dist/components/select.d.ts +15 -0
- package/dist/components/select.js +39 -0
- package/dist/components/selectable-card.d.ts +33 -0
- package/dist/components/selectable-card.js +66 -0
- package/dist/components/separator.d.ts +3 -0
- package/dist/components/separator.js +8 -0
- package/dist/components/settings-dialog-shell.d.ts +46 -0
- package/dist/components/settings-dialog-shell.js +48 -0
- package/dist/components/shimmer-text.d.ts +8 -0
- package/dist/components/shimmer-text.js +26 -0
- package/dist/components/side-panel-tokens.d.ts +27 -0
- package/dist/components/side-panel-tokens.js +26 -0
- package/dist/components/side-panel.d.ts +94 -0
- package/dist/components/side-panel.js +106 -0
- package/dist/components/skeleton.d.ts +2 -0
- package/dist/components/skeleton.js +6 -0
- package/dist/components/sortable.d.ts +65 -0
- package/dist/components/sortable.js +110 -0
- package/dist/components/spec-strip.d.ts +18 -0
- package/dist/components/spec-strip.js +15 -0
- package/dist/components/speech-playback.d.ts +25 -0
- package/dist/components/speech-playback.js +35 -0
- package/dist/components/sql-editor.d.ts +13 -0
- package/dist/components/sql-editor.js +228 -0
- package/dist/components/stat-tile.d.ts +7 -0
- package/dist/components/stat-tile.js +6 -0
- package/dist/components/status-badge.d.ts +17 -0
- package/dist/components/status-badge.js +8 -0
- package/dist/components/status-tone.d.ts +9 -0
- package/dist/components/status-tone.js +10 -0
- package/dist/components/sticky-header-fade.d.ts +8 -0
- package/dist/components/sticky-header-fade.js +11 -0
- package/dist/components/swatch-button.d.ts +14 -0
- package/dist/components/swatch-button.js +25 -0
- package/dist/components/switch.d.ts +6 -0
- package/dist/components/switch.js +9 -0
- package/dist/components/system-load/index.d.ts +6 -0
- package/dist/components/system-load/index.js +6 -0
- package/dist/components/system-load/load-block.d.ts +20 -0
- package/dist/components/system-load/load-block.js +23 -0
- package/dist/components/system-load/load-grid.d.ts +16 -0
- package/dist/components/system-load/load-grid.js +23 -0
- package/dist/components/system-load/load-meter.d.ts +25 -0
- package/dist/components/system-load/load-meter.js +29 -0
- package/dist/components/system-load/load-tone.d.ts +21 -0
- package/dist/components/system-load/load-tone.js +29 -0
- package/dist/components/system-load/meter-row.d.ts +31 -0
- package/dist/components/system-load/meter-row.js +29 -0
- package/dist/components/system-load/resource-meter.d.ts +19 -0
- package/dist/components/system-load/resource-meter.js +15 -0
- package/dist/components/tab-title-bar.d.ts +12 -0
- package/dist/components/tab-title-bar.js +9 -0
- package/dist/components/table.d.ts +10 -0
- package/dist/components/table.js +29 -0
- package/dist/components/tabs.d.ts +11 -0
- package/dist/components/tabs.js +97 -0
- package/dist/components/template-chip-input.d.ts +36 -0
- package/dist/components/template-chip-input.js +271 -0
- package/dist/components/template-input.d.ts +24 -0
- package/dist/components/template-input.js +57 -0
- package/dist/components/terminal-spinner.d.ts +11 -0
- package/dist/components/terminal-spinner.js +48 -0
- package/dist/components/textarea.d.ts +3 -0
- package/dist/components/textarea.js +7 -0
- package/dist/components/time-column.d.ts +24 -0
- package/dist/components/time-column.js +60 -0
- package/dist/components/timestamped-transcript.d.ts +38 -0
- package/dist/components/timestamped-transcript.js +29 -0
- package/dist/components/timezone-picker.d.ts +22 -0
- package/dist/components/timezone-picker.js +60 -0
- package/dist/components/tooltip.d.ts +11 -0
- package/dist/components/tooltip.js +29 -0
- package/dist/components/transcription-overlay.d.ts +47 -0
- package/dist/components/transcription-overlay.js +30 -0
- package/dist/components/usage-bar.d.ts +17 -0
- package/dist/components/usage-bar.js +26 -0
- package/dist/components/view-query-bar.d.ts +13 -0
- package/dist/components/view-query-bar.js +13 -0
- package/dist/components/view-query-controls.d.ts +159 -0
- package/dist/components/view-query-controls.js +109 -0
- package/dist/components/view-save-menu.d.ts +20 -0
- package/dist/components/view-save-menu.js +13 -0
- package/dist/components/view-sort-editor.d.ts +40 -0
- package/dist/components/view-sort-editor.js +47 -0
- package/dist/components/voice-line.d.ts +37 -0
- package/dist/components/voice-line.js +293 -0
- package/dist/components/weekly-schedule-editor.d.ts +15 -0
- package/dist/components/weekly-schedule-editor.js +28 -0
- package/dist/flow/canvas/canvas-frame.d.ts +160 -0
- package/dist/flow/canvas/canvas-frame.js +521 -0
- package/dist/flow/canvas/collaborator-layer.d.ts +12 -0
- package/dist/flow/canvas/collaborator-layer.js +43 -0
- package/dist/flow/canvas/connector-edge.d.ts +22 -0
- package/dist/flow/canvas/connector-edge.js +61 -0
- package/dist/flow/canvas/editor-context.d.ts +23 -0
- package/dist/flow/canvas/editor-context.js +6 -0
- package/dist/flow/canvas/graph-core.d.ts +60 -0
- package/dist/flow/canvas/graph-core.js +296 -0
- package/dist/flow/canvas/minimap-sticky-overlay.d.ts +4 -0
- package/dist/flow/canvas/minimap-sticky-overlay.js +45 -0
- package/dist/flow/canvas/node-card.d.ts +46 -0
- package/dist/flow/canvas/node-card.js +56 -0
- package/dist/flow/canvas/node-library.d.ts +40 -0
- package/dist/flow/canvas/node-library.js +54 -0
- package/dist/flow/canvas/sticky-notes.d.ts +25 -0
- package/dist/flow/canvas/sticky-notes.js +101 -0
- package/dist/flow/collaborative-graph-presence.d.ts +28 -0
- package/dist/flow/collaborative-graph-presence.js +55 -0
- package/dist/flow/collaborative-graph-sync.d.ts +40 -0
- package/dist/flow/collaborative-graph-sync.js +127 -0
- package/dist/flow/editor/adapter.d.ts +64 -0
- package/dist/flow/editor/adapter.js +1 -0
- package/dist/flow/editor/command-registry.d.ts +10 -0
- package/dist/flow/editor/command-registry.js +35 -0
- package/dist/flow/editor/dag-canvas.d.ts +18 -0
- package/dist/flow/editor/dag-canvas.js +33 -0
- package/dist/flow/editor/flow-editor.d.ts +54 -0
- package/dist/flow/editor/flow-editor.js +199 -0
- package/dist/flow/editor/issues-popover.d.ts +11 -0
- package/dist/flow/editor/issues-popover.js +70 -0
- package/dist/flow/editor/library-rail.d.ts +19 -0
- package/dist/flow/editor/library-rail.js +25 -0
- package/dist/flow/editor/make-controller.d.ts +18 -0
- package/dist/flow/editor/make-controller.js +44 -0
- package/dist/flow/editor/node-chip.d.ts +2 -0
- package/dist/flow/editor/node-chip.js +16 -0
- package/dist/flow/editor/save-publish-bar.d.ts +27 -0
- package/dist/flow/editor/save-publish-bar.js +14 -0
- package/dist/flow/editor/settings-dialog.d.ts +11 -0
- package/dist/flow/editor/settings-dialog.js +11 -0
- package/dist/flow/editor/settings-shell.d.ts +21 -0
- package/dist/flow/editor/settings-shell.js +20 -0
- package/dist/flow/editor/side-rail.d.ts +22 -0
- package/dist/flow/editor/side-rail.js +15 -0
- package/dist/flow/editor/use-dag-editor.d.ts +94 -0
- package/dist/flow/editor/use-dag-editor.js +502 -0
- package/dist/flow/index.d.ts +25 -0
- package/dist/flow/index.js +25 -0
- package/dist/flow/presence-motion.d.ts +10 -0
- package/dist/flow/presence-motion.js +88 -0
- package/dist/flow/styles.css +1 -0
- package/dist/flow/use-collaborative-graph.d.ts +61 -0
- package/dist/flow/use-collaborative-graph.js +396 -0
- package/dist/flow/use-debounced-value.d.ts +1 -0
- package/dist/flow/use-debounced-value.js +10 -0
- package/dist/hooks/use-detail-nav.d.ts +11 -0
- package/dist/hooks/use-detail-nav.js +49 -0
- package/dist/hooks/use-flush-on-hide.d.ts +1 -0
- package/dist/hooks/use-flush-on-hide.js +21 -0
- package/dist/hooks/use-media-query.d.ts +1 -0
- package/dist/hooks/use-media-query.js +20 -0
- package/dist/hooks/use-virtualizer.d.ts +1 -0
- package/dist/hooks/use-virtualizer.js +1 -0
- package/dist/index.d.ts +139 -0
- package/dist/index.js +139 -0
- package/dist/lib/brand-style.d.ts +6 -0
- package/dist/lib/brand-style.js +16 -0
- package/dist/lib/cn.d.ts +2 -0
- package/dist/lib/cn.js +5 -0
- package/dist/lib/composited-glint.d.ts +7 -0
- package/dist/lib/composited-glint.js +98 -0
- package/dist/lib/date-time-format.d.ts +2 -0
- package/dist/lib/date-time-format.js +21 -0
- package/dist/lib/disclosure-state.d.ts +11 -0
- package/dist/lib/disclosure-state.js +22 -0
- package/dist/lib/label-swatches.d.ts +10 -0
- package/dist/lib/label-swatches.js +24 -0
- package/dist/lib/local-ui-state.d.ts +10 -0
- package/dist/lib/local-ui-state.js +95 -0
- package/dist/lib/parse-loose-datetime.d.ts +1 -0
- package/dist/lib/parse-loose-datetime.js +180 -0
- package/dist/lib/parse-loose-time.d.ts +6 -0
- package/dist/lib/parse-loose-time.js +53 -0
- package/dist/lib/parse-loose.d.ts +1 -0
- package/dist/lib/parse-loose.js +170 -0
- package/dist/lib/react-19-jsx.d.ts +8 -0
- package/dist/lib/react-19-jsx.js +1 -0
- package/dist/lib/view-chrome.d.ts +5 -0
- package/dist/lib/view-chrome.js +5 -0
- package/dist/lib/view-query/config.d.ts +26 -0
- package/dist/lib/view-query/config.js +162 -0
- package/dist/lib/view-query/evaluate.d.ts +11 -0
- package/dist/lib/view-query/evaluate.js +196 -0
- package/dist/lib/view-query/group-order.d.ts +25 -0
- package/dist/lib/view-query/group-order.js +49 -0
- package/dist/lib/view-query/index.d.ts +6 -0
- package/dist/lib/view-query/index.js +6 -0
- package/dist/lib/view-query/model.d.ts +34 -0
- package/dist/lib/view-query/model.js +88 -0
- package/dist/lib/view-query/sort.d.ts +7 -0
- package/dist/lib/view-query/sort.js +30 -0
- package/dist/lib/view-query/types.d.ts +70 -0
- package/dist/lib/view-query/types.js +12 -0
- package/dist/lib/weekly-schedule.d.ts +8 -0
- package/dist/lib/weekly-schedule.js +10 -0
- package/dist/mcp-app/index.d.ts +4 -0
- package/dist/mcp-app/index.js +3 -0
- package/dist/mcp-app/shadcn.css +629 -0
- package/dist/mcp-app/styles.css +72 -0
- package/dist/mcp-app/theme.d.ts +2 -0
- package/dist/mcp-app/theme.js +62 -0
- package/dist/mcp-app/use-atmos-mcp-app.d.ts +109 -0
- package/dist/mcp-app/use-atmos-mcp-app.js +30 -0
- package/dist/mcp-app/vite.d.ts +7 -0
- package/dist/mcp-app/vite.js +30 -0
- package/dist/tokens/index.d.ts +7 -0
- package/dist/tokens/index.js +7 -0
- package/dist/tokens/semantic.css +110 -0
- package/dist/tokens/tokens.css +195 -0
- package/package.json +98 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/** The budget a consumer whose table carries an octet_length CHECK should pass to writeViewConfig. */
|
|
2
|
+
export const MAX_VIEW_CONFIG_BYTES = 16 * 1024;
|
|
3
|
+
export { GROUP_ORDER_ALIASES, toInMemoryGroupOrder, toStoredGroupOrder } from './group-order.js';
|
|
4
|
+
const SYNTHETIC_DEFAULT_SORT_ID = 'default-sort';
|
|
5
|
+
export function readViewConfig(raw, registry, sortFields, display) {
|
|
6
|
+
const root = asRecord(raw) ?? {};
|
|
7
|
+
const query = asRecord(root.query) ?? root;
|
|
8
|
+
const displayRoot = asRecord(root.display) ?? root;
|
|
9
|
+
return {
|
|
10
|
+
query: {
|
|
11
|
+
filter: readFilter(query.filter, registry),
|
|
12
|
+
sort: readSort(query.sort, sortFields),
|
|
13
|
+
},
|
|
14
|
+
display: display.read(displayRoot),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function writeViewConfig(blob, next, display, options) {
|
|
18
|
+
const owned = new Set(['filter', 'sort', 'query', 'display', ...display.keys]);
|
|
19
|
+
const rest = Object.entries(asRecord(blob) ?? {}).filter(([key]) => !owned.has(key));
|
|
20
|
+
const out = {
|
|
21
|
+
...Object.fromEntries(rest),
|
|
22
|
+
query: next.query,
|
|
23
|
+
display: display.write(next.display),
|
|
24
|
+
};
|
|
25
|
+
const maxBytes = options?.maxBytes;
|
|
26
|
+
if (maxBytes !== undefined) {
|
|
27
|
+
const bytes = viewConfigByteLength(out);
|
|
28
|
+
if (bytes > maxBytes) {
|
|
29
|
+
throw new Error(`view config is ${bytes} bytes, over the ${maxBytes} byte limit`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
const utf8 = new TextEncoder();
|
|
35
|
+
/**
|
|
36
|
+
* The size Postgres will measure: `octet_length(config::text)` on a jsonb column, which
|
|
37
|
+
* re-serializes with a space after every `:` and `,` and counts UTF-8 bytes, not UTF-16 units.
|
|
38
|
+
*/
|
|
39
|
+
export function viewConfigByteLength(blob) {
|
|
40
|
+
return utf8.encode(jsonbText(blob)).length;
|
|
41
|
+
}
|
|
42
|
+
function jsonbText(value) {
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
return `[${value.map((item) => jsonbText(item === undefined ? null : item)).join(', ')}]`;
|
|
45
|
+
}
|
|
46
|
+
if (value && typeof value === 'object') {
|
|
47
|
+
const pairs = Object.entries(value)
|
|
48
|
+
.filter(([, member]) => member !== undefined && typeof member !== 'function')
|
|
49
|
+
.map(([key, member]) => `${JSON.stringify(key)}: ${jsonbText(member)}`);
|
|
50
|
+
return `{${pairs.join(', ')}}`;
|
|
51
|
+
}
|
|
52
|
+
return JSON.stringify(value) ?? 'null';
|
|
53
|
+
}
|
|
54
|
+
function asRecord(raw) {
|
|
55
|
+
if (typeof raw === 'string') {
|
|
56
|
+
if (!raw.trim())
|
|
57
|
+
return null;
|
|
58
|
+
try {
|
|
59
|
+
return asRecord(JSON.parse(raw));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
66
|
+
return null;
|
|
67
|
+
return raw;
|
|
68
|
+
}
|
|
69
|
+
function emptyFilterTree() {
|
|
70
|
+
return { id: 'root', node: 'group', conjunction: 'and', children: [] };
|
|
71
|
+
}
|
|
72
|
+
function readFilter(raw, registry) {
|
|
73
|
+
const node = readNode(raw, registry, 'root');
|
|
74
|
+
return node?.node === 'group' ? node : emptyFilterTree();
|
|
75
|
+
}
|
|
76
|
+
function readNode(raw, registry, fallbackId) {
|
|
77
|
+
const node = asRecord(raw);
|
|
78
|
+
if (!node)
|
|
79
|
+
return null;
|
|
80
|
+
const id = typeof node.id === 'string' ? node.id : fallbackId;
|
|
81
|
+
if (node.node === 'group') {
|
|
82
|
+
const children = Array.isArray(node.children)
|
|
83
|
+
? node.children
|
|
84
|
+
.map((child, index) => readNode(child, registry, `${fallbackId}.${index}`))
|
|
85
|
+
.filter((child) => child !== null)
|
|
86
|
+
: [];
|
|
87
|
+
return { id, node: 'group', conjunction: node.conjunction === 'or' ? 'or' : 'and', children };
|
|
88
|
+
}
|
|
89
|
+
return node.node === 'condition' ? readCondition(node, registry, id) : null;
|
|
90
|
+
}
|
|
91
|
+
function readCondition(node, registry, id) {
|
|
92
|
+
if (typeof node.field !== 'string')
|
|
93
|
+
return null;
|
|
94
|
+
const meta = registry.meta[node.field];
|
|
95
|
+
if (!meta)
|
|
96
|
+
return null;
|
|
97
|
+
const operator = meta.operators.find((declared) => declared === node.operator);
|
|
98
|
+
if (!operator)
|
|
99
|
+
return null;
|
|
100
|
+
const value = readValue(node.value);
|
|
101
|
+
if (!value)
|
|
102
|
+
return null;
|
|
103
|
+
return { id, node: 'condition', field: node.field, operator, value };
|
|
104
|
+
}
|
|
105
|
+
function readValue(raw) {
|
|
106
|
+
const value = asRecord(raw);
|
|
107
|
+
if (!value)
|
|
108
|
+
return null;
|
|
109
|
+
switch (value.kind) {
|
|
110
|
+
case 'text':
|
|
111
|
+
return typeof value.text === 'string' ? { kind: 'text', text: value.text } : null;
|
|
112
|
+
case 'tokens':
|
|
113
|
+
return isStringArray(value.tokens) ? { kind: 'tokens', tokens: value.tokens } : null;
|
|
114
|
+
case 'ids':
|
|
115
|
+
return isStringArray(value.ids) ? { kind: 'ids', ids: value.ids } : null;
|
|
116
|
+
case 'number':
|
|
117
|
+
return isFiniteNumber(value.n) ? { kind: 'number', n: value.n } : null;
|
|
118
|
+
case 'boolean':
|
|
119
|
+
return typeof value.value === 'boolean' ? { kind: 'boolean', value: value.value } : null;
|
|
120
|
+
case 'date':
|
|
121
|
+
return isNullableIso(value.iso) ? { kind: 'date', iso: value.iso ?? null } : null;
|
|
122
|
+
case 'date_range':
|
|
123
|
+
return isNullableIso(value.from) && isNullableIso(value.to)
|
|
124
|
+
? { kind: 'date_range', from: value.from ?? null, to: value.to ?? null }
|
|
125
|
+
: null;
|
|
126
|
+
case 'relative_days':
|
|
127
|
+
return isFiniteNumber(value.days) ? { kind: 'relative_days', days: value.days } : null;
|
|
128
|
+
case 'current_user':
|
|
129
|
+
return { kind: 'current_user' };
|
|
130
|
+
case 'empty':
|
|
131
|
+
return { kind: 'empty' };
|
|
132
|
+
default:
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function isStringArray(raw) {
|
|
137
|
+
return Array.isArray(raw) && raw.every((item) => typeof item === 'string');
|
|
138
|
+
}
|
|
139
|
+
function isFiniteNumber(raw) {
|
|
140
|
+
return typeof raw === 'number' && Number.isFinite(raw);
|
|
141
|
+
}
|
|
142
|
+
function isNullableIso(raw) {
|
|
143
|
+
return raw === null || raw === undefined || typeof raw === 'string';
|
|
144
|
+
}
|
|
145
|
+
function readSort(raw, sortFields) {
|
|
146
|
+
if (!Array.isArray(raw))
|
|
147
|
+
return [];
|
|
148
|
+
const sort = [];
|
|
149
|
+
for (const [index, item] of raw.entries()) {
|
|
150
|
+
const entry = asRecord(item);
|
|
151
|
+
if (!entry || typeof entry.field !== 'string' || !sortFields.has(entry.field))
|
|
152
|
+
continue;
|
|
153
|
+
sort.push({
|
|
154
|
+
id: typeof entry.id === 'string' ? entry.id : `s-${index}`,
|
|
155
|
+
field: entry.field,
|
|
156
|
+
dir: entry.dir === 'desc' ? 'desc' : 'asc',
|
|
157
|
+
nulls: entry.nulls === 'first' ? 'first' : entry.nulls === 'last' ? 'last' : undefined,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const onlyEntry = sort.length === 1 ? sort[0] : undefined;
|
|
161
|
+
return onlyEntry?.id === SYNTHETIC_DEFAULT_SORT_ID ? [] : sort;
|
|
162
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FieldRegistry } from './model.js';
|
|
2
|
+
import { type FilterTree } from './types.js';
|
|
3
|
+
export interface EvalContext {
|
|
4
|
+
categoryOf?: (id: string) => string | undefined;
|
|
5
|
+
currentUserId?: string | null;
|
|
6
|
+
now?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface EvalAdapter<TRow> {
|
|
9
|
+
valueOf: (row: TRow, field: string) => unknown;
|
|
10
|
+
}
|
|
11
|
+
export declare function evaluateTree<TRow>(row: TRow, tree: FilterTree, registry: FieldRegistry, adapter: EvalAdapter<TRow>, ctx?: EvalContext): boolean;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { CURRENT_USER_FILTER_ID, } from './types.js';
|
|
2
|
+
const NO_CONTEXT = {};
|
|
3
|
+
export function evaluateTree(row, tree, registry, adapter, ctx = NO_CONTEXT) {
|
|
4
|
+
return evaluateNode(row, tree, registry, adapter, ctx);
|
|
5
|
+
}
|
|
6
|
+
function evaluateNode(row, node, registry, adapter, ctx) {
|
|
7
|
+
if (node.node === 'group') {
|
|
8
|
+
if (node.children.length === 0)
|
|
9
|
+
return true;
|
|
10
|
+
const results = node.children.map((child) => evaluateNode(row, child, registry, adapter, ctx));
|
|
11
|
+
return node.conjunction === 'and' ? results.every(Boolean) : results.some(Boolean);
|
|
12
|
+
}
|
|
13
|
+
const meta = registry.meta[node.field];
|
|
14
|
+
if (!meta)
|
|
15
|
+
return true;
|
|
16
|
+
const value = adapter.valueOf(row, node.field);
|
|
17
|
+
switch (meta.kind) {
|
|
18
|
+
case 'text':
|
|
19
|
+
return evalText(value, node.operator, node.value);
|
|
20
|
+
case 'number':
|
|
21
|
+
return evalNumber(value, node.operator, node.value);
|
|
22
|
+
case 'boolean':
|
|
23
|
+
return evalBoolean(value, node.operator, node.value);
|
|
24
|
+
case 'date':
|
|
25
|
+
return evalDate(value, node.operator, node.value, ctx);
|
|
26
|
+
case 'select':
|
|
27
|
+
return evalSelect(value, node.operator, node.value, meta, ctx);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function evalText(value, op, filter) {
|
|
31
|
+
const text = typeof value === 'string' ? value : '';
|
|
32
|
+
if (op === 'is_set')
|
|
33
|
+
return text.length > 0;
|
|
34
|
+
if (op === 'is_not_set')
|
|
35
|
+
return text.length === 0;
|
|
36
|
+
if (filter.kind !== 'text')
|
|
37
|
+
return false;
|
|
38
|
+
const probe = filter.text.trim().toLowerCase();
|
|
39
|
+
if (!probe)
|
|
40
|
+
return true;
|
|
41
|
+
const hay = text.toLowerCase();
|
|
42
|
+
switch (op) {
|
|
43
|
+
case 'contains':
|
|
44
|
+
return hay.includes(probe);
|
|
45
|
+
case 'starts_with':
|
|
46
|
+
return hay.startsWith(probe);
|
|
47
|
+
case 'ends_with':
|
|
48
|
+
return hay.endsWith(probe);
|
|
49
|
+
case 'is':
|
|
50
|
+
return hay === probe;
|
|
51
|
+
case 'is_not':
|
|
52
|
+
return hay !== probe;
|
|
53
|
+
default:
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function evalNumber(value, op, filter) {
|
|
58
|
+
const n = typeof value === 'number' ? value : null;
|
|
59
|
+
if (op === 'is_set')
|
|
60
|
+
return n != null;
|
|
61
|
+
if (op === 'is_not_set')
|
|
62
|
+
return n == null;
|
|
63
|
+
if (n == null || filter.kind !== 'number')
|
|
64
|
+
return false;
|
|
65
|
+
const target = filter.n;
|
|
66
|
+
switch (op) {
|
|
67
|
+
case 'eq':
|
|
68
|
+
return n === target;
|
|
69
|
+
case 'neq':
|
|
70
|
+
return n !== target;
|
|
71
|
+
case 'gt':
|
|
72
|
+
return n > target;
|
|
73
|
+
case 'gte':
|
|
74
|
+
return n >= target;
|
|
75
|
+
case 'lt':
|
|
76
|
+
return n < target;
|
|
77
|
+
case 'lte':
|
|
78
|
+
return n <= target;
|
|
79
|
+
default:
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function evalBoolean(value, op, filter) {
|
|
84
|
+
const current = typeof value === 'boolean' ? value : null;
|
|
85
|
+
if (op === 'is_set')
|
|
86
|
+
return current != null;
|
|
87
|
+
if (op === 'is_not_set')
|
|
88
|
+
return current == null;
|
|
89
|
+
if (current == null || filter.kind !== 'boolean')
|
|
90
|
+
return false;
|
|
91
|
+
switch (op) {
|
|
92
|
+
case 'is':
|
|
93
|
+
return current === filter.value;
|
|
94
|
+
case 'is_not':
|
|
95
|
+
return current !== filter.value;
|
|
96
|
+
default:
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function evalDate(value, op, filter, ctx) {
|
|
101
|
+
if (op === 'is_set')
|
|
102
|
+
return value != null;
|
|
103
|
+
if (op === 'is_not_set')
|
|
104
|
+
return value == null;
|
|
105
|
+
if (value == null)
|
|
106
|
+
return false;
|
|
107
|
+
const ts = typeof value === 'number' ? value : typeof value === 'string' ? Date.parse(value) : NaN;
|
|
108
|
+
if (Number.isNaN(ts))
|
|
109
|
+
return false;
|
|
110
|
+
if (op === 'within_next' || op === 'within_last') {
|
|
111
|
+
if (filter.kind !== 'relative_days')
|
|
112
|
+
return false;
|
|
113
|
+
const now = ctx.now ?? Date.now();
|
|
114
|
+
const span = filter.days * 86_400_000;
|
|
115
|
+
return op === 'within_next' ? ts >= now && ts <= now + span : ts <= now && ts >= now - span;
|
|
116
|
+
}
|
|
117
|
+
if (op === 'between') {
|
|
118
|
+
if (filter.kind !== 'date_range')
|
|
119
|
+
return false;
|
|
120
|
+
const from = filter.from ? Date.parse(filter.from) : null;
|
|
121
|
+
const to = filter.to ? Date.parse(filter.to) : null;
|
|
122
|
+
if (from != null && ts < from)
|
|
123
|
+
return false;
|
|
124
|
+
if (to != null && ts > to)
|
|
125
|
+
return false;
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
if (filter.kind !== 'date')
|
|
129
|
+
return false;
|
|
130
|
+
if (filter.iso == null)
|
|
131
|
+
return true;
|
|
132
|
+
const target = Date.parse(filter.iso);
|
|
133
|
+
switch (op) {
|
|
134
|
+
case 'before':
|
|
135
|
+
return ts < target;
|
|
136
|
+
case 'after':
|
|
137
|
+
return ts > target;
|
|
138
|
+
case 'on':
|
|
139
|
+
return sameUtcDay(ts, target);
|
|
140
|
+
default:
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function sameUtcDay(a, b) {
|
|
145
|
+
const da = new Date(a);
|
|
146
|
+
const db = new Date(b);
|
|
147
|
+
return (da.getUTCFullYear() === db.getUTCFullYear() &&
|
|
148
|
+
da.getUTCMonth() === db.getUTCMonth() &&
|
|
149
|
+
da.getUTCDate() === db.getUTCDate());
|
|
150
|
+
}
|
|
151
|
+
function evalSelect(value, op, filter, meta, ctx) {
|
|
152
|
+
const held = heldValues(value, meta.cardinality === 'many');
|
|
153
|
+
if (op === 'is_set')
|
|
154
|
+
return held.length > 0;
|
|
155
|
+
if (op === 'is_not_set')
|
|
156
|
+
return held.length === 0;
|
|
157
|
+
const ids = selectedIds(filter, meta, ctx);
|
|
158
|
+
if (ids == null)
|
|
159
|
+
return false;
|
|
160
|
+
if (ids.length === 0)
|
|
161
|
+
return true;
|
|
162
|
+
const categoryOf = meta.categoryOf ?? ctx.categoryOf;
|
|
163
|
+
const matched = held.some((one) => {
|
|
164
|
+
if (ids.includes(one))
|
|
165
|
+
return true;
|
|
166
|
+
const category = categoryOf?.(one);
|
|
167
|
+
return category != null && ids.includes(category);
|
|
168
|
+
});
|
|
169
|
+
switch (op) {
|
|
170
|
+
case 'is':
|
|
171
|
+
case 'is_any_of':
|
|
172
|
+
return matched;
|
|
173
|
+
case 'is_not':
|
|
174
|
+
case 'is_none_of':
|
|
175
|
+
return !matched;
|
|
176
|
+
default:
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function heldValues(value, many) {
|
|
181
|
+
if (many) {
|
|
182
|
+
return Array.isArray(value) ? value.filter((one) => typeof one === 'string') : [];
|
|
183
|
+
}
|
|
184
|
+
return typeof value === 'string' ? [value] : [];
|
|
185
|
+
}
|
|
186
|
+
function selectedIds(filter, meta, ctx) {
|
|
187
|
+
if (filter.kind === 'current_user')
|
|
188
|
+
return ctx.currentUserId ? [ctx.currentUserId] : [];
|
|
189
|
+
if (filter.kind !== 'ids')
|
|
190
|
+
return null;
|
|
191
|
+
const alias = meta.aliasId;
|
|
192
|
+
const resolved = filter.ids
|
|
193
|
+
.map((id) => (id === CURRENT_USER_FILTER_ID ? ctx.currentUserId : id))
|
|
194
|
+
.filter((id) => Boolean(id));
|
|
195
|
+
return alias ? resolved.map(alias) : resolved;
|
|
196
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type GroupItemOrder = 'manual' | 'alphabetical' | 'item_count';
|
|
2
|
+
/**
|
|
3
|
+
* Maps the value persisted on the wire onto the in-memory vocabulary.
|
|
4
|
+
* Do not delete an entry once added: if a wire value ever reaches storage with
|
|
5
|
+
* no alias back to its in-memory counterpart, an older bundle that doesn't
|
|
6
|
+
* recognize it will silently degrade group ordering to `manual`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const GROUP_ORDER_ALIASES: Readonly<Record<string, string>>;
|
|
9
|
+
export declare function toInMemoryGroupOrder(stored: string): string;
|
|
10
|
+
export declare function toStoredGroupOrder(inMemory: string): string;
|
|
11
|
+
export interface OrderableGroup {
|
|
12
|
+
key: string;
|
|
13
|
+
rows: readonly unknown[];
|
|
14
|
+
}
|
|
15
|
+
export interface GroupOrderDisplay {
|
|
16
|
+
groupItemOrder: GroupItemOrder;
|
|
17
|
+
groupOrderIds: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
export interface GroupVisibilityDisplay {
|
|
20
|
+
groupBy: string;
|
|
21
|
+
hideEmptyGroups: boolean;
|
|
22
|
+
hiddenGroupIds: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
export declare function applyGroupOrder<TGroup extends OrderableGroup>(groups: TGroup[], display: GroupOrderDisplay, labelOf: (group: TGroup) => string): TGroup[];
|
|
25
|
+
export declare function applyGroupDisplay<TGroup extends OrderableGroup>(groups: TGroup[], display: GroupVisibilityDisplay): TGroup[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps the value persisted on the wire onto the in-memory vocabulary.
|
|
3
|
+
* Do not delete an entry once added: if a wire value ever reaches storage with
|
|
4
|
+
* no alias back to its in-memory counterpart, an older bundle that doesn't
|
|
5
|
+
* recognize it will silently degrade group ordering to `manual`.
|
|
6
|
+
*/
|
|
7
|
+
export const GROUP_ORDER_ALIASES = { task_count: 'item_count' };
|
|
8
|
+
const STORED_GROUP_ORDERS = Object.fromEntries(Object.entries(GROUP_ORDER_ALIASES).map(([stored, inMemory]) => [inMemory, stored]));
|
|
9
|
+
export function toInMemoryGroupOrder(stored) {
|
|
10
|
+
return GROUP_ORDER_ALIASES[stored] ?? stored;
|
|
11
|
+
}
|
|
12
|
+
export function toStoredGroupOrder(inMemory) {
|
|
13
|
+
return STORED_GROUP_ORDERS[inMemory] ?? inMemory;
|
|
14
|
+
}
|
|
15
|
+
export function applyGroupOrder(groups, display, labelOf) {
|
|
16
|
+
switch (display.groupItemOrder) {
|
|
17
|
+
case 'alphabetical':
|
|
18
|
+
return [...groups].sort((a, b) => labelOf(a).localeCompare(labelOf(b)));
|
|
19
|
+
case 'item_count':
|
|
20
|
+
return [...groups].sort((a, b) => b.rows.length - a.rows.length || labelOf(a).localeCompare(labelOf(b)));
|
|
21
|
+
default:
|
|
22
|
+
return orderByIds(groups, display.groupOrderIds);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function applyGroupDisplay(groups, display) {
|
|
26
|
+
if (display.groupBy === 'none')
|
|
27
|
+
return groups;
|
|
28
|
+
const hidden = new Set(display.hiddenGroupIds);
|
|
29
|
+
return groups.filter((group) => {
|
|
30
|
+
if (hidden.has(group.key))
|
|
31
|
+
return false;
|
|
32
|
+
return !display.hideEmptyGroups || group.rows.length > 0;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function orderByIds(groups, ids) {
|
|
36
|
+
if (ids.length === 0)
|
|
37
|
+
return groups;
|
|
38
|
+
const byId = new Map(groups.map((group) => [group.key, group]));
|
|
39
|
+
const seen = new Set();
|
|
40
|
+
const ordered = [];
|
|
41
|
+
for (const id of ids) {
|
|
42
|
+
const group = byId.get(id);
|
|
43
|
+
if (!group || seen.has(id))
|
|
44
|
+
continue;
|
|
45
|
+
ordered.push(group);
|
|
46
|
+
seen.add(id);
|
|
47
|
+
}
|
|
48
|
+
return [...ordered, ...groups.filter((group) => !seen.has(group.key))];
|
|
49
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type Conjunction, type FieldKind, type FilterCondition, type FilterGroup, type FilterNode, type FilterOp, type FilterTree, type FilterValue } from './types.js';
|
|
2
|
+
export interface ViewQueryFieldMeta {
|
|
3
|
+
field: string;
|
|
4
|
+
operators: FilterOp[];
|
|
5
|
+
defaultOperator: FilterOp;
|
|
6
|
+
kind: FieldKind;
|
|
7
|
+
defaultNumber?: number;
|
|
8
|
+
/** Multi-valued fields (assignee) match if ANY value satisfies the condition. */
|
|
9
|
+
cardinality?: 'one' | 'many';
|
|
10
|
+
/** Maps a stored id onto the row's actual value, for aliased columns. */
|
|
11
|
+
aliasId?: (id: string) => string;
|
|
12
|
+
/** Lazily resolved option source, so a consumer can supply late-bound select options. */
|
|
13
|
+
selectOptions?: () => readonly {
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
}[];
|
|
17
|
+
/** Explicit category resolver, modelled on the evaluator's EvalContext.categoryOf. */
|
|
18
|
+
categoryOf?: (id: string) => string | undefined;
|
|
19
|
+
}
|
|
20
|
+
export interface FieldRegistry<TMeta extends ViewQueryFieldMeta = ViewQueryFieldMeta> {
|
|
21
|
+
order: string[];
|
|
22
|
+
meta: Record<string, TMeta>;
|
|
23
|
+
}
|
|
24
|
+
export declare function defaultValueFor(meta: ViewQueryFieldMeta, op: FilterOp): FilterValue;
|
|
25
|
+
export declare function newCondition<TMeta extends ViewQueryFieldMeta>(registry: FieldRegistry<TMeta>, field: string): FilterCondition;
|
|
26
|
+
export declare function newGroup(conjunction?: Conjunction): FilterGroup;
|
|
27
|
+
export declare function updateNode(tree: FilterTree, id: string, fn: (node: FilterNode) => FilterNode): FilterTree;
|
|
28
|
+
export declare function removeNode(tree: FilterTree, id: string): FilterTree;
|
|
29
|
+
export declare function addChild(tree: FilterTree, groupId: string, child: FilterNode): FilterTree;
|
|
30
|
+
export declare function wrapInGroup(tree: FilterTree, id: string, conjunction?: Conjunction): FilterTree;
|
|
31
|
+
export declare function topLevelConditions(tree: FilterTree): FilterCondition[];
|
|
32
|
+
export declare function topLevelGroups(tree: FilterTree): FilterGroup[];
|
|
33
|
+
export declare function conditionCount(node: FilterNode): number;
|
|
34
|
+
export declare function isAdvanced(tree: FilterTree): boolean;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// `{ kind: 'current_user' }` (FilterValue) and CURRENT_USER_FILTER_ID (a sentinel
|
|
2
|
+
// field id) are two mechanisms for the same "current user" idea; unifying them is
|
|
3
|
+
// an objects-table data migration and is deferred.
|
|
4
|
+
import { nodeId, } from './types.js';
|
|
5
|
+
export function defaultValueFor(meta, op) {
|
|
6
|
+
if (op === 'is_set' || op === 'is_not_set')
|
|
7
|
+
return { kind: 'empty' };
|
|
8
|
+
if (op === 'within_next' || op === 'within_last')
|
|
9
|
+
return { kind: 'relative_days', days: 7 };
|
|
10
|
+
switch (meta.kind) {
|
|
11
|
+
case 'text':
|
|
12
|
+
return op === 'is_any_of' || op === 'is_none_of'
|
|
13
|
+
? { kind: 'tokens', tokens: [] }
|
|
14
|
+
: { kind: 'text', text: '' };
|
|
15
|
+
case 'select':
|
|
16
|
+
return { kind: 'ids', ids: [] };
|
|
17
|
+
case 'number':
|
|
18
|
+
return { kind: 'number', n: meta.defaultNumber ?? 3 };
|
|
19
|
+
case 'date':
|
|
20
|
+
return op === 'between'
|
|
21
|
+
? { kind: 'date_range', from: null, to: null }
|
|
22
|
+
: { kind: 'date', iso: null };
|
|
23
|
+
case 'boolean':
|
|
24
|
+
return { kind: 'boolean', value: true };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function newCondition(registry, field) {
|
|
28
|
+
const meta = registry.meta[field];
|
|
29
|
+
if (!meta)
|
|
30
|
+
throw new Error(`newCondition: unknown field "${field}"`);
|
|
31
|
+
const operator = meta.defaultOperator;
|
|
32
|
+
return {
|
|
33
|
+
id: nodeId('c'),
|
|
34
|
+
node: 'condition',
|
|
35
|
+
field,
|
|
36
|
+
operator,
|
|
37
|
+
value: defaultValueFor(meta, operator),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function newGroup(conjunction = 'and') {
|
|
41
|
+
return { id: nodeId('g'), node: 'group', conjunction, children: [] };
|
|
42
|
+
}
|
|
43
|
+
export function updateNode(tree, id, fn) {
|
|
44
|
+
return mapNode(tree, id, fn);
|
|
45
|
+
}
|
|
46
|
+
function mapNode(node, id, fn) {
|
|
47
|
+
if (node.id === id)
|
|
48
|
+
return fn(node);
|
|
49
|
+
if (node.node === 'group') {
|
|
50
|
+
return { ...node, children: node.children.map((child) => mapNode(child, id, fn)) };
|
|
51
|
+
}
|
|
52
|
+
return node;
|
|
53
|
+
}
|
|
54
|
+
export function removeNode(tree, id) {
|
|
55
|
+
return pruneChildren(tree, id);
|
|
56
|
+
}
|
|
57
|
+
function pruneChildren(node, id) {
|
|
58
|
+
if (node.node !== 'group')
|
|
59
|
+
return node;
|
|
60
|
+
return {
|
|
61
|
+
...node,
|
|
62
|
+
children: node.children
|
|
63
|
+
.filter((child) => child.id !== id)
|
|
64
|
+
.map((child) => pruneChildren(child, id)),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function addChild(tree, groupId, child) {
|
|
68
|
+
return updateNode(tree, groupId, (node) => node.node === 'group' ? { ...node, children: [...node.children, child] } : node);
|
|
69
|
+
}
|
|
70
|
+
export function wrapInGroup(tree, id, conjunction = 'or') {
|
|
71
|
+
if (id === tree.id)
|
|
72
|
+
return tree;
|
|
73
|
+
return updateNode(tree, id, (node) => ({ ...newGroup(conjunction), children: [node] }));
|
|
74
|
+
}
|
|
75
|
+
export function topLevelConditions(tree) {
|
|
76
|
+
return tree.children.filter((node) => node.node === 'condition');
|
|
77
|
+
}
|
|
78
|
+
export function topLevelGroups(tree) {
|
|
79
|
+
return tree.children.filter((node) => node.node === 'group');
|
|
80
|
+
}
|
|
81
|
+
export function conditionCount(node) {
|
|
82
|
+
if (node.node === 'condition')
|
|
83
|
+
return 1;
|
|
84
|
+
return node.children.reduce((sum, child) => sum + conditionCount(child), 0);
|
|
85
|
+
}
|
|
86
|
+
export function isAdvanced(tree) {
|
|
87
|
+
return tree.children.some((node) => node.node === 'group') || tree.conjunction === 'or';
|
|
88
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ViewSort } from './types.js';
|
|
2
|
+
export type SortValue = string | number | null | undefined;
|
|
3
|
+
export interface SortAdapter<T> {
|
|
4
|
+
valueOf: (row: T, field: string) => SortValue;
|
|
5
|
+
tieBreak?: (a: T, b: T) => number;
|
|
6
|
+
}
|
|
7
|
+
export declare function sortRows<T>(rows: readonly T[], sort: ViewSort, adapter: SortAdapter<T>): T[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const collator = new Intl.Collator(undefined, { sensitivity: 'base', numeric: true });
|
|
2
|
+
function compareOne(a, b, spec, adapter) {
|
|
3
|
+
const av = adapter.valueOf(a, spec.field);
|
|
4
|
+
const bv = adapter.valueOf(b, spec.field);
|
|
5
|
+
const nullsLast = (spec.nulls ?? 'last') === 'last';
|
|
6
|
+
if (av == null && bv == null)
|
|
7
|
+
return 0;
|
|
8
|
+
if (av == null)
|
|
9
|
+
return nullsLast ? 1 : -1;
|
|
10
|
+
if (bv == null)
|
|
11
|
+
return nullsLast ? -1 : 1;
|
|
12
|
+
const cmp = typeof av === 'string' && typeof bv === 'string'
|
|
13
|
+
? collator.compare(av, bv)
|
|
14
|
+
: Number(av) - Number(bv);
|
|
15
|
+
return spec.dir === 'asc' ? cmp : -cmp;
|
|
16
|
+
}
|
|
17
|
+
export function sortRows(rows, sort, adapter) {
|
|
18
|
+
if (sort.length === 0)
|
|
19
|
+
return rows;
|
|
20
|
+
const copy = [...rows];
|
|
21
|
+
copy.sort((a, b) => {
|
|
22
|
+
for (const spec of sort) {
|
|
23
|
+
const cmp = compareOne(a, b, spec, adapter);
|
|
24
|
+
if (cmp !== 0)
|
|
25
|
+
return cmp;
|
|
26
|
+
}
|
|
27
|
+
return adapter.tieBreak?.(a, b) ?? 0;
|
|
28
|
+
});
|
|
29
|
+
return copy;
|
|
30
|
+
}
|