@panphora/bevel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +16 -0
- package/README.md +226 -0
- package/bevel-fonts.css +16 -0
- package/bevel.css +3075 -0
- package/bevel.js +2126 -0
- package/dist/react/components/activity-feed.d.ts +89 -0
- package/dist/react/components/activity-feed.js +54 -0
- package/dist/react/components/alert-dialog.d.ts +14 -0
- package/dist/react/components/alert-dialog.js +19 -0
- package/dist/react/components/app-shell.d.ts +98 -0
- package/dist/react/components/app-shell.js +86 -0
- package/dist/react/components/app-toolbar.d.ts +19 -0
- package/dist/react/components/app-toolbar.js +22 -0
- package/dist/react/components/avatar-group.d.ts +67 -0
- package/dist/react/components/avatar-group.js +49 -0
- package/dist/react/components/badge-and-pill.d.ts +34 -0
- package/dist/react/components/badge-and-pill.js +12 -0
- package/dist/react/components/breadcrumbs.d.ts +56 -0
- package/dist/react/components/breadcrumbs.js +44 -0
- package/dist/react/components/bulk-bar.d.ts +22 -0
- package/dist/react/components/bulk-bar.js +59 -0
- package/dist/react/components/button.d.ts +15 -0
- package/dist/react/components/button.js +20 -0
- package/dist/react/components/callout-and-hint.d.ts +17 -0
- package/dist/react/components/callout-and-hint.js +8 -0
- package/dist/react/components/checkbox.d.ts +5 -0
- package/dist/react/components/checkbox.js +13 -0
- package/dist/react/components/chip.d.ts +149 -0
- package/dist/react/components/chip.js +29 -0
- package/dist/react/components/code-block.d.ts +50 -0
- package/dist/react/components/code-block.js +77 -0
- package/dist/react/components/code-editor.d.ts +122 -0
- package/dist/react/components/code-editor.js +695 -0
- package/dist/react/components/column-manager.d.ts +28 -0
- package/dist/react/components/column-manager.js +85 -0
- package/dist/react/components/combobox.d.ts +32 -0
- package/dist/react/components/combobox.js +37 -0
- package/dist/react/components/data-grid.d.ts +108 -0
- package/dist/react/components/data-grid.js +159 -0
- package/dist/react/components/data-table.d.ts +2 -0
- package/dist/react/components/data-table.js +6 -0
- package/dist/react/components/date.d.ts +23 -0
- package/dist/react/components/date.js +11 -0
- package/dist/react/components/details.d.ts +13 -0
- package/dist/react/components/details.js +20 -0
- package/dist/react/components/dialog.d.ts +74 -0
- package/dist/react/components/dialog.js +31 -0
- package/dist/react/components/diff-view.d.ts +73 -0
- package/dist/react/components/diff-view.js +68 -0
- package/dist/react/components/documents.d.ts +70 -0
- package/dist/react/components/documents.js +10 -0
- package/dist/react/components/drawer.d.ts +45 -0
- package/dist/react/components/drawer.js +35 -0
- package/dist/react/components/editable-grid-cells.d.ts +32 -0
- package/dist/react/components/editable-grid-cells.js +116 -0
- package/dist/react/components/eyebrow-kicker-lede-pull-quote.d.ts +39 -0
- package/dist/react/components/eyebrow-kicker-lede-pull-quote.js +12 -0
- package/dist/react/components/field.d.ts +32 -0
- package/dist/react/components/field.js +27 -0
- package/dist/react/components/file-tree.d.ts +75 -0
- package/dist/react/components/file-tree.js +247 -0
- package/dist/react/components/file.d.ts +43 -0
- package/dist/react/components/file.js +73 -0
- package/dist/react/components/files-table.d.ts +174 -0
- package/dist/react/components/files-table.js +237 -0
- package/dist/react/components/filter-builder.d.ts +37 -0
- package/dist/react/components/filter-builder.js +109 -0
- package/dist/react/components/form-error-summary.d.ts +16 -0
- package/dist/react/components/form-error-summary.js +49 -0
- package/dist/react/components/icon-button.d.ts +8 -0
- package/dist/react/components/icon-button.js +14 -0
- package/dist/react/components/icon.d.ts +7 -0
- package/dist/react/components/icon.js +15 -0
- package/dist/react/components/image.d.ts +72 -0
- package/dist/react/components/image.js +24 -0
- package/dist/react/components/inline-edit.d.ts +70 -0
- package/dist/react/components/inline-edit.js +149 -0
- package/dist/react/components/input.d.ts +29 -0
- package/dist/react/components/input.js +20 -0
- package/dist/react/components/kanban-board.d.ts +140 -0
- package/dist/react/components/kanban-board.js +374 -0
- package/dist/react/components/layout.d.ts +51 -0
- package/dist/react/components/layout.js +28 -0
- package/dist/react/components/list.d.ts +28 -0
- package/dist/react/components/list.js +6 -0
- package/dist/react/components/masthead-and-verdict.d.ts +60 -0
- package/dist/react/components/masthead-and-verdict.js +8 -0
- package/dist/react/components/menu.d.ts +26 -0
- package/dist/react/components/menu.js +31 -0
- package/dist/react/components/multi-select-picker.d.ts +49 -0
- package/dist/react/components/multi-select-picker.js +20 -0
- package/dist/react/components/note.d.ts +33 -0
- package/dist/react/components/note.js +8 -0
- package/dist/react/components/number-field.d.ts +16 -0
- package/dist/react/components/number-field.js +17 -0
- package/dist/react/components/pagination.d.ts +45 -0
- package/dist/react/components/pagination.js +87 -0
- package/dist/react/components/persistent-action-banner.d.ts +71 -0
- package/dist/react/components/persistent-action-banner.js +39 -0
- package/dist/react/components/popconfirm.d.ts +42 -0
- package/dist/react/components/popconfirm.js +59 -0
- package/dist/react/components/popover.d.ts +16 -0
- package/dist/react/components/popover.js +21 -0
- package/dist/react/components/progress-and-gauge.d.ts +44 -0
- package/dist/react/components/progress-and-gauge.js +19 -0
- package/dist/react/components/radio.d.ts +7 -0
- package/dist/react/components/radio.js +13 -0
- package/dist/react/components/rich-text-toolbar.d.ts +26 -0
- package/dist/react/components/rich-text-toolbar.js +64 -0
- package/dist/react/components/rows-and-card.d.ts +88 -0
- package/dist/react/components/rows-and-card.js +10 -0
- package/dist/react/components/save-status-indicator.d.ts +11 -0
- package/dist/react/components/save-status-indicator.js +33 -0
- package/dist/react/components/segmented-control.d.ts +21 -0
- package/dist/react/components/segmented-control.js +15 -0
- package/dist/react/components/select.d.ts +29 -0
- package/dist/react/components/select.js +29 -0
- package/dist/react/components/selectable-cards.d.ts +30 -0
- package/dist/react/components/selectable-cards.js +29 -0
- package/dist/react/components/settings-rows.d.ts +51 -0
- package/dist/react/components/settings-rows.js +8 -0
- package/dist/react/components/sidebar-nav.d.ts +77 -0
- package/dist/react/components/sidebar-nav.js +41 -0
- package/dist/react/components/skeleton-and-empty.d.ts +51 -0
- package/dist/react/components/skeleton-and-empty.js +14 -0
- package/dist/react/components/slider.d.ts +19 -0
- package/dist/react/components/slider.js +17 -0
- package/dist/react/components/small-charts.d.ts +60 -0
- package/dist/react/components/small-charts.js +76 -0
- package/dist/react/components/sortable.d.ts +30 -0
- package/dist/react/components/sortable.js +85 -0
- package/dist/react/components/split-button.d.ts +38 -0
- package/dist/react/components/split-button.js +43 -0
- package/dist/react/components/split-panes.d.ts +35 -0
- package/dist/react/components/split-panes.js +102 -0
- package/dist/react/components/stat-tiles.d.ts +54 -0
- package/dist/react/components/stat-tiles.js +8 -0
- package/dist/react/components/steps.d.ts +11 -0
- package/dist/react/components/steps.js +13 -0
- package/dist/react/components/switch.d.ts +6 -0
- package/dist/react/components/switch.js +13 -0
- package/dist/react/components/table.d.ts +61 -0
- package/dist/react/components/table.js +6 -0
- package/dist/react/components/tabs.d.ts +7 -0
- package/dist/react/components/tabs.js +13 -0
- package/dist/react/components/tags.d.ts +33 -0
- package/dist/react/components/tags.js +83 -0
- package/dist/react/components/textarea.d.ts +10 -0
- package/dist/react/components/textarea.js +6 -0
- package/dist/react/components/toast.d.ts +35 -0
- package/dist/react/components/toast.js +90 -0
- package/dist/react/components/tooltip-and-key.d.ts +20 -0
- package/dist/react/components/tooltip-and-key.js +20 -0
- package/dist/react/components/tree-view.d.ts +34 -0
- package/dist/react/components/tree-view.js +118 -0
- package/dist/react/components/upload-dropzone.d.ts +92 -0
- package/dist/react/components/upload-dropzone.js +116 -0
- package/dist/react/components/wizard-shell.d.ts +78 -0
- package/dist/react/components/wizard-shell.js +176 -0
- package/dist/react/fonts.css.d.ts +1 -0
- package/dist/react/index.d.ts +76 -0
- package/dist/react/index.js +77 -0
- package/dist/react/integrations/quickcrop.d.ts +58 -0
- package/dist/react/integrations/quickcrop.js +137 -0
- package/dist/react/integrations/richclay.d.ts +43 -0
- package/dist/react/integrations/richclay.js +129 -0
- package/dist/react/internal/classes.d.ts +3 -0
- package/dist/react/internal/classes.js +10 -0
- package/dist/react/internal/close-mark.d.ts +1 -0
- package/dist/react/internal/close-mark.js +10 -0
- package/dist/react/internal/controllable-state.d.ts +9 -0
- package/dist/react/internal/controllable-state.js +22 -0
- package/dist/react/internal/drop-effect.d.ts +28 -0
- package/dist/react/internal/drop-effect.js +8 -0
- package/dist/react/internal/form-reset.d.ts +2 -0
- package/dist/react/internal/form-reset.js +22 -0
- package/dist/react/internal/format.d.ts +1 -0
- package/dist/react/internal/format.js +14 -0
- package/dist/react/internal/link.d.ts +2 -0
- package/dist/react/internal/link.js +8 -0
- package/dist/react/internal/part.d.ts +20 -0
- package/dist/react/internal/part.js +47 -0
- package/dist/react/internal/pointer-session.d.ts +8 -0
- package/dist/react/internal/pointer-session.js +60 -0
- package/dist/react/internal/refs.d.ts +2 -0
- package/dist/react/internal/refs.js +22 -0
- package/dist/react/internal/rich-text.d.ts +28 -0
- package/dist/react/internal/rich-text.js +4 -0
- package/dist/react/internal/table-context.d.ts +39 -0
- package/dist/react/internal/table-context.js +12 -0
- package/dist/react/internal/use-object-url.d.ts +1 -0
- package/dist/react/internal/use-object-url.js +15 -0
- package/dist/react/layout.css +274 -0
- package/dist/react/layout.css.d.ts +1 -0
- package/dist/react/models/filter.d.ts +31 -0
- package/dist/react/models/filter.js +79 -0
- package/dist/react/models/kanban.d.ts +51 -0
- package/dist/react/models/kanban.js +35 -0
- package/dist/react/models/reorder.d.ts +9 -0
- package/dist/react/models/reorder.js +23 -0
- package/dist/react/models/table.d.ts +85 -0
- package/dist/react/models/table.js +144 -0
- package/dist/react/models/tree.d.ts +16 -0
- package/dist/react/models/tree.js +26 -0
- package/dist/react/provider.d.ts +18 -0
- package/dist/react/provider.js +31 -0
- package/dist/react/recipes/context-hypercms.d.ts +17 -0
- package/dist/react/recipes/context-hypercms.js +71 -0
- package/dist/react/recipes/context-quickcrop.d.ts +8 -0
- package/dist/react/recipes/context-quickcrop.js +49 -0
- package/dist/react/theme.css +8004 -0
- package/dist/react/theme.css.d.ts +1 -0
- package/examples/README.md +34 -0
- package/examples/components/activity-feed.html +28 -0
- package/examples/components/app-shell.html +12 -0
- package/examples/components/app-toolbar.html +38 -0
- package/examples/components/avatar-group.html +27 -0
- package/examples/components/breadcrumbs.html +26 -0
- package/examples/components/bulk-bar.html +22 -0
- package/examples/components/code-block.html +12 -0
- package/examples/components/code-editor.html +25 -0
- package/examples/components/column-manager.html +9 -0
- package/examples/components/combobox.html +13 -0
- package/examples/components/context-hypercms.css +41 -0
- package/examples/components/context-hypercms.html +40 -0
- package/examples/components/context-hypercms.js +62 -0
- package/examples/components/context-quickcrop.html +137 -0
- package/examples/components/diff-view.html +9 -0
- package/examples/components/drawer.html +11 -0
- package/examples/components/editable-grid-cells.html +9 -0
- package/examples/components/file-browser.html +53 -0
- package/examples/components/file-tree.html +34 -0
- package/examples/components/files-table.html +80 -0
- package/examples/components/filter-builder.html +11 -0
- package/examples/components/form-error-summary.html +11 -0
- package/examples/components/index.html +51 -0
- package/examples/components/inline-edit.html +42 -0
- package/examples/components/kanban-board.html +42 -0
- package/examples/components/manifest.json +3279 -0
- package/examples/components/modal.html +11 -0
- package/examples/components/multi-select-picker.html +13 -0
- package/examples/components/pagination.html +32 -0
- package/examples/components/persistent-action-banner.html +23 -0
- package/examples/components/popconfirm.html +13 -0
- package/examples/components/popover-menu.html +13 -0
- package/examples/components/reference/baseline/DECISIONS.md +85 -0
- package/examples/components/reference/baseline/README.md +58 -0
- package/examples/components/reference/baseline/bevel-fonts.css +16 -0
- package/examples/components/reference/baseline/bevel.css +1339 -0
- package/examples/components/reference/baseline/bevel.js +496 -0
- package/examples/components/reference/baseline/fonts/LICENSE-ibm-plex-mono.txt +93 -0
- package/examples/components/reference/baseline/fonts/LICENSE-ibm-plex-sans.txt +93 -0
- package/examples/components/reference/baseline/fonts/LICENSE-newsreader.txt +93 -0
- package/examples/components/reference/baseline/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/examples/components/reference/baseline/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/examples/components/reference/baseline/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- package/examples/components/reference/baseline/fonts/ibm-plex-sans-latin-400-italic.woff2 +0 -0
- package/examples/components/reference/baseline/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- package/examples/components/reference/baseline/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- package/examples/components/reference/baseline/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- package/examples/components/reference/baseline/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
- package/examples/components/reference/baseline/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
- package/examples/components/reference/fable/IMPROVEMENTS.md +106 -0
- package/examples/components/reference/fable/PARENT-REPAIRS.md +17 -0
- package/examples/components/reference/fable/__pycache__/build_repairs.cpython-311.pyc +0 -0
- package/examples/components/reference/fable/__pycache__/kanban.cpython-311.pyc +0 -0
- package/examples/components/reference/fable/as-authored/IMPROVEMENTS-completed.md +106 -0
- package/examples/components/reference/fable/as-authored/base-astra.css +17 -0
- package/examples/components/reference/fable/as-authored/base-parent.css +416 -0
- package/examples/components/reference/fable/as-authored/build.py +858 -0
- package/examples/components/reference/fable/as-authored/fable.css +823 -0
- package/examples/components/reference/fable/as-authored/kanban-completed.css +235 -0
- package/examples/components/reference/fable/as-authored/kanban-completed.html +53 -0
- package/examples/components/reference/fable/as-authored/kanban-completed.py +212 -0
- package/examples/components/reference/fable/as-authored/kanban.py +207 -0
- package/examples/components/reference/fable/base-astra.css +17 -0
- package/examples/components/reference/fable/base-parent.css +416 -0
- package/examples/components/reference/fable/build.py +858 -0
- package/examples/components/reference/fable/build_repairs.py +25 -0
- package/examples/components/reference/fable/components/activity-feed.html +3 -0
- package/examples/components/reference/fable/components/app-toolbar.html +3 -0
- package/examples/components/reference/fable/components/avatar-group.html +4 -0
- package/examples/components/reference/fable/components/breadcrumbs.html +3 -0
- package/examples/components/reference/fable/components/code-block.html +9 -0
- package/examples/components/reference/fable/components/column-manager.html +3 -0
- package/examples/components/reference/fable/components/combobox.html +6 -0
- package/examples/components/reference/fable/components/context-hypercms.html +3 -0
- package/examples/components/reference/fable/components/context-quickcrop.html +4 -0
- package/examples/components/reference/fable/components/diff-view.html +13 -0
- package/examples/components/reference/fable/components/drawer.html +3 -0
- package/examples/components/reference/fable/components/editable-grid-cells.html +3 -0
- package/examples/components/reference/fable/components/filter-builder.html +3 -0
- package/examples/components/reference/fable/components/form-error-summary.html +4 -0
- package/examples/components/reference/fable/components/inline-edit.html +3 -0
- package/examples/components/reference/fable/components/kanban-board.html +53 -0
- package/examples/components/reference/fable/components/modal.html +3 -0
- package/examples/components/reference/fable/components/multi-select-picker.html +5 -0
- package/examples/components/reference/fable/components/pagination.html +3 -0
- package/examples/components/reference/fable/components/persistent-action-banner.html +4 -0
- package/examples/components/reference/fable/components/popconfirm.html +5 -0
- package/examples/components/reference/fable/components/popover-menu.html +3 -0
- package/examples/components/reference/fable/components/rich-text-toolbar.html +3 -0
- package/examples/components/reference/fable/components/save-status-indicator.html +2 -0
- package/examples/components/reference/fable/components/selectable-cards.html +3 -0
- package/examples/components/reference/fable/components/settings-rows.html +3 -0
- package/examples/components/reference/fable/components/sidebar-nav.html +4 -0
- package/examples/components/reference/fable/components/small-charts.html +5 -0
- package/examples/components/reference/fable/components/split-button.html +4 -0
- package/examples/components/reference/fable/components/split-panes.html +8 -0
- package/examples/components/reference/fable/components/stat-tiles.html +3 -0
- package/examples/components/reference/fable/components/toast-stack.html +5 -0
- package/examples/components/reference/fable/components/tree-view.html +4 -0
- package/examples/components/reference/fable/components/upload-dropzone.html +6 -0
- package/examples/components/reference/fable/components/wizard-shell.html +3 -0
- package/examples/components/reference/fable/coverage.json +414 -0
- package/examples/components/reference/fable/fable.css +823 -0
- package/examples/components/reference/fable/index.html +5 -0
- package/examples/components/reference/fable/kanban.css +237 -0
- package/examples/components/reference/fable/kanban.py +212 -0
- package/examples/components/reference/fable/parent-repairs.css +9 -0
- package/examples/components/reference/fixtures/data.json +102 -0
- package/examples/components/reference/fixtures/host.js +47 -0
- package/examples/components/reference/fixtures/static-host.js +39 -0
- package/examples/components/reference/fixtures/workspace.jpg +0 -0
- package/examples/components/reference/parent/CHANGES.md +179 -0
- package/examples/components/reference/parent/__pycache__/kanban.cpython-311.pyc +0 -0
- package/examples/components/reference/parent/base-astra.css +17 -0
- package/examples/components/reference/parent/base-parent.css +416 -0
- package/examples/components/reference/parent/build.py +148 -0
- package/examples/components/reference/parent/components/activity-feed.html +2 -0
- package/examples/components/reference/parent/components/app-toolbar.html +2 -0
- package/examples/components/reference/parent/components/avatar-group.html +4 -0
- package/examples/components/reference/parent/components/breadcrumbs.html +2 -0
- package/examples/components/reference/parent/components/code-block.html +8 -0
- package/examples/components/reference/parent/components/column-manager.html +2 -0
- package/examples/components/reference/parent/components/combobox.html +6 -0
- package/examples/components/reference/parent/components/context-hypercms.html +2 -0
- package/examples/components/reference/parent/components/context-quickcrop.html +4 -0
- package/examples/components/reference/parent/components/diff-view.html +4 -0
- package/examples/components/reference/parent/components/drawer.html +2 -0
- package/examples/components/reference/parent/components/editable-grid-cells.html +2 -0
- package/examples/components/reference/parent/components/filter-builder.html +2 -0
- package/examples/components/reference/parent/components/form-error-summary.html +2 -0
- package/examples/components/reference/parent/components/inline-edit.html +2 -0
- package/examples/components/reference/parent/components/kanban-board.html +84 -0
- package/examples/components/reference/parent/components/modal.html +2 -0
- package/examples/components/reference/parent/components/multi-select-picker.html +5 -0
- package/examples/components/reference/parent/components/pagination.html +2 -0
- package/examples/components/reference/parent/components/persistent-action-banner.html +4 -0
- package/examples/components/reference/parent/components/popconfirm.html +5 -0
- package/examples/components/reference/parent/components/popover-menu.html +2 -0
- package/examples/components/reference/parent/components/rich-text-toolbar.html +2 -0
- package/examples/components/reference/parent/components/save-status-indicator.html +2 -0
- package/examples/components/reference/parent/components/selectable-cards.html +2 -0
- package/examples/components/reference/parent/components/settings-rows.html +2 -0
- package/examples/components/reference/parent/components/sidebar-nav.html +4 -0
- package/examples/components/reference/parent/components/small-charts.html +2 -0
- package/examples/components/reference/parent/components/split-button.html +5 -0
- package/examples/components/reference/parent/components/split-panes.html +8 -0
- package/examples/components/reference/parent/components/stat-tiles.html +2 -0
- package/examples/components/reference/parent/components/toast-stack.html +5 -0
- package/examples/components/reference/parent/components/tree-view.html +4 -0
- package/examples/components/reference/parent/components/upload-dropzone.html +6 -0
- package/examples/components/reference/parent/components/wizard-shell.html +2 -0
- package/examples/components/reference/parent/coverage.json +414 -0
- package/examples/components/reference/parent/index.html +1 -0
- package/examples/components/reference/parent/kanban.css +114 -0
- package/examples/components/reference/parent/kanban.py +55 -0
- package/examples/components/reference/parent/refinement.css +53 -0
- package/examples/components/reference/selected/build.py +70 -0
- package/examples/components/reference/selected/components/activity-feed.html +3 -0
- package/examples/components/reference/selected/components/app-toolbar.html +3 -0
- package/examples/components/reference/selected/components/avatar-group.html +4 -0
- package/examples/components/reference/selected/components/breadcrumbs.html +3 -0
- package/examples/components/reference/selected/components/code-block.html +9 -0
- package/examples/components/reference/selected/components/column-manager.html +3 -0
- package/examples/components/reference/selected/components/combobox.html +6 -0
- package/examples/components/reference/selected/components/context-hypercms.html +3 -0
- package/examples/components/reference/selected/components/context-quickcrop.html +4 -0
- package/examples/components/reference/selected/components/diff-view.html +13 -0
- package/examples/components/reference/selected/components/drawer.html +3 -0
- package/examples/components/reference/selected/components/editable-grid-cells.html +3 -0
- package/examples/components/reference/selected/components/filter-builder.html +3 -0
- package/examples/components/reference/selected/components/form-error-summary.html +4 -0
- package/examples/components/reference/selected/components/inline-edit.html +3 -0
- package/examples/components/reference/selected/components/kanban-board.html +53 -0
- package/examples/components/reference/selected/components/modal.html +2 -0
- package/examples/components/reference/selected/components/multi-select-picker.html +5 -0
- package/examples/components/reference/selected/components/pagination.html +3 -0
- package/examples/components/reference/selected/components/persistent-action-banner.html +4 -0
- package/examples/components/reference/selected/components/popconfirm.html +5 -0
- package/examples/components/reference/selected/components/popover-menu.html +3 -0
- package/examples/components/reference/selected/components/rich-text-toolbar.html +3 -0
- package/examples/components/reference/selected/components/save-status-indicator.html +2 -0
- package/examples/components/reference/selected/components/selectable-cards.html +3 -0
- package/examples/components/reference/selected/components/settings-rows.html +3 -0
- package/examples/components/reference/selected/components/sidebar-nav.html +4 -0
- package/examples/components/reference/selected/components/small-charts.html +5 -0
- package/examples/components/reference/selected/components/split-button.html +4 -0
- package/examples/components/reference/selected/components/split-panes.html +8 -0
- package/examples/components/reference/selected/components/stat-tiles.html +3 -0
- package/examples/components/reference/selected/components/toast-stack.html +5 -0
- package/examples/components/reference/selected/components/tree-view.html +4 -0
- package/examples/components/reference/selected/components/upload-dropzone.html +6 -0
- package/examples/components/reference/selected/components/wizard-shell.html +3 -0
- package/examples/components/reference/selected/corrections.css +11 -0
- package/examples/components/reference/selected/coverage.json +413 -0
- package/examples/components/reference/selected/gallery-build.mjs +49 -0
- package/examples/components/reference/selected/gallery-spec.json +12 -0
- package/examples/components/reference/selected/index.html +1 -0
- package/examples/components/reference/selected/kanban-comparison.json +6 -0
- package/examples/components/reference/selected/manifest.json +476 -0
- package/examples/components/reference/sha256.json +142 -0
- package/examples/components/reference/sources/diagonal-close.svg +1 -0
- package/examples/components/reference/sources/hyperclay/public-assets/css/hyperclay-app-source.css +614 -0
- package/examples/components/reference/sources/hyperclay/public-assets/js/app.js +94 -0
- package/examples/components/reference/sources/hyperclayjs/src/ui/theModal.js +897 -0
- package/examples/components/reference/sources/hypercms/scripts/build-theme.js +203 -0
- package/examples/components/reference/sources/hypercms/src/anchor.js +24 -0
- package/examples/components/reference/sources/hypercms/src/apply-loop.js +167 -0
- package/examples/components/reference/sources/hypercms/src/diagnostics.js +44 -0
- package/examples/components/reference/sources/hypercms/src/enhance.js +173 -0
- package/examples/components/reference/sources/hypercms/src/events.js +955 -0
- package/examples/components/reference/sources/hypercms/src/form-builder.js +518 -0
- package/examples/components/reference/sources/hypercms/src/form-rules.js +208 -0
- package/examples/components/reference/sources/hypercms/src/hooks.js +100 -0
- package/examples/components/reference/sources/hypercms/src/hypercms-bundle.js +10 -0
- package/examples/components/reference/sources/hypercms/src/hypercms.js +347 -0
- package/examples/components/reference/sources/hypercms/src/morph.js +10 -0
- package/examples/components/reference/sources/hypercms/src/path.js +61 -0
- package/examples/components/reference/sources/hypercms/src/place.js +131 -0
- package/examples/components/reference/sources/hypercms/src/platform.js +131 -0
- package/examples/components/reference/sources/hypercms/src/refresh.js +87 -0
- package/examples/components/reference/sources/hypercms/src/richclay-bridge.js +140 -0
- package/examples/components/reference/sources/hypercms/src/row-identity.js +111 -0
- package/examples/components/reference/sources/hypercms/src/scaffold.js +10 -0
- package/examples/components/reference/sources/hypercms/src/session.js +303 -0
- package/examples/components/reference/sources/hypercms/src/shell.js +248 -0
- package/examples/components/reference/sources/hypercms/src/styles.css +298 -0
- package/examples/components/reference/sources/hypercms/src/targets.js +144 -0
- package/examples/components/reference/sources/hypercms/src/templates.js +656 -0
- package/examples/components/reference/sources/hypercms/src/theme/pixel-quiet.overrides.css +761 -0
- package/examples/components/reference/sources/hypercms/src/theme.generated.css +1772 -0
- package/examples/components/reference/sources/hypercms/src/toggle.js +666 -0
- package/examples/components/reference/sources/hypercms/src/unresolved.js +211 -0
- package/examples/components/reference/sources/hypercms/src/vendor/mirk.vendor.js +212 -0
- package/examples/components/reference/sources/hypercms/src/views/inline-layer.js +421 -0
- package/examples/components/reference/sources/hypercms/src/views/inline.js +1072 -0
- package/examples/components/reference/sources/hypercms/src/views/sidebar.js +97 -0
- package/examples/components/reference/sources/modal-reference.html +301 -0
- package/examples/components/reference/sources/quickcrop/quickcrop.css +124 -0
- package/examples/components/reference/sources/quickcrop/quickcrop.js +400 -0
- package/examples/components/reference/sources/richclay/richclay.css +416 -0
- package/examples/components/reference/sources/richclay/richclay.js +2962 -0
- package/examples/components/reference/sources/richclay/src/a11y.js +67 -0
- package/examples/components/reference/sources/richclay/src/browser-global.js +7 -0
- package/examples/components/reference/sources/richclay/src/buttons.js +254 -0
- package/examples/components/reference/sources/richclay/src/hyperclay.js +470 -0
- package/examples/components/reference/sources/richclay/src/normalize.js +380 -0
- package/examples/components/reference/sources/richclay/src/richclay.js +1356 -0
- package/examples/components/reference/sources/richclay/src/sanitize.js +189 -0
- package/examples/components/reference/sources/richclay/src/styles.js +23 -0
- package/examples/components/reference/sources/richclay/src/toolbar-float.js +175 -0
- package/examples/components/reference/sources/richclay/src/toolbar.js +394 -0
- package/examples/components/reference/sources/richclay/src/vendor-bundle.js +15 -0
- package/examples/components/reference/sources/richclay/vendor/DOMPURIFY-LICENSE +202 -0
- package/examples/components/reference/sources/richclay/vendor/DOMPURIFY-LICENSE-MPL +373 -0
- package/examples/components/reference/sources/richclay/vendor/SQUIRE-LICENSE +21 -0
- package/examples/components/reference/sources/richclay/vendor/purify.es.mjs +2249 -0
- package/examples/components/reference/sources/richclay/vendor/purify.min.js +3 -0
- package/examples/components/reference/sources/richclay/vendor/squire.js +22 -0
- package/examples/components/reference/sources/richclay/vendor/squire.mjs +22 -0
- package/examples/components/reference/sources/richclay/vendor/squire.provenance.json +19 -0
- package/examples/components/rich-text-toolbar.html +43 -0
- package/examples/components/save-status-indicator.html +11 -0
- package/examples/components/selectable-cards.html +9 -0
- package/examples/components/settings-rows.html +10 -0
- package/examples/components/sidebar-nav.html +9 -0
- package/examples/components/small-charts.html +21 -0
- package/examples/components/split-button.html +13 -0
- package/examples/components/split-panes-editor.html +6 -0
- package/examples/components/split-panes.html +9 -0
- package/examples/components/stat-tiles.html +23 -0
- package/examples/components/toast-stack.html +21 -0
- package/examples/components/tree-view.html +9 -0
- package/examples/components/upload-dropzone.html +88 -0
- package/examples/components/wizard-shell.html +50 -0
- package/examples/datastar/index.html +38 -0
- package/examples/datastar/server.mjs +31 -0
- package/examples/fixtures/field-guide.pdf +14 -0
- package/examples/fixtures/workspace.jpg +0 -0
- package/examples/setup-peers.mjs +35 -0
- package/fonts/LICENSE-ibm-plex-mono.txt +93 -0
- package/fonts/LICENSE-ibm-plex-sans.txt +93 -0
- package/fonts/LICENSE-newsreader.txt +93 -0
- package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-400-italic.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- package/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
- package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
- package/icons/LICENSE +21 -0
- package/icons/house.json +71 -0
- package/icons/icons.d.ts +17 -0
- package/icons/icons.js +648 -0
- package/icons/icons.svg +126 -0
- package/icons/marks/build-tray.source.mjs +45 -0
- package/icons/marks/hyperclay-local.svg +11 -0
- package/icons/phosphor.json +53 -0
- package/icons/round.json +8 -0
- package/integrations/datastar.js +202 -0
- package/integrations/quickcrop.css +26 -0
- package/integrations/quickcrop.js +56 -0
- package/integrations/richclay.css +100 -0
- package/integrations/richclay.js +131 -0
- package/package.json +630 -0
- package/react/README.md +244 -0
- package/src/README.md +83 -0
- package/vendor/datastar/datastar-1.0.3.js +10 -0
- package/vendor/datastar/provenance.json +6 -0
- package/vendor/floating-ui/LICENSE-core +20 -0
- package/vendor/floating-ui/LICENSE-dom +20 -0
- package/vendor/floating-ui/floating-ui.core.umd.min.js +1 -0
- package/vendor/floating-ui/floating-ui.dom.umd.min.js +1 -0
- package/vendor/floating-ui/provenance.json +17 -0
- package/vendor/richclay/LICENSE +16 -0
- package/vendor/richclay/THIRD-PARTY-NOTICES.md +45 -0
- package/vendor/richclay/provenance.json +17 -0
- package/vendor/richclay/richclay.css +404 -0
- package/vendor/richclay/richclay.js +2940 -0
- package/vendor/richclay/vendor/DOMPURIFY-LICENSE +202 -0
- package/vendor/richclay/vendor/DOMPURIFY-LICENSE-MPL +373 -0
- package/vendor/richclay/vendor/SQUIRE-LICENSE +21 -0
- package/vendor/richclay/vendor/purify.min.js +3 -0
- package/vendor/richclay/vendor/squire.js +22 -0
- package/vendor/richclay/vendor/squire.provenance.json +30 -0
package/bevel.js
ADDED
|
@@ -0,0 +1,2126 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* bevel.js — the delegated runtime for the bevel UI kit, 0.1.0.
|
|
3
|
+
* https://github.com/panphora/bevel
|
|
4
|
+
*
|
|
5
|
+
* One listener per interaction on `document`. No init(), no MutationObserver:
|
|
6
|
+
* every current AND future element is handled, so injected, re-rendered, and
|
|
7
|
+
* saved-and-reopened markup just works. Idempotent; safe to include twice.
|
|
8
|
+
*
|
|
9
|
+
* The markup carries the initial state (chips are real DOM, the slider fill is
|
|
10
|
+
* an inline --bevel-value, native inputs hold their own value), so the page is
|
|
11
|
+
* correct before this script runs. The script does two things on top:
|
|
12
|
+
* 1. Drives the transitions (steppers, chips, tabs, drag order, previews,
|
|
13
|
+
* the grid's sort, select-all, expand, search and pages).
|
|
14
|
+
* 2. Mirrors live control state back into attributes (checked, selected,
|
|
15
|
+
* value, the textarea's text), so document.documentElement.outerHTML
|
|
16
|
+
* round-trips every visible state of every bevel control.
|
|
17
|
+
*/
|
|
18
|
+
(function () {
|
|
19
|
+
if (window.__bevel) return;
|
|
20
|
+
window.__bevel = true;
|
|
21
|
+
|
|
22
|
+
const X_SVG = '<svg aria-hidden="true" viewBox="0 0 256 256" fill="currentColor"><path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"/></svg>';
|
|
23
|
+
|
|
24
|
+
const on = (type, selector, handler, options) => {
|
|
25
|
+
document.addEventListener(type, (e) => {
|
|
26
|
+
const target = e.target instanceof Element ? e.target.closest(selector) : null;
|
|
27
|
+
if (target) handler(e, target);
|
|
28
|
+
}, options);
|
|
29
|
+
};
|
|
30
|
+
const emit = (el, name, detail) => el.dispatchEvent(new CustomEvent(name, { bubbles: true, detail }));
|
|
31
|
+
const nativeChange = (input) => {
|
|
32
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
33
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
34
|
+
};
|
|
35
|
+
const clearInput = (input) => { input.value = ""; input.removeAttribute("value"); };
|
|
36
|
+
|
|
37
|
+
/* ---------- state mirroring: the outerHTML contract ---------- */
|
|
38
|
+
const MIRROR_VALUE = ".bevel-input, .bevel-number__input, .bevel-slider__input, .bevel-date__field, .bevel-sortable__field, .bevel-tags__input";
|
|
39
|
+
on("input", MIRROR_VALUE, (e, input) => input.setAttribute("value", input.value));
|
|
40
|
+
on("input", ".bevel-textarea", (e, area) => { area.textContent = area.value; });
|
|
41
|
+
on("change", ".bevel-checkbox input, .bevel-toggle input, .bevel-table__pick input", (e, input) => {
|
|
42
|
+
input.toggleAttribute("checked", input.checked);
|
|
43
|
+
});
|
|
44
|
+
on("change", ".bevel-radio input", (e, input) => {
|
|
45
|
+
for (const r of document.querySelectorAll('input[type="radio"]')) {
|
|
46
|
+
if (r.name === input.name && r.form === input.form) r.toggleAttribute("checked", r.checked);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
on("change", ".bevel-select__field", (e, select) => {
|
|
50
|
+
for (const o of select.options) o.toggleAttribute("selected", o.selected);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/* ---------- number ---------- */
|
|
54
|
+
on("click", ".bevel-number__step", (e, step) => {
|
|
55
|
+
const input = step.closest(".bevel-number")?.querySelector(".bevel-number__input");
|
|
56
|
+
if (!input || input.disabled) return;
|
|
57
|
+
try { step.dataset.step === "down" ? input.stepDown() : input.stepUp(); } catch { return; }
|
|
58
|
+
nativeChange(input);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/* ---------- slider ---------- */
|
|
62
|
+
const percent = (input) => {
|
|
63
|
+
const min = input.min === "" ? 0 : Number(input.min);
|
|
64
|
+
const max = input.max === "" ? 100 : Number(input.max);
|
|
65
|
+
if (!(max > min)) return "0%";
|
|
66
|
+
const pct = ((Number(input.value) - min) / (max - min)) * 100;
|
|
67
|
+
return `${+Math.min(100, Math.max(0, pct)).toFixed(2)}%`;
|
|
68
|
+
};
|
|
69
|
+
const syncSlider = (input) => {
|
|
70
|
+
const wrap = input.closest(".bevel-slider");
|
|
71
|
+
if (!wrap) return;
|
|
72
|
+
wrap.style.setProperty("--bevel-value", percent(input));
|
|
73
|
+
const out = wrap.closest(".bevel-sliderrow")?.querySelector("output, .bevel-sliderrow__value");
|
|
74
|
+
if (out) { if (out.tagName === "OUTPUT") out.value = input.value; else out.textContent = input.value; }
|
|
75
|
+
};
|
|
76
|
+
on("input", ".bevel-slider__input", (e, input) => syncSlider(input));
|
|
77
|
+
|
|
78
|
+
/* ---------- file ---------- */
|
|
79
|
+
const fileName = (wrap) => wrap.querySelector(".bevel-file__name");
|
|
80
|
+
const clearFile = (wrap, name) => {
|
|
81
|
+
name.textContent = name.dataset.empty || "No file chosen";
|
|
82
|
+
name.removeAttribute("data-filled");
|
|
83
|
+
if (name.tagName === "A") name.removeAttribute("href");
|
|
84
|
+
wrap.querySelector(".bevel-file__remove")?.remove();
|
|
85
|
+
};
|
|
86
|
+
on("change", ".bevel-file__input", (e, input) => {
|
|
87
|
+
const wrap = input.closest(".bevel-file");
|
|
88
|
+
const name = wrap && fileName(wrap);
|
|
89
|
+
if (!name) return;
|
|
90
|
+
if (!name.dataset.empty && !name.hasAttribute("data-filled")) name.dataset.empty = name.textContent;
|
|
91
|
+
const files = input.files;
|
|
92
|
+
if (files && files.length) {
|
|
93
|
+
name.textContent = files.length === 1 ? files[0].name : `${files.length} files`;
|
|
94
|
+
name.setAttribute("data-filled", "");
|
|
95
|
+
if (!wrap.querySelector(".bevel-file__remove")) {
|
|
96
|
+
const remove = document.createElement("button");
|
|
97
|
+
remove.type = "button";
|
|
98
|
+
remove.className = "bevel-file__remove";
|
|
99
|
+
remove.setAttribute("aria-label", "Remove file");
|
|
100
|
+
remove.innerHTML = X_SVG;
|
|
101
|
+
name.after(remove);
|
|
102
|
+
}
|
|
103
|
+
} else clearFile(wrap, name);
|
|
104
|
+
});
|
|
105
|
+
on("click", ".bevel-file__remove", (e, button) => {
|
|
106
|
+
const wrap = button.closest(".bevel-file");
|
|
107
|
+
if (!wrap) return;
|
|
108
|
+
const input = wrap.querySelector(".bevel-file__input");
|
|
109
|
+
const name = fileName(wrap);
|
|
110
|
+
if (input) { input.value = ""; nativeChange(input); }
|
|
111
|
+
if (name && name.hasAttribute("data-filled")) clearFile(wrap, name);
|
|
112
|
+
emit(wrap, "bevel:remove");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/* ---------- image ---------- */
|
|
116
|
+
const showPreview = (wrap, src) => {
|
|
117
|
+
const img = wrap.querySelector(".bevel-image__preview");
|
|
118
|
+
const placeholder = wrap.querySelector(".bevel-image__placeholder");
|
|
119
|
+
if (!img) return null;
|
|
120
|
+
if (img.dataset.blob) { URL.revokeObjectURL(img.dataset.blob); delete img.dataset.blob; }
|
|
121
|
+
if (src) { img.src = src; img.hidden = false; if (placeholder) placeholder.hidden = true; }
|
|
122
|
+
else { img.removeAttribute("src"); img.hidden = true; if (placeholder) placeholder.hidden = false; }
|
|
123
|
+
return img;
|
|
124
|
+
};
|
|
125
|
+
on("change", ".bevel-image__input", (e, input) => {
|
|
126
|
+
const wrap = input.closest(".bevel-image");
|
|
127
|
+
const file = input.files && input.files[0];
|
|
128
|
+
if (!wrap) return;
|
|
129
|
+
if (!file) { showPreview(wrap, null); return; }
|
|
130
|
+
const url = URL.createObjectURL(file);
|
|
131
|
+
const img = showPreview(wrap, url);
|
|
132
|
+
if (img) img.dataset.blob = url; else URL.revokeObjectURL(url);
|
|
133
|
+
});
|
|
134
|
+
on("click", ".bevel-image__remove", (e, button) => {
|
|
135
|
+
const wrap = button.closest(".bevel-image");
|
|
136
|
+
const input = wrap?.querySelector(".bevel-image__input");
|
|
137
|
+
if (!wrap) return;
|
|
138
|
+
if (input) input.value = "";
|
|
139
|
+
showPreview(wrap, null);
|
|
140
|
+
emit(wrap, "bevel:remove");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
/* ---------- tags ---------- */
|
|
144
|
+
const addTag = (wrap, raw) => {
|
|
145
|
+
const text = raw.trim();
|
|
146
|
+
if (!text) return false;
|
|
147
|
+
const taken = [...wrap.querySelectorAll(".bevel-tags__chip input")].some((i) => i.value.toLowerCase() === text.toLowerCase());
|
|
148
|
+
if (taken) return false;
|
|
149
|
+
const chip = document.createElement("span");
|
|
150
|
+
chip.className = "bevel-tags__chip";
|
|
151
|
+
chip.append(text);
|
|
152
|
+
const hidden = document.createElement("input");
|
|
153
|
+
hidden.type = "hidden";
|
|
154
|
+
hidden.name = wrap.dataset.name || "tags[]";
|
|
155
|
+
hidden.value = text;
|
|
156
|
+
const remove = document.createElement("button");
|
|
157
|
+
remove.type = "button";
|
|
158
|
+
remove.className = "bevel-tags__remove";
|
|
159
|
+
remove.setAttribute("aria-label", `Remove ${text}`);
|
|
160
|
+
remove.textContent = "×";
|
|
161
|
+
chip.append(hidden, remove);
|
|
162
|
+
wrap.querySelector(".bevel-tags__input").before(chip);
|
|
163
|
+
emit(wrap, "bevel:tags");
|
|
164
|
+
return true;
|
|
165
|
+
};
|
|
166
|
+
on("keydown", ".bevel-tags__input", (e, input) => {
|
|
167
|
+
const wrap = input.closest(".bevel-tags");
|
|
168
|
+
if (!wrap) return;
|
|
169
|
+
if (e.key === "Enter" || e.key === ",") {
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
if (addTag(wrap, input.value) || !input.value.trim()) clearInput(input);
|
|
172
|
+
} else if (e.key === "Backspace" && !input.value) {
|
|
173
|
+
const chips = wrap.querySelectorAll(".bevel-tags__chip");
|
|
174
|
+
const last = chips[chips.length - 1];
|
|
175
|
+
if (last) { last.remove(); emit(wrap, "bevel:tags"); }
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
on("focusout", ".bevel-tags__input", (e, input) => {
|
|
179
|
+
const wrap = input.closest(".bevel-tags");
|
|
180
|
+
if (wrap && input.value.trim() && addTag(wrap, input.value)) clearInput(input);
|
|
181
|
+
});
|
|
182
|
+
on("click", ".bevel-tags__remove", (e, button) => {
|
|
183
|
+
const wrap = button.closest(".bevel-tags");
|
|
184
|
+
button.closest(".bevel-tags__chip")?.remove();
|
|
185
|
+
if (wrap) emit(wrap, "bevel:tags");
|
|
186
|
+
});
|
|
187
|
+
on("click", ".bevel-tags", (e, wrap) => {
|
|
188
|
+
if (e.target === wrap) wrap.querySelector(".bevel-tags__input")?.focus();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
/* ---------- sortable ---------- */
|
|
192
|
+
let drag = null;
|
|
193
|
+
const rows = (list) => [...list.querySelectorAll(":scope > .bevel-sortable__item")];
|
|
194
|
+
on("pointerdown", ".bevel-sortable__grip", (e, grip) => {
|
|
195
|
+
if (e.pointerType === "mouse" && e.button !== 0) return;
|
|
196
|
+
const item = grip.closest(".bevel-sortable__item");
|
|
197
|
+
const list = item?.parentElement;
|
|
198
|
+
if (!list) return;
|
|
199
|
+
drag = { item, list, id: e.pointerId, from: rows(list).indexOf(item) };
|
|
200
|
+
item.classList.add("is-dragging");
|
|
201
|
+
grip.setPointerCapture(e.pointerId);
|
|
202
|
+
e.preventDefault();
|
|
203
|
+
});
|
|
204
|
+
document.addEventListener("pointermove", (e) => {
|
|
205
|
+
if (!drag || e.pointerId !== drag.id) return;
|
|
206
|
+
const { item, list } = drag;
|
|
207
|
+
for (const sibling of rows(list)) {
|
|
208
|
+
if (sibling === item) continue;
|
|
209
|
+
const box = sibling.getBoundingClientRect();
|
|
210
|
+
const mid = box.top + box.height / 2;
|
|
211
|
+
const follows = item.compareDocumentPosition(sibling) & Node.DOCUMENT_POSITION_FOLLOWING;
|
|
212
|
+
if (follows && e.clientY > mid) sibling.after(item);
|
|
213
|
+
else if (!follows && e.clientY < mid) sibling.before(item);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
const endDrag = (e) => {
|
|
217
|
+
if (!drag || e.pointerId !== drag.id) return;
|
|
218
|
+
const { item, list, from } = drag;
|
|
219
|
+
item.classList.remove("is-dragging");
|
|
220
|
+
drag = null;
|
|
221
|
+
const to = rows(list).indexOf(item);
|
|
222
|
+
if (to !== from) emit(list, "bevel:reorder", { from, to });
|
|
223
|
+
};
|
|
224
|
+
document.addEventListener("pointerup", endDrag);
|
|
225
|
+
document.addEventListener("pointercancel", endDrag);
|
|
226
|
+
|
|
227
|
+
/* ---------- chip ---------- */
|
|
228
|
+
on("click", "[data-bevel-chip]", (e, button) => {
|
|
229
|
+
const chip = button.closest(".bevel-chip");
|
|
230
|
+
if (!chip) return;
|
|
231
|
+
const action = button.dataset.bevelChip;
|
|
232
|
+
if (action === "open") chip.classList.add("bevel-chip--open");
|
|
233
|
+
else if (action === "collapse") chip.classList.remove("bevel-chip--open");
|
|
234
|
+
else if (action === "changes") chip.querySelector(".bevel-chip__panel")?.classList.toggle("is-changes");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
/* ---------- tabs ---------- */
|
|
238
|
+
const selectTab = (tab) => {
|
|
239
|
+
const list = tab.closest(".bevel-tabs");
|
|
240
|
+
if (!list) return;
|
|
241
|
+
for (const t of list.querySelectorAll(".bevel-tabs__tab")) {
|
|
242
|
+
const selected = t === tab;
|
|
243
|
+
t.setAttribute("aria-selected", String(selected));
|
|
244
|
+
t.tabIndex = selected ? 0 : -1;
|
|
245
|
+
const id = t.getAttribute("aria-controls");
|
|
246
|
+
const panel = id && document.getElementById(id);
|
|
247
|
+
if (panel) panel.hidden = !selected;
|
|
248
|
+
}
|
|
249
|
+
emit(list, "bevel:tab", { tab });
|
|
250
|
+
};
|
|
251
|
+
on("click", ".bevel-tabs__tab", (e, tab) => selectTab(tab));
|
|
252
|
+
on("keydown", ".bevel-tabs__tab", (e, tab) => {
|
|
253
|
+
const list = tab.closest(".bevel-tabs");
|
|
254
|
+
if (!list) return;
|
|
255
|
+
const tabs = [...list.querySelectorAll(".bevel-tabs__tab")];
|
|
256
|
+
const i = tabs.indexOf(tab);
|
|
257
|
+
const next = e.key === "ArrowRight" ? tabs[(i + 1) % tabs.length]
|
|
258
|
+
: e.key === "ArrowLeft" ? tabs[(i - 1 + tabs.length) % tabs.length]
|
|
259
|
+
: e.key === "Home" ? tabs[0]
|
|
260
|
+
: e.key === "End" ? tabs[tabs.length - 1]
|
|
261
|
+
: null;
|
|
262
|
+
if (!next) return;
|
|
263
|
+
e.preventDefault();
|
|
264
|
+
next.focus();
|
|
265
|
+
selectTab(next);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
/* ---------- segmented control ---------- */
|
|
269
|
+
on("click", ".bevel-seg > .bevel-button[aria-pressed]", (e, button) => {
|
|
270
|
+
const group = button.parentElement;
|
|
271
|
+
for (const b of group.querySelectorAll(":scope > .bevel-button[aria-pressed]")) b.setAttribute("aria-pressed", String(b === button));
|
|
272
|
+
emit(group, "bevel:seg", { value: button.dataset.value ?? button.textContent.trim(), button });
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
/* ---------- grid ---------- */
|
|
276
|
+
/* Sort, select-all, expand, search and pages. Rows never leave the DOM:
|
|
277
|
+
search and paging set hidden, sorting reorders, and every state is an
|
|
278
|
+
attribute (aria-sort, checked, aria-expanded, data-page, value). The
|
|
279
|
+
select-all's mixed flag and a detail row's hidden are derived from the
|
|
280
|
+
rows on every layout, so a saved page recovers them. */
|
|
281
|
+
const gridOf = (el) => el.closest(".bevel-grid");
|
|
282
|
+
const gridRows = (grid) => [...grid.querySelectorAll(".bevel-grid__table > tbody > .bevel-grid__row")];
|
|
283
|
+
const detailOf = (row) => {
|
|
284
|
+
const next = row.nextElementSibling;
|
|
285
|
+
return next && next.classList.contains("bevel-grid__detail") ? next : null;
|
|
286
|
+
};
|
|
287
|
+
const isOpen = (row) => row.querySelector(".bevel-grid__expand")?.getAttribute("aria-expanded") === "true";
|
|
288
|
+
const pageButton = (n, current, standalone = false) => {
|
|
289
|
+
const b = document.createElement("button");
|
|
290
|
+
b.type = "button";
|
|
291
|
+
b.className = `bevel-button bevel-button--small ${standalone ? "bevel-pagination__page" : "bevel-grid__page"}`;
|
|
292
|
+
b.dataset[standalone ? "bevelPage" : "page"] = n;
|
|
293
|
+
b.innerHTML = '<span class="bevel-button__label"></span>';
|
|
294
|
+
b.firstChild.textContent = n;
|
|
295
|
+
if (n === current) b.setAttribute("aria-current", "page");
|
|
296
|
+
return b;
|
|
297
|
+
};
|
|
298
|
+
const renderPages = (pages, total, current) => {
|
|
299
|
+
const standalone = pages.classList.contains("bevel-pagination__pages");
|
|
300
|
+
const selector = standalone ? "data-bevel-page" : "data-page";
|
|
301
|
+
const prev = pages.querySelector(`[${selector}="prev"]`);
|
|
302
|
+
const next = pages.querySelector(`[${selector}="next"]`);
|
|
303
|
+
const items = [];
|
|
304
|
+
if (total <= 7) for (let n = 1; n <= total; n++) items.push(n);
|
|
305
|
+
else {
|
|
306
|
+
items.push(1);
|
|
307
|
+
if (current > 3) items.push("…");
|
|
308
|
+
for (let n = Math.max(2, current - 1); n <= Math.min(total - 1, current + 1); n++) items.push(n);
|
|
309
|
+
if (current < total - 2) items.push("…");
|
|
310
|
+
items.push(total);
|
|
311
|
+
}
|
|
312
|
+
const rendered = [...pages.children].filter(node => node !== prev && node !== next);
|
|
313
|
+
const matches = rendered.length === items.length && rendered.every((node, index) => items[index] === "…" ? node.textContent === "…" && node.getAttribute("aria-hidden") === "true" : node.getAttribute(selector) === String(items[index]) && node.hasAttribute("aria-current") === (items[index] === current));
|
|
314
|
+
if (!matches) {
|
|
315
|
+
rendered.forEach(node => node.remove());
|
|
316
|
+
const frag = document.createDocumentFragment();
|
|
317
|
+
for (const n of items) {
|
|
318
|
+
if (n === "…") {
|
|
319
|
+
const gap = document.createElement("span");
|
|
320
|
+
gap.className = standalone ? "bevel-pagination__gap" : "bevel-grid__gap";
|
|
321
|
+
gap.setAttribute("aria-hidden", "true");
|
|
322
|
+
gap.textContent = n;
|
|
323
|
+
frag.append(gap);
|
|
324
|
+
} else frag.append(pageButton(n, current, standalone));
|
|
325
|
+
}
|
|
326
|
+
if (next) next.before(frag); else pages.append(frag);
|
|
327
|
+
}
|
|
328
|
+
if (prev && prev.disabled !== (current <= 1)) prev.disabled = current <= 1;
|
|
329
|
+
if (next && next.disabled !== (current >= total)) next.disabled = current >= total;
|
|
330
|
+
};
|
|
331
|
+
const renderStandalonePages = (list, total, current) => {
|
|
332
|
+
const previous = list.querySelector('[data-bevel-page="prev"]'), next = list.querySelector('[data-bevel-page="next"]'), items = [];
|
|
333
|
+
if (total <= 7) for (let page = 1; page <= total; page++) items.push(page);
|
|
334
|
+
else if (current <= 2) items.push(1, 2, 3, "…", total);
|
|
335
|
+
else if (current >= total - 1) items.push(1, "…", total - 2, total - 1, total);
|
|
336
|
+
else {
|
|
337
|
+
items.push(1);
|
|
338
|
+
if (current > 3) items.push("…");
|
|
339
|
+
items.push(current - 1, current, current + 1);
|
|
340
|
+
if (current < total - 2) items.push("…");
|
|
341
|
+
items.push(total);
|
|
342
|
+
}
|
|
343
|
+
const rendered = [...list.children].filter((item) => item !== previous && item !== next), matches = rendered.length === items.length && rendered.every((node, index) => items[index] === "…" ? node.matches('.bevel-pagination__gap[aria-hidden="true"]') && node.textContent === "…" : node.matches(`.bevel-pagination__page[data-bevel-page="${items[index]}"]`) && node.hasAttribute("aria-current") === (items[index] === current));
|
|
344
|
+
if (!matches) {
|
|
345
|
+
rendered.forEach((item) => item.remove());
|
|
346
|
+
for (const item of items) {
|
|
347
|
+
const node = item === "…" ? document.createElement("span") : pageButton(item, current, true);
|
|
348
|
+
if (item === "…") { node.className = "bevel-pagination__gap"; node.setAttribute("aria-hidden", "true"); node.textContent = item; }
|
|
349
|
+
if (next) next.before(node); else list.append(node);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (previous && previous.disabled !== (current <= 1)) previous.disabled = current <= 1;
|
|
353
|
+
if (next && next.disabled !== (current >= total)) next.disabled = current >= total;
|
|
354
|
+
};
|
|
355
|
+
const syncPick = (grid) => {
|
|
356
|
+
const head = grid.querySelector(".bevel-grid__table > thead .bevel-grid__pick input");
|
|
357
|
+
const all = [...grid.querySelectorAll(".bevel-grid__table > tbody .bevel-grid__pick input")];
|
|
358
|
+
const shown = all.filter((i) => !i.disabled && !i.closest("tr").hidden);
|
|
359
|
+
const on = shown.filter((i) => i.checked).length;
|
|
360
|
+
if (head) {
|
|
361
|
+
const checked = shown.length > 0 && on === shown.length, mixed = on > 0 && on < shown.length;
|
|
362
|
+
if (head.checked !== checked) head.checked = checked;
|
|
363
|
+
if (head.indeterminate !== mixed) head.indeterminate = mixed;
|
|
364
|
+
if (head.hasAttribute("checked") !== checked) head.toggleAttribute("checked", checked);
|
|
365
|
+
}
|
|
366
|
+
const out = grid.querySelector(".bevel-grid__selected");
|
|
367
|
+
if (out) { const count = String(all.filter(i => i.checked).length); if (out.textContent !== count) out.textContent = count; }
|
|
368
|
+
};
|
|
369
|
+
const matchesView = (row, view) => {
|
|
370
|
+
const values = (view?.dataset.gridMatch || "").toLowerCase().split("|").filter(Boolean);
|
|
371
|
+
return !values.length || values.includes(cellKey(row, Number(view.dataset.gridColumn), "text"));
|
|
372
|
+
};
|
|
373
|
+
const syncStats = (grid, rows) => {
|
|
374
|
+
for (const output of grid.querySelectorAll("[data-grid-aggregate]")) {
|
|
375
|
+
const values = rows.map((row) => cellKey(row, Number(output.dataset.gridColumn), "number"));
|
|
376
|
+
const sum = values.reduce((total, value) => total + value, 0);
|
|
377
|
+
const kind = output.dataset.gridAggregate;
|
|
378
|
+
const value = kind === "count" ? rows.length : kind === "average" ? (values.length ? sum / values.length : 0) : sum;
|
|
379
|
+
const format = output.dataset.gridFormat;
|
|
380
|
+
const text = format === "currency" ? new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 }).format(value)
|
|
381
|
+
: format === "percent" ? `${Math.round(value)}%` : new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 }).format(value);
|
|
382
|
+
if (output.textContent !== text) output.textContent = text;
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
const filterDefinitions = root => { try { const node=root.querySelector('script[type="application/json"][data-bevel-filter-fields]'); return node ? JSON.parse(node.textContent) : []; } catch { return []; } };
|
|
386
|
+
const filterRead = root => ({ match: root.dataset.bevelMatch === "any" ? "any" : "all", rules:[...root.querySelectorAll('.bevel-filter__rules > .bevel-filter__rule')].map(row=>({ key:row.dataset.bevelKey, field:row.dataset.bevelField, operator:row.dataset.bevelOperator, value:row.querySelector('[data-bevel-filter-value]')?.value ?? "" })) });
|
|
387
|
+
const filterEmpty = value => value == null || value === "";
|
|
388
|
+
const filterDate = value => { if(!/^\d{4}-\d{2}-\d{2}$/.test(String(value||"")))return false; const date=new Date(`${value}T00:00:00Z`); return !Number.isNaN(date.valueOf())&&date.toISOString().slice(0,10)===value; };
|
|
389
|
+
const filterOperators = { text: ["equals", "contains", "starts-with", "is-empty"], number: ["eq", "ne", "gt", "gte", "lt", "lte", "is-empty"], date: ["before", "on", "after", "is-empty"], enum: ["is", "is-not", "is-empty"] };
|
|
390
|
+
const filterRuleValid = (rule, field) => {
|
|
391
|
+
if (!field || !filterOperators[field.type || "text"]?.includes(rule.operator)) return false;
|
|
392
|
+
if (rule.operator === "is-empty") return true;
|
|
393
|
+
if (field.type === "number") return String(rule.value).trim() !== "" && Number.isFinite(Number(rule.value));
|
|
394
|
+
if (field.type === "date") return filterDate(rule.value);
|
|
395
|
+
if (field.type === "enum" && field.options) return field.options.some(option => String(option.id) === String(rule.value));
|
|
396
|
+
return true;
|
|
397
|
+
};
|
|
398
|
+
const refreshFilterRule = row => {
|
|
399
|
+
const field = filterDefinitions(row.closest(".bevel-filter")).find(item => item.id === row.dataset.bevelField);
|
|
400
|
+
if (!field) return;
|
|
401
|
+
const operator = row.querySelector("[data-bevel-filter-operator]"), previous = row.querySelector("[data-bevel-filter-value]");
|
|
402
|
+
if (!operator || !previous) return;
|
|
403
|
+
const choices = filterOperators[field.type || "text"], labels = { "starts-with": "starts with", "is-empty": "is empty", "is-not": "is not", eq: "=", ne: "≠", gt: ">", gte: "≥", lt: "<", lte: "≤" };
|
|
404
|
+
const selected = choices.includes(row.dataset.bevelOperator) ? row.dataset.bevelOperator : choices[0];
|
|
405
|
+
operator.replaceChildren(...choices.map(value => { const option = document.createElement("option"); option.value = value; option.textContent = labels[value] || value; option.selected = value === selected; option.toggleAttribute("selected", option.selected); return option; }));
|
|
406
|
+
row.dataset.bevelOperator = selected;
|
|
407
|
+
const value = previous.value, input = document.createElement(field.type === "enum" && field.options ? "select" : "input");
|
|
408
|
+
input.dataset.bevelFilterValue = ""; input.setAttribute("aria-label", `${field.label || field.id} filter value`);
|
|
409
|
+
if (input.tagName === "SELECT") {
|
|
410
|
+
input.className = "bevel-select__field";
|
|
411
|
+
for (const option of field.options) { const node = document.createElement("option"); node.value = String(option.id); node.textContent = option.label; input.append(node); }
|
|
412
|
+
input.value = field.options.some(option => String(option.id) === value) ? value : String(field.options[0]?.id || "");
|
|
413
|
+
for (const option of input.options) option.toggleAttribute("selected", option.selected);
|
|
414
|
+
} else { input.type = "text"; input.className = "bevel-input bevel-input--small"; input.value = value; input.setAttribute("value", value); if (field.type === "number") input.inputMode = "decimal"; }
|
|
415
|
+
const oldWrapper = previous.parentElement.classList.contains("bevel-select") ? previous.parentElement : previous;
|
|
416
|
+
if (input.tagName === "SELECT") { const wrapper = document.createElement("span"); wrapper.className = "bevel-select"; wrapper.append(input); oldWrapper.replaceWith(wrapper); }
|
|
417
|
+
else oldWrapper.replaceWith(input);
|
|
418
|
+
input.disabled = selected === "is-empty";
|
|
419
|
+
};
|
|
420
|
+
const filterMatches = (record, expression, fields=[]) => {
|
|
421
|
+
const definitions = new Map(fields.map(field=>[field.id,field])), rules=expression?.rules || [];
|
|
422
|
+
const test = rule => { const field=definitions.get(rule.field); if(!field)return false; const actual=record[rule.field], op=rule.operator, wanted=rule.value; if(op==="is-empty")return filterEmpty(actual); if(field.type==="number"){ if(filterEmpty(actual)||filterEmpty(wanted)||String(actual).trim()===""||String(wanted).trim()===""||!Number.isFinite(Number(actual))||!Number.isFinite(Number(wanted)))return false; const a=Number(actual),b=Number(wanted); return op==="eq"?a===b:op==="ne"?a!==b:op==="gt"?a>b:op==="gte"?a>=b:op==="lt"?a<b:op==="lte"?a<=b:false; } if(field.type==="date"){ if(!filterDate(actual)||!filterDate(wanted))return false; return op==="before"?actual<wanted:op==="on"?actual===wanted:op==="after"?actual>wanted:false; } if(field.type==="enum")return op==="is"?String(actual)===String(wanted):op==="is-not"?String(actual)!==String(wanted):false; const a=String(actual??"").toLowerCase(),b=String(wanted??"").toLowerCase(); return op==="equals"?a===b:op==="contains"?a.includes(b):op==="starts-with"?a.startsWith(b):false; };
|
|
423
|
+
return !rules.length || (expression.match==="any" ? rules.some(test) : rules.every(test));
|
|
424
|
+
};
|
|
425
|
+
const gridRecord = row => Object.fromEntries([...row.children].filter(cell=>cell.dataset.bevelColumn).map(cell=>[cell.dataset.bevelColumn,cell.dataset.value ?? cell.dataset.sort ?? cell.textContent.trim()]));
|
|
426
|
+
const gridFilter = grid => { const root=grid.id&&document.querySelector(`.bevel-filter[data-bevel-grid="${CSS.escape(grid.id)}"]`); if(!root)return null; return { expression:filterRead(root), fields:filterDefinitions(root) }; };
|
|
427
|
+
const searchedGridRows = grid => {
|
|
428
|
+
const query = (grid.querySelector(".bevel-grid__search")?.value || "").trim().toLowerCase(), activeFilter = gridFilter(grid);
|
|
429
|
+
return gridRows(grid).filter(row => (!query || row.textContent.toLowerCase().includes(query)) && (!activeFilter || filterMatches(gridRecord(row), activeFilter.expression, activeFilter.fields)));
|
|
430
|
+
};
|
|
431
|
+
const layoutGrid = (grid) => {
|
|
432
|
+
const size = Number(grid.dataset.pageSize) || 0;
|
|
433
|
+
const rows = gridRows(grid);
|
|
434
|
+
const searched = searchedGridRows(grid);
|
|
435
|
+
const view = grid.querySelector('.bevel-grid__view[aria-pressed="true"]');
|
|
436
|
+
const matches = new Set(searched.filter((row) => matchesView(row, view)));
|
|
437
|
+
const resultLabel = grid.closest(".bevel-grid-result")?.querySelector(".bevel-grid-result__label");
|
|
438
|
+
if (resultLabel) { const label = `Applied / ${matches.size} matching record${matches.size === 1 ? "" : "s"}`; if (resultLabel.textContent !== label) resultLabel.textContent = label; }
|
|
439
|
+
|
|
440
|
+
for (const tab of grid.querySelectorAll(".bevel-grid__view")) {
|
|
441
|
+
const count = tab.querySelector("span");
|
|
442
|
+
if (count) { const text = String(searched.filter(row => matchesView(row, tab)).length); if (count.textContent !== text) count.textContent = text; }
|
|
443
|
+
}
|
|
444
|
+
syncStats(grid, [...matches]);
|
|
445
|
+
const total = size ? Math.max(1, Math.ceil(matches.size / size)) : 1;
|
|
446
|
+
const current = Math.min(total, Math.max(1, Number(grid.dataset.page) || 1));
|
|
447
|
+
if (size && grid.dataset.page !== String(current)) grid.dataset.page = String(current);
|
|
448
|
+
const first = size ? (current - 1) * size : 0;
|
|
449
|
+
const last = size ? first + size : matches.size;
|
|
450
|
+
let i = 0;
|
|
451
|
+
for (const row of rows) {
|
|
452
|
+
const match = matches.has(row);
|
|
453
|
+
const show = match && i >= first && i < last;
|
|
454
|
+
if (match) i++;
|
|
455
|
+
if (row.hidden !== !show) row.hidden = !show;
|
|
456
|
+
const detail = detailOf(row);
|
|
457
|
+
if (detail && detail.hidden !== (!show || !isOpen(row))) detail.hidden = !show || !isOpen(row);
|
|
458
|
+
}
|
|
459
|
+
for (const group of grid.querySelectorAll(".bevel-grid__table > tbody > .bevel-grid__group")) {
|
|
460
|
+
let any = false;
|
|
461
|
+
for (let tr = group.nextElementSibling; tr && !tr.classList.contains("bevel-grid__group"); tr = tr.nextElementSibling) {
|
|
462
|
+
if (tr.classList.contains("bevel-grid__row") && !tr.hidden) { any = true; break; }
|
|
463
|
+
}
|
|
464
|
+
if (group.hidden !== !any) group.hidden = !any;
|
|
465
|
+
}
|
|
466
|
+
const empty = grid.querySelector(".bevel-grid__table > tbody > .bevel-grid__empty");
|
|
467
|
+
if (empty && rows.length && empty.hidden !== (matches.size > 0)) empty.hidden = matches.size > 0;
|
|
468
|
+
const pages = grid.querySelector(".bevel-grid__pages");
|
|
469
|
+
if (pages && size) renderPages(pages, total, current);
|
|
470
|
+
const range = grid.querySelector(".bevel-grid__range");
|
|
471
|
+
if (range) { const text = matches.size ? `${first + 1} to ${Math.min(last, matches.size)} of ${matches.size}` : "0 of 0"; if (range.textContent !== text) range.textContent = text; }
|
|
472
|
+
syncPick(grid);
|
|
473
|
+
if (grid.id) document.querySelectorAll(`.bevel-pagination[data-bevel-target="${CSS.escape(grid.id)}"]`).forEach(pager => refreshPager(pager));
|
|
474
|
+
};
|
|
475
|
+
const cellKey = (row, index, type) => {
|
|
476
|
+
const cell = row.children[index];
|
|
477
|
+
const raw = cell ? (cell.dataset.sort ?? cell.textContent.trim()) : "";
|
|
478
|
+
if (type === "number") return Number(raw.replace(/[^\d.-]/g, "")) || 0;
|
|
479
|
+
return raw.toLowerCase();
|
|
480
|
+
};
|
|
481
|
+
on("click", ".bevel-grid__sort", (e, button) => {
|
|
482
|
+
const th = button.closest("th");
|
|
483
|
+
const table = th?.closest(".bevel-grid__table");
|
|
484
|
+
const grid = table && gridOf(table);
|
|
485
|
+
if (!grid) return;
|
|
486
|
+
const dir = th.getAttribute("aria-sort") === "ascending" ? "descending" : "ascending";
|
|
487
|
+
if (grid.dataset.bevelDataMode === "remote") {
|
|
488
|
+
remoteRequest(grid, "sort", th.dataset.bevelColumn || String([...th.parentElement.children].indexOf(th)), th.getAttribute("aria-sort") || "none", dir);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
for (const h of table.querySelectorAll("thead [aria-sort]")) h.setAttribute("aria-sort", h === th ? dir : "none");
|
|
492
|
+
const index = [...th.parentElement.children].indexOf(th);
|
|
493
|
+
const type = th.dataset.type || "text";
|
|
494
|
+
const sign = dir === "ascending" ? 1 : -1;
|
|
495
|
+
const tbody = table.tBodies[0];
|
|
496
|
+
const segments = [{ head: [], units: [] }];
|
|
497
|
+
for (const tr of [...tbody.children]) {
|
|
498
|
+
const seg = segments[segments.length - 1];
|
|
499
|
+
if (tr.classList.contains("bevel-grid__group")) segments.push({ head: [tr], units: [] });
|
|
500
|
+
else if (tr.classList.contains("bevel-grid__row")) seg.units.push([tr]);
|
|
501
|
+
else if (seg.units.length) seg.units[seg.units.length - 1].push(tr);
|
|
502
|
+
else seg.head.push(tr);
|
|
503
|
+
}
|
|
504
|
+
for (const seg of segments) {
|
|
505
|
+
seg.units.sort((a, b) => {
|
|
506
|
+
const x = cellKey(a[0], index, type), y = cellKey(b[0], index, type);
|
|
507
|
+
return x < y ? -sign : x > y ? sign : 0;
|
|
508
|
+
});
|
|
509
|
+
tbody.append(...seg.head, ...seg.units.flat());
|
|
510
|
+
}
|
|
511
|
+
layoutGrid(grid);
|
|
512
|
+
emit(grid, "bevel:sort", { index, dir, th });
|
|
513
|
+
});
|
|
514
|
+
on("change", ".bevel-grid__table > thead .bevel-grid__pick input", (e, input) => {
|
|
515
|
+
const grid = gridOf(input);
|
|
516
|
+
if (!grid) return;
|
|
517
|
+
for (const i of grid.querySelectorAll(".bevel-grid__table > tbody .bevel-grid__pick input")) {
|
|
518
|
+
if (i.disabled || i.closest("tr").hidden) continue;
|
|
519
|
+
i.checked = input.checked;
|
|
520
|
+
i.toggleAttribute("checked", i.checked);
|
|
521
|
+
}
|
|
522
|
+
syncPick(grid);
|
|
523
|
+
emit(grid, "bevel:select", { all: input.checked });
|
|
524
|
+
});
|
|
525
|
+
on("change", ".bevel-grid__table > tbody .bevel-grid__pick input", (e, input) => {
|
|
526
|
+
const grid = gridOf(input);
|
|
527
|
+
if (!grid) return;
|
|
528
|
+
syncPick(grid);
|
|
529
|
+
emit(grid, "bevel:select", { row: input.closest("tr"), checked: input.checked });
|
|
530
|
+
});
|
|
531
|
+
on("click", ".bevel-grid__expand", (e, button) => {
|
|
532
|
+
const row = button.closest("tr");
|
|
533
|
+
const open = button.getAttribute("aria-expanded") !== "true";
|
|
534
|
+
button.setAttribute("aria-expanded", String(open));
|
|
535
|
+
const detail = detailOf(row) || document.getElementById(button.getAttribute("aria-controls") || "");
|
|
536
|
+
if (detail) detail.hidden = !open;
|
|
537
|
+
const grid = gridOf(button);
|
|
538
|
+
if (grid) emit(grid, "bevel:expand", { row, open });
|
|
539
|
+
});
|
|
540
|
+
const changeGridPage = (grid, token) => {
|
|
541
|
+
if (!grid) return false;
|
|
542
|
+
const current = Number(grid.dataset.page) || 1;
|
|
543
|
+
const want = token === "prev" ? current - 1 : token === "next" ? current + 1 : Number(token);
|
|
544
|
+
if (!want || want < 1) return false;
|
|
545
|
+
if (grid.dataset.bevelDataMode === "remote") {
|
|
546
|
+
const total = Number(grid.dataset.bevelTotal), size = Number(grid.dataset.pageSize) || 10;
|
|
547
|
+
if (grid.hasAttribute("data-bevel-total") && want > Math.max(1, Math.ceil(Math.max(0, total) / size))) return false;
|
|
548
|
+
remoteRequest(grid, "page", String(want), current, want);
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
551
|
+
grid.dataset.page = String(want);
|
|
552
|
+
layoutGrid(grid);
|
|
553
|
+
emit(grid, "bevel:page", { page: Number(grid.dataset.page) });
|
|
554
|
+
return true;
|
|
555
|
+
};
|
|
556
|
+
on("click", ".bevel-grid__page", (e, button) => {
|
|
557
|
+
if (button.disabled) return;
|
|
558
|
+
changeGridPage(gridOf(button), button.dataset.page);
|
|
559
|
+
});
|
|
560
|
+
on("click", ".bevel-grid__view", (e, button) => {
|
|
561
|
+
const grid = gridOf(button);
|
|
562
|
+
if (!grid) return;
|
|
563
|
+
if (grid.dataset.bevelDataMode === "remote") { remoteRequest(grid, "view", button.dataset.value || "", grid.querySelector('.bevel-grid__view[aria-pressed="true"]')?.dataset.value || "", button.dataset.value || ""); return; }
|
|
564
|
+
for (const view of grid.querySelectorAll(".bevel-grid__view")) view.setAttribute("aria-pressed", String(view === button));
|
|
565
|
+
if (grid.dataset.pageSize) grid.dataset.page = "1";
|
|
566
|
+
layoutGrid(grid);
|
|
567
|
+
const scroll = grid.querySelector(".bevel-grid__scroll");
|
|
568
|
+
if (scroll) scroll.scrollTop = 0;
|
|
569
|
+
emit(grid, "bevel:view", { value: button.dataset.value, button });
|
|
570
|
+
});
|
|
571
|
+
on("input", ".bevel-grid__search", (e, input) => {
|
|
572
|
+
const grid = gridOf(input);
|
|
573
|
+
if (!grid) return;
|
|
574
|
+
if (grid.dataset.bevelDataMode === "remote") { input.setAttribute("value", input.value); remoteRequest(grid, "search", "", grid.dataset.bevelQuery || "", input.value); return; }
|
|
575
|
+
if (grid.dataset.pageSize) grid.dataset.page = "1";
|
|
576
|
+
layoutGrid(grid);
|
|
577
|
+
emit(grid, "bevel:search", { query: input.value });
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
/* ---------- files table ---------- */
|
|
581
|
+
const filesPicks = (root) => [...root.querySelectorAll(".bevel-files__table > tbody .bevel-files__pick input")].filter((input) => !input.disabled);
|
|
582
|
+
const syncFiles = (root) => {
|
|
583
|
+
const head = root.querySelector(".bevel-files__table > thead .bevel-files__pick input");
|
|
584
|
+
if (!head) return;
|
|
585
|
+
const picks = filesPicks(root), count = picks.filter((input) => input.checked).length;
|
|
586
|
+
const checked = picks.length > 0 && count === picks.length, mixed = count > 0 && !checked;
|
|
587
|
+
if (head.checked !== checked) head.checked = checked;
|
|
588
|
+
if (head.indeterminate !== mixed) head.indeterminate = mixed;
|
|
589
|
+
if (head.hasAttribute("checked") !== checked) head.toggleAttribute("checked", checked);
|
|
590
|
+
};
|
|
591
|
+
on("change", ".bevel-files__table > thead .bevel-files__pick input", (e, input) => {
|
|
592
|
+
const root = input.closest(".bevel-files");
|
|
593
|
+
if (!root) return;
|
|
594
|
+
for (const pick of filesPicks(root)) {
|
|
595
|
+
pick.checked = input.checked;
|
|
596
|
+
pick.toggleAttribute("checked", pick.checked);
|
|
597
|
+
}
|
|
598
|
+
syncFiles(root);
|
|
599
|
+
emit(root, "bevel:select", { all: input.checked });
|
|
600
|
+
});
|
|
601
|
+
on("change", ".bevel-files__table > tbody .bevel-files__pick input", (e, input) => {
|
|
602
|
+
const root = input.closest(".bevel-files");
|
|
603
|
+
if (!root) return;
|
|
604
|
+
const row = input.closest("tr");
|
|
605
|
+
syncFiles(root);
|
|
606
|
+
emit(root, "bevel:select", { row, key: row?.dataset.bevelKey ?? "", checked: input.checked });
|
|
607
|
+
});
|
|
608
|
+
on("click", ".bevel-files__sort", (e, button) => {
|
|
609
|
+
const th = button.closest("th"), root = button.closest(".bevel-files");
|
|
610
|
+
if (!th || !root) return;
|
|
611
|
+
const direction = th.getAttribute("aria-sort") === "ascending" ? "descending" : "ascending";
|
|
612
|
+
for (const head of th.parentElement.querySelectorAll("th[aria-sort]")) head.setAttribute("aria-sort", head === th ? direction : "none");
|
|
613
|
+
emit(root, "bevel:sort", { column: th.dataset.bevelColumn ?? "", direction, th });
|
|
614
|
+
});
|
|
615
|
+
on("click", ".bevel-files__row", (e, row) => {
|
|
616
|
+
if (!row.contains(e.target) || e.target.closest('a, button, input, label, select, textarea, summary, [role="button"], [role="menuitem"], .bevel-files__pick')) return;
|
|
617
|
+
row.querySelector(".bevel-files__title")?.click();
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
/* ---------- functional component foundation ---------- */
|
|
621
|
+
const sessions = new Map();
|
|
622
|
+
let overlaySequence = Date.now();
|
|
623
|
+
const uploadFiles = new WeakMap();
|
|
624
|
+
const uploadSize = bytes => bytes < 1024 ? `${bytes} B` : `${Math.round(bytes / 1024)} KB`;
|
|
625
|
+
const wizardForms = new Map();
|
|
626
|
+
const rootFor = (el, selector) => el?.closest(selector);
|
|
627
|
+
const disabled = (el) => !el || el.disabled || el.readOnly || el.getAttribute("aria-disabled") === "true" || !!el.closest("fieldset:disabled");
|
|
628
|
+
const targetById = (id) => id ? document.getElementById(id) : null;
|
|
629
|
+
const componentName = (root) => [...root.classList].find((name) => /^bevel-(modal|menu|drawer|popconfirm|combobox|multiselect|choice-card|dropzone|inline-edit|settings|errors|split-button|app-shell|sidebar|toolbar|bulk-bar|breadcrumbs|pagination|file-tree|tree|wizard|action-banner|code|diff|activity|stats|avatars|chart|filter|columns|split|richtext|kanban|grid|files)$/.test(name))?.slice(6) || "component";
|
|
630
|
+
const jsonValue = (value) => value == null || typeof value !== "object" ? value : JSON.parse(JSON.stringify(value));
|
|
631
|
+
const transition = (root, action, key, previous, next, mutate, source = "user") => {
|
|
632
|
+
const detail = { component: componentName(root), action, key: String(key ?? ""), previous: jsonValue(previous), next: jsonValue(next) };
|
|
633
|
+
if (!root.dispatchEvent(new CustomEvent("bevel:beforechange", { bubbles: true, cancelable: true, detail }))) return false;
|
|
634
|
+
mutate();
|
|
635
|
+
if (source !== "external") emit(root, "bevel:change", detail);
|
|
636
|
+
return true;
|
|
637
|
+
};
|
|
638
|
+
const action = (root, name, key = "") => emit(root, "bevel:action", { component: componentName(root), action: name, key: String(key) });
|
|
639
|
+
const error = (root, code, message) => {
|
|
640
|
+
emit(root, "bevel:error", { component: componentName(root), code, message });
|
|
641
|
+
console.error(`[Bevel ${code}] ${message}`);
|
|
642
|
+
};
|
|
643
|
+
const focusable = (root) => [...root.querySelectorAll("button,a[href],input,select,textarea,[tabindex]")].find((el) => !disabled(el) && el.tabIndex >= 0 && !el.hidden);
|
|
644
|
+
const panelTrigger = (panel) => {
|
|
645
|
+
const id = panel.id;
|
|
646
|
+
return id ? document.querySelector(`[role="combobox"][aria-controls="${CSS.escape(id)}"],[data-bevel-open="${CSS.escape(id)}"]`) : null;
|
|
647
|
+
};
|
|
648
|
+
const setExpanded = (panel, value) => {
|
|
649
|
+
if (!panel.id) return;
|
|
650
|
+
for (const control of document.querySelectorAll(`[aria-controls="${CSS.escape(panel.id)}"]`)) control.setAttribute("aria-expanded", String(value));
|
|
651
|
+
};
|
|
652
|
+
const isModal = (root) => root.matches("dialog.bevel-modal, dialog.bevel-drawer");
|
|
653
|
+
const isAnchored = (root) => root.matches(".bevel-menu,.bevel-popconfirm,.bevel-combobox__listbox,.bevel-multiselect__panel");
|
|
654
|
+
const opened = () => [...document.querySelectorAll("[data-bevel-open]:is(dialog,[popover])")].filter((el) => el.hasAttribute("open") || el.matches(":popover-open")).sort((a, b) => (sessions.get(a)?.openOrder || Number(a.dataset.bevelOpenOrder) || 0) - (sessions.get(b)?.openOrder || Number(b.dataset.bevelOpenOrder) || 0));
|
|
655
|
+
const position = (panel, trigger) => {
|
|
656
|
+
const floating = window.FloatingUIDOM;
|
|
657
|
+
if (!floating) {
|
|
658
|
+
error(panel, "missing-positioner", "Anchored components require the vendored Floating UI core and DOM scripts.");
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
let active = true;
|
|
662
|
+
const middleware = [floating.offset(8), floating.flip(), floating.shift({ padding: 8 }), floating.size({ padding: 8, apply({ availableHeight, elements }) { if (active) elements.floating.style.setProperty("--bevel-available-height", `${Math.max(120, availableHeight)}px`); } })];
|
|
663
|
+
const arrow = panel.querySelector("[data-bevel-arrow]");
|
|
664
|
+
if (arrow) middleware.push(floating.arrow({ element: arrow }));
|
|
665
|
+
const reference = panel.matches(".bevel-combobox__listbox,.bevel-multiselect__panel") ? trigger.closest(".bevel-combobox__control,.bevel-multiselect__control") || trigger : trigger;
|
|
666
|
+
const update = () => floating.computePosition(reference, panel, { strategy: "fixed", placement: panel.dataset.bevelPlacement || "bottom-start", middleware }).then(({ x, y, placement, middlewareData }) => {
|
|
667
|
+
if (!active || !panel.isConnected) return;
|
|
668
|
+
panel.style.left = `${x}px`; panel.style.top = `${y}px`; panel.dataset.bevelActualPlacement = placement;
|
|
669
|
+
if (arrow && middlewareData.arrow) {
|
|
670
|
+
arrow.style.left = middlewareData.arrow.x == null ? "" : `${middlewareData.arrow.x}px`;
|
|
671
|
+
arrow.style.top = middlewareData.arrow.y == null ? "" : `${middlewareData.arrow.y}px`;
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
const stop = floating.autoUpdate(reference, panel, update);
|
|
675
|
+
const cleanup = () => { active = false; stop(); };
|
|
676
|
+
panel.dataset.bevelGeometry = "";
|
|
677
|
+
sessions.set(panel, { ...(sessions.get(panel) || {}), cleanup, trigger });
|
|
678
|
+
return true;
|
|
679
|
+
};
|
|
680
|
+
const closeOverlay = (root, { reason = "api", source = "user", focus = true } = {}) => {
|
|
681
|
+
if (!root || !(root.hasAttribute("open") || root.matches(":popover-open") || root.hasAttribute("data-bevel-open"))) return false;
|
|
682
|
+
const session = sessions.get(root);
|
|
683
|
+
const commit = () => {
|
|
684
|
+
session?.cleanup?.(); sessions.delete(root);
|
|
685
|
+
root.removeAttribute("data-bevel-open");
|
|
686
|
+
if (root.matches("dialog") && root.open) root.close();
|
|
687
|
+
else if (root.matches("[popover]") && root.matches(":popover-open")) root.hidePopover();
|
|
688
|
+
const trigger = session?.trigger || panelTrigger(root);
|
|
689
|
+
trigger?.setAttribute("aria-expanded", "false");
|
|
690
|
+
setExpanded(root, false);
|
|
691
|
+
root.style.removeProperty("left"); root.style.removeProperty("top"); root.style.removeProperty("--bevel-available-height");
|
|
692
|
+
root.removeAttribute("data-bevel-geometry");
|
|
693
|
+
if (focus && trigger?.isConnected && !disabled(trigger)) trigger.focus();
|
|
694
|
+
};
|
|
695
|
+
return transition(root, "close", root.id, true, false, commit, source);
|
|
696
|
+
};
|
|
697
|
+
const openOverlay = (root, { trigger = panelTrigger(root), source = "user" } = {}) => {
|
|
698
|
+
if (!root || root.hasAttribute("open") || root.matches(":popover-open")) return false;
|
|
699
|
+
if (!(root instanceof HTMLDialogElement) && !root.matches("[popover]")) { error(root, "unsupported-overlay", "Overlay roots must be a dialog or manual popover."); return false; }
|
|
700
|
+
if (isAnchored(root) && (!trigger || disabled(trigger))) return false;
|
|
701
|
+
if (root.matches(".bevel-combobox__listbox") && (disabled(root.closest(".bevel-combobox")) || disabled(root.closest(".bevel-combobox").querySelector('[role="combobox"]')))) return false;
|
|
702
|
+
if (isAnchored(root) && !window.FloatingUIDOM) { error(root, "missing-positioner", "Anchored components require the vendored Floating UI core and DOM scripts."); return false; }
|
|
703
|
+
if (isModal(root)) {
|
|
704
|
+
for (const panel of opened().filter((el) => isAnchored(el) && !root.contains(el))) if (!closeOverlay(panel, { reason: "modal-open", focus: false })) return false;
|
|
705
|
+
}
|
|
706
|
+
const commit = () => {
|
|
707
|
+
root.setAttribute("data-bevel-open", "");
|
|
708
|
+
if (isModal(root)) {
|
|
709
|
+
root.dataset.bevelOpenOrder = String(++overlaySequence);
|
|
710
|
+
root.showModal();
|
|
711
|
+
} else {
|
|
712
|
+
root.showPopover();
|
|
713
|
+
position(root, trigger);
|
|
714
|
+
}
|
|
715
|
+
sessions.set(root, { ...(sessions.get(root) || {}), trigger, openOrder: ++overlaySequence });
|
|
716
|
+
if (trigger) trigger.setAttribute("aria-expanded", "true");
|
|
717
|
+
setExpanded(root, true);
|
|
718
|
+
if (!root.matches(".bevel-combobox__listbox")) requestAnimationFrame(() => (root.querySelector("[autofocus]") || focusable(root))?.focus());
|
|
719
|
+
};
|
|
720
|
+
return transition(root, "open", root.id, false, true, commit, source);
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
const requestScopes = new Map();
|
|
724
|
+
let requestSequence = 0;
|
|
725
|
+
const remoteRequest = (root, name, key, previous, next, scope = name) => {
|
|
726
|
+
const requestId = `bevel-${Date.now().toString(36)}-${++requestSequence}`;
|
|
727
|
+
const scopes = requestScopes.get(root) || new Map();
|
|
728
|
+
scopes.set(scope, requestId); requestScopes.set(root, scopes);
|
|
729
|
+
root.toggleAttribute("data-bevel-pending", true);
|
|
730
|
+
emit(root, "bevel:request", { component: componentName(root), action: name, key: String(key ?? ""), requestId, revision: root.dataset.bevelRevision || null, previous: jsonValue(previous), next: jsonValue(next) });
|
|
731
|
+
return requestId;
|
|
732
|
+
};
|
|
733
|
+
const localOrRemote = (root, name, key, previous, next, mutate, scope) => root.dataset.bevelDataMode === "remote"
|
|
734
|
+
? (remoteRequest(root, name, key, previous, next, scope), true)
|
|
735
|
+
: transition(root, name, key, previous, next, mutate);
|
|
736
|
+
|
|
737
|
+
const refreshCombo = (root, restore = false) => {
|
|
738
|
+
const input = root.querySelector('[role="combobox"]');
|
|
739
|
+
const hidden = root.querySelector('input[type="hidden"]');
|
|
740
|
+
const list = root.querySelector('[role="listbox"]');
|
|
741
|
+
if (!input || !list) return;
|
|
742
|
+
const key = hidden?.value || root.dataset.bevelValue || "";
|
|
743
|
+
const option = [...list.querySelectorAll('[role="option"]')].find((el) => el.dataset.bevelKey === key && el.getAttribute("aria-disabled") !== "true");
|
|
744
|
+
for (const el of list.querySelectorAll('[role="option"]')) { const selected = String(el === option); if (el.getAttribute("aria-selected") !== selected) el.setAttribute("aria-selected", selected); }
|
|
745
|
+
if (root.dataset.bevelValue !== key) root.dataset.bevelValue = key;
|
|
746
|
+
if (hidden && hidden.getAttribute("value") !== key) hidden.setAttribute("value", key);
|
|
747
|
+
const label = option?.dataset.bevelLabel || option?.textContent.trim() || "";
|
|
748
|
+
if (restore || document.activeElement !== input || !input.value) { input.value = label; if (input.getAttribute("value") !== label) input.setAttribute("value", label); }
|
|
749
|
+
input.setCustomValidity(input.required && (!option || input.value !== label) ? "Choose an option." : "");
|
|
750
|
+
};
|
|
751
|
+
const refreshMulti = (root) => {
|
|
752
|
+
const name = root.dataset.bevelName || "values[]";
|
|
753
|
+
const selected = [...root.querySelectorAll('.bevel-multiselect__panel input[type="checkbox"]:checked')];
|
|
754
|
+
const wanted = selected.map((checkbox) => checkbox.value);
|
|
755
|
+
const generated = [...root.querySelectorAll(':scope > input[type="hidden"][data-bevel-generated]')];
|
|
756
|
+
if (generated.length !== wanted.length || generated.some((input, index) => input.name !== name || input.value !== wanted[index])) {
|
|
757
|
+
generated.forEach((input) => input.remove());
|
|
758
|
+
for (const value of wanted) {
|
|
759
|
+
const hidden = document.createElement("input"); hidden.type = "hidden"; hidden.name = name; hidden.value = value; hidden.dataset.bevelGenerated = "multiselect"; root.append(hidden);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
for (const checkbox of root.querySelectorAll('.bevel-multiselect__panel input[type="checkbox"]')) if (checkbox.hasAttribute("checked") !== checkbox.checked) checkbox.toggleAttribute("checked", checkbox.checked);
|
|
763
|
+
const summary = root.querySelector(".bevel-multiselect__summary"), text = selected.length ? `${selected.length} selected` : "Choose options"; if (summary && summary.textContent !== text) summary.textContent = text;
|
|
764
|
+
for (const count of root.querySelectorAll("[data-bevel-multiselect-count]")) if (count.textContent !== String(selected.length)) count.textContent = String(selected.length);
|
|
765
|
+
const tags = root.querySelector(".bevel-multiselect__tags");
|
|
766
|
+
if (tags) {
|
|
767
|
+
const present = [...tags.querySelectorAll(".bevel-multiselect__tag")].map((tag) => tag.dataset.bevelKey);
|
|
768
|
+
if (present.length !== wanted.length || present.some((key, index) => key !== wanted[index])) {
|
|
769
|
+
tags.replaceChildren(...selected.map((checkbox) => {
|
|
770
|
+
const tag = document.createElement("span"); tag.className = "bevel-multiselect__tag"; tag.dataset.bevelGenerated = "multiselect-tag"; tag.dataset.bevelKey = checkbox.value;
|
|
771
|
+
const label = document.createElement("span"); label.textContent = checkbox.dataset.bevelLabel || checkbox.closest("label")?.textContent.trim() || checkbox.value;
|
|
772
|
+
if (checkbox.dataset.bevelTone) { const dot = document.createElement("i"); dot.className = `bevel-label-dot bevel-label-dot--${checkbox.dataset.bevelTone}`; dot.setAttribute("aria-hidden", "true"); tag.append(dot); }
|
|
773
|
+
tag.append(label);
|
|
774
|
+
if (!disabled(checkbox)) { const remove = document.createElement("button"); remove.type = "button"; remove.dataset.bevelMultiselectRemove = checkbox.value; remove.setAttribute("aria-label", `Remove ${label.textContent}`); remove.innerHTML = '<svg aria-hidden="true" height="11" width="11" viewBox="0 0 256 256" fill="currentColor"><path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"/></svg>'; tag.append(remove); }
|
|
775
|
+
return tag;
|
|
776
|
+
}));
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
const splitSessions = new Map();
|
|
781
|
+
const splitMedia = matchMedia("(max-width: 640px)");
|
|
782
|
+
const splitValue = root => Number(root.getAttribute(root.hasAttribute("data-bevel-split") ? "data-bevel-split" : "data-bevel-ratio")) || 50;
|
|
783
|
+
const splitBounds = root => {
|
|
784
|
+
const vertical = root.dataset.bevelOrientation === "vertical", extent = vertical ? root.clientHeight : root.clientWidth;
|
|
785
|
+
const divider = root.querySelector(":scope > .bevel-split__divider"), gap = divider ? vertical ? divider.offsetHeight : divider.offsetWidth : 10;
|
|
786
|
+
const first = Math.max(0, Number(root.dataset.bevelMinFirst) || 180), second = Math.max(0, Number(root.dataset.bevelMinSecond) || 180);
|
|
787
|
+
if (extent <= 0) return [25, 75];
|
|
788
|
+
if (extent - gap < first + second) { const value = 100 * Math.max(0, extent - gap) * first / ((first + second) * extent); return [value, value]; }
|
|
789
|
+
const low = Math.max(25, first / extent * 100), high = Math.min(75, (extent - gap - second) / extent * 100);
|
|
790
|
+
if (low > high) { const value = Math.min((extent - gap - second) / extent * 100, Math.max(first / extent * 100, 50)); return [value, value]; }
|
|
791
|
+
return [low, high];
|
|
792
|
+
};
|
|
793
|
+
const splitClamped = (root, value) => { const [min, max] = splitBounds(root); return Math.min(max, Math.max(min, value)); };
|
|
794
|
+
const refreshSplit = root => {
|
|
795
|
+
const divider = root.querySelector(":scope > .bevel-split__divider");
|
|
796
|
+
if (!divider) return;
|
|
797
|
+
let session = splitSessions.get(root);
|
|
798
|
+
if (!session) {
|
|
799
|
+
session = { drag: null, observer: new ResizeObserver(() => { if (root.isConnected) refreshSplit(root); }) };
|
|
800
|
+
splitSessions.set(root, session); session.observer.observe(root);
|
|
801
|
+
}
|
|
802
|
+
const stacked = splitMedia.matches, [min, max] = splitBounds(root), value = splitClamped(root, splitValue(root)), inactive = stacked || min === max || root.matches('[aria-disabled="true"],[disabled]');
|
|
803
|
+
if (session.drag && (inactive || !root.isConnected || splitValue(root) !== session.drag.previous || root.dataset.bevelOrientation !== session.drag.orientation)) session.drag.stop(false);
|
|
804
|
+
if (!session.drag) { const size = `${value}%`; if (root.style.getPropertyValue("--bevel-split-size") !== size) root.style.setProperty("--bevel-split-size", size); }
|
|
805
|
+
const attrs = { role: "separator", "aria-orientation": root.dataset.bevelOrientation === "vertical" ? "horizontal" : "vertical", "aria-valuemin": String(Math.round(min * 100) / 100), "aria-valuemax": String(Math.round(max * 100) / 100), "aria-valuenow": String(Math.round(value * 100) / 100), "aria-disabled": String(inactive), tabindex: inactive ? "-1" : "0" };
|
|
806
|
+
for (const [name, text] of Object.entries(attrs)) if (divider.getAttribute(name) !== text) divider.setAttribute(name, text);
|
|
807
|
+
if (divider.hidden !== stacked) divider.hidden = stacked;
|
|
808
|
+
const caption = root.nextElementSibling;
|
|
809
|
+
if (caption?.matches(".bevel-split__caption")) { const text = caption.querySelector("output"), label = `${root.dataset.bevelLabel || "List"} ${Math.round(value)}% · minimum ${Math.round(min)}% · drag the divider`; if (text && text.textContent !== label) text.textContent = label; }
|
|
810
|
+
};
|
|
811
|
+
const commitSplit = (root, next) => {
|
|
812
|
+
const previous = splitValue(root);
|
|
813
|
+
if (previous === next) return false;
|
|
814
|
+
return transition(root, "resize", root.dataset.bevelKey || "", previous, next, () => { root.setAttribute(root.hasAttribute("data-bevel-split") ? "data-bevel-split" : "data-bevel-ratio", String(next)); refreshSplit(root); });
|
|
815
|
+
};
|
|
816
|
+
splitMedia.addEventListener("change", () => { for (const root of splitSessions.keys()) if (root.isConnected) refreshSplit(root); });
|
|
817
|
+
const codeFeedback = new Map();
|
|
818
|
+
const clearCodeFeedback = (root) => {
|
|
819
|
+
const session = codeFeedback.get(root);
|
|
820
|
+
if (session?.timer) clearTimeout(session.timer);
|
|
821
|
+
codeFeedback.delete(root);
|
|
822
|
+
root.querySelectorAll('[data-bevel-transient="clipboard"]').forEach(node => node.remove());
|
|
823
|
+
};
|
|
824
|
+
const refreshCode = (root) => {
|
|
825
|
+
const wrapped = root.hasAttribute("data-bevel-wrap");
|
|
826
|
+
for (const button of root.querySelectorAll("[data-bevel-code-wrap]")) if (button.getAttribute("aria-pressed") !== String(wrapped)) button.setAttribute("aria-pressed", String(wrapped));
|
|
827
|
+
};
|
|
828
|
+
const diffVersions = new WeakMap();
|
|
829
|
+
const refreshDiff = (root) => {
|
|
830
|
+
const template = root.querySelector("template[data-bevel-diff-records]");
|
|
831
|
+
if (!template) return;
|
|
832
|
+
const layout = root.dataset.bevelLayout === "split" ? "split" : "unified";
|
|
833
|
+
if (root.dataset.bevelLayout !== layout) root.dataset.bevelLayout = layout;
|
|
834
|
+
for (const button of root.querySelectorAll("button[data-bevel-layout]")) if (button.getAttribute("aria-pressed") !== String(button.dataset.bevelLayout === layout)) button.setAttribute("aria-pressed", String(button.dataset.bevelLayout === layout));
|
|
835
|
+
const signature = `${layout}:${template.innerHTML}:${root.dataset.bevelFilename || ""}:${root.dataset.bevelHunk || ""}`;
|
|
836
|
+
if (diffVersions.get(root) === signature && root.querySelector('[data-bevel-generated="diff"]')) return;
|
|
837
|
+
const records = [...template.content.querySelectorAll("[data-bevel-kind]")];
|
|
838
|
+
for (const count of root.querySelectorAll(".bevel-diff__count[data-bevel-kind]")) {
|
|
839
|
+
const kind = count.dataset.bevelKind, text = `${kind === "remove" ? "−" : "+"}${records.filter(record => record.dataset.bevelKind === kind).length}`;
|
|
840
|
+
if (count.textContent !== text) count.textContent = text;
|
|
841
|
+
}
|
|
842
|
+
const line = (record, side) => {
|
|
843
|
+
const row = document.createElement("div"), kind = record.dataset.bevelKind, code = record.querySelector("code");
|
|
844
|
+
row.className = "bevel-diff__line"; row.dataset.bevelKind = kind; row.dataset.bevelKey = record.dataset.bevelKey || "";
|
|
845
|
+
row.setAttribute("role", "listitem");
|
|
846
|
+
const number = document.createElement("span"), symbol = document.createElement("span"), label = document.createElement("span");
|
|
847
|
+
number.textContent = record.dataset[side === "before" || kind === "remove" ? "bevelOld" : "bevelNew"] || ""; number.setAttribute("aria-hidden", "true");
|
|
848
|
+
symbol.textContent = kind === "add" ? "+" : kind === "remove" ? "−" : " "; symbol.setAttribute("aria-hidden", "true");
|
|
849
|
+
label.className = "bevel-sr-only"; label.textContent = `${kind === "add" ? "Added" : kind === "remove" ? "Removed" : "Unchanged"}, line ${number.textContent}: `;
|
|
850
|
+
row.append(number, symbol, label, code ? code.cloneNode(true) : document.createElement("code"));
|
|
851
|
+
return row;
|
|
852
|
+
};
|
|
853
|
+
const body = document.createElement("div"); body.className = "bevel-diff__body"; body.dataset.bevelGenerated = "diff";
|
|
854
|
+
if (layout === "unified") {
|
|
855
|
+
const hunk = document.createElement("div"); hunk.className = "bevel-diff__hunk"; hunk.textContent = root.dataset.bevelHunk || "";
|
|
856
|
+
const list = document.createElement("div"); list.setAttribute("role", "list"); list.setAttribute("aria-label", "Changes");
|
|
857
|
+
list.append(...records.map(record => line(record))); body.append(hunk, list);
|
|
858
|
+
} else for (const side of ["before", "after"]) {
|
|
859
|
+
const section = document.createElement("section"), title = document.createElement("h3"), filename = document.createElement("small"), list = document.createElement("div");
|
|
860
|
+
title.textContent = side === "before" ? "Before " : "After "; filename.textContent = root.dataset.bevelFilename || ""; title.append(filename);
|
|
861
|
+
list.setAttribute("role", "list"); list.setAttribute("aria-label", side === "before" ? "Before" : "After");
|
|
862
|
+
list.append(...records.filter(record => record.dataset.bevelKind !== (side === "before" ? "add" : "remove")).map(record => line(record, side)));
|
|
863
|
+
section.append(title, list); body.append(section);
|
|
864
|
+
}
|
|
865
|
+
const old = root.querySelector('[data-bevel-generated="diff"]');
|
|
866
|
+
if (old) old.replaceWith(body); else template.after(body);
|
|
867
|
+
diffVersions.set(root, signature);
|
|
868
|
+
};
|
|
869
|
+
const refreshPager = (root, notifyClamp = false) => {
|
|
870
|
+
const target = root.dataset.bevelTarget && document.getElementById(root.dataset.bevelTarget), grid = target?.matches(".bevel-grid") ? target : null, items = target && !grid ? [...target.children] : [], remote = grid?.dataset.bevelDataMode === "remote";
|
|
871
|
+
if (grid) {
|
|
872
|
+
const page = grid.dataset.page || "1", size = grid.dataset.pageSize || "10";
|
|
873
|
+
if (root.dataset.bevelPage !== page) root.dataset.bevelPage = page;
|
|
874
|
+
if (root.dataset.bevelPageSize !== size) root.dataset.bevelPageSize = size;
|
|
875
|
+
if (remote) {
|
|
876
|
+
if (grid.hasAttribute("data-bevel-total")) {
|
|
877
|
+
if (root.dataset.bevelTotal !== grid.dataset.bevelTotal) root.dataset.bevelTotal = grid.dataset.bevelTotal;
|
|
878
|
+
}
|
|
879
|
+
else if (root.hasAttribute("data-bevel-total")) root.removeAttribute("data-bevel-total");
|
|
880
|
+
} else {
|
|
881
|
+
const view = grid.querySelector('.bevel-grid__view[aria-pressed="true"]');
|
|
882
|
+
const total = String(searchedGridRows(grid).filter(row => matchesView(row, view)).length);
|
|
883
|
+
if (root.dataset.bevelTotal !== total) root.dataset.bevelTotal = total;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (target && !grid && root.dataset.bevelTotal !== String(items.length)) root.dataset.bevelTotal = String(items.length);
|
|
887
|
+
const knownTotal = root.hasAttribute("data-bevel-total"), page = Math.max(1, Number(root.dataset.bevelPage) || 1), size = Math.max(1, Number(root.dataset.bevelPageSize) || 10), total = knownTotal ? Math.max(0, Number(root.dataset.bevelTotal) || 0) : null, pages = total == null ? null : Math.max(1, Math.ceil(total / size));
|
|
888
|
+
const current = pages == null ? page : Math.min(page, pages), normalized = String(current), clamped = current !== page; if (root.dataset.bevelPage !== normalized) root.dataset.bevelPage = normalized;
|
|
889
|
+
const previous = root.querySelector('[data-bevel-page="prev"]'), next = root.querySelector('[data-bevel-page="next"]');
|
|
890
|
+
if (previous?.hasAttribute("disabled") !== (current <= 1)) previous?.toggleAttribute("disabled", current <= 1);
|
|
891
|
+
if (next?.hasAttribute("disabled") !== (pages != null && current >= pages)) next?.toggleAttribute("disabled", pages != null && current >= pages);
|
|
892
|
+
for (const control of root.querySelectorAll("select[data-bevel-page-size]")) {
|
|
893
|
+
if (control.value !== String(size)) control.value = String(size);
|
|
894
|
+
for (const option of control.options) if (option.hasAttribute("selected") !== option.selected) option.toggleAttribute("selected", option.selected);
|
|
895
|
+
}
|
|
896
|
+
const list = root.querySelector(".bevel-pagination__pages");
|
|
897
|
+
if (list) {
|
|
898
|
+
if (pages == null) {
|
|
899
|
+
for (const item of [...list.children]) if (!item.matches('[data-bevel-page="prev"],[data-bevel-page="next"]')) item.remove();
|
|
900
|
+
}
|
|
901
|
+
else renderStandalonePages(list, pages, current);
|
|
902
|
+
}
|
|
903
|
+
const out = root.querySelector(".bevel-pagination__range"), label = total == null ? `${(current - 1) * size + 1} to ${current * size}` : total ? `${(current - 1) * size + 1} to ${Math.min(current * size, total)} of ${total}` : "0 of 0"; if (out && out.textContent !== label) out.textContent = label;
|
|
904
|
+
if (items.length) { const first = (current - 1) * size, last = first + size; items.forEach((item, index) => { const hidden = index < first || index >= last; if (item.hidden !== hidden) item.hidden = hidden; }); }
|
|
905
|
+
if (clamped && notifyClamp) emit(root, "bevel:change", { component: "pagination", action: "page", key: String(current), previous: page, next: current });
|
|
906
|
+
};
|
|
907
|
+
const treeItems = (root) => [...root.querySelectorAll('[role="treeitem"][data-bevel-key], button[data-bevel-key], summary[data-bevel-key], a[data-bevel-key]')].filter((item) => item.closest(".bevel-tree") === root);
|
|
908
|
+
const treeVisible = (root) => treeItems(root).filter((item) => {
|
|
909
|
+
if (disabled(item) || item.hidden || item.closest("[hidden]")) return false;
|
|
910
|
+
for (let branch = item.parentElement?.closest("details"); branch && root.contains(branch); branch = branch.parentElement?.closest("details")) if (!branch.open && item !== branch.querySelector(":scope > summary")) return false;
|
|
911
|
+
return true;
|
|
912
|
+
});
|
|
913
|
+
const refreshTree = (root) => {
|
|
914
|
+
if (root.getAttribute("role") !== "tree") root.setAttribute("role", "tree");
|
|
915
|
+
const items = treeItems(root), selected = root.dataset.bevelSelected || "";
|
|
916
|
+
for (const branch of root.querySelectorAll("details")) { if (branch.closest(".bevel-tree") !== root) continue; const value = String(branch.open), summary = branch.querySelector(":scope > summary"); if (branch.dataset.bevelExpanded !== value) branch.dataset.bevelExpanded = value; if (summary && summary.getAttribute("aria-expanded") !== value) summary.setAttribute("aria-expanded", value); }
|
|
917
|
+
for (const item of items) { if (item.getAttribute("role") !== "treeitem") item.setAttribute("role", "treeitem"); const value = String(item.dataset.bevelKey === selected); if (item.getAttribute("aria-selected") !== value) item.setAttribute("aria-selected", value); }
|
|
918
|
+
const visible = treeVisible(root), current = visible.find((item) => item === document.activeElement) || visible.find((item) => item.tabIndex === 0) || visible.find((item) => item.dataset.bevelKey === selected) || visible[0];
|
|
919
|
+
for (const item of items) { const value = item === current ? "0" : "-1"; if (item.getAttribute("tabindex") !== value) item.setAttribute("tabindex", value); }
|
|
920
|
+
};
|
|
921
|
+
const refreshSidebar = (root) => {
|
|
922
|
+
const collapsed = root.hasAttribute("data-bevel-collapsed");
|
|
923
|
+
for (const button of root.querySelectorAll("[data-bevel-sidebar-toggle]")) { const expanded = String(!collapsed), label = collapsed ? "Expand sidebar" : "Collapse sidebar"; if (button.getAttribute("aria-expanded") !== expanded) button.setAttribute("aria-expanded", expanded); if (button.getAttribute("aria-label") !== label) button.setAttribute("aria-label", label); if (button.title !== label) button.title = label; }
|
|
924
|
+
};
|
|
925
|
+
const refreshShell = (root) => {
|
|
926
|
+
const open = String(root.hasAttribute("data-bevel-nav-open"));
|
|
927
|
+
for (const button of root.querySelectorAll("[data-bevel-shell-nav]")) if (button.getAttribute("aria-expanded") !== open) button.setAttribute("aria-expanded", open);
|
|
928
|
+
for (const side of root.querySelectorAll(".bevel-app-shell__side")) {
|
|
929
|
+
const collapsed = side.hasAttribute("data-bevel-collapsed"), expanded = String(!collapsed);
|
|
930
|
+
for (const button of side.querySelectorAll("[data-bevel-shell-collapse]")) { const label = collapsed ? button.dataset.bevelLabelExpand || "Expand sidebar" : button.dataset.bevelLabelCollapse || "Collapse sidebar"; if (button.getAttribute("aria-expanded") !== expanded) button.setAttribute("aria-expanded", expanded); if (button.getAttribute("aria-label") !== label) button.setAttribute("aria-label", label); if (button.title !== label) button.title = label; }
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
const wizardPanels = (root) => [...root.querySelectorAll(".bevel-wizard__panel")].filter((panel) => panel.closest(".bevel-wizard") === root);
|
|
934
|
+
const wizardKeyed = (panels) => panels.some((panel) => panel.hasAttribute("data-bevel-key"));
|
|
935
|
+
const wizardKey = (panels, panel, index = panels.indexOf(panel)) => wizardKeyed(panels) ? panel.dataset.bevelKey : String(index);
|
|
936
|
+
const wizardControls = (panel) => [...panel.querySelectorAll("input,select,textarea")].filter((control) => control.closest(".bevel-wizard__panel") === panel && control.closest(".bevel-wizard") === panel.closest(".bevel-wizard"));
|
|
937
|
+
const invalidControls = (controls) => [...controls].filter((control) => !disabled(control) && !control.closest('.bevel-wizard__panel[disabled],.bevel-wizard__panel[aria-disabled="true"]') && control.willValidate && !control.validity.valid);
|
|
938
|
+
const wizardInvalid = (panels) => invalidControls(panels.flatMap(wizardControls));
|
|
939
|
+
const setupWizardForm = (root) => {
|
|
940
|
+
const form = root.closest("form");
|
|
941
|
+
if (!form?.matches("[data-bevel-validate]")) return;
|
|
942
|
+
let session = wizardForms.get(form);
|
|
943
|
+
if (!session) {
|
|
944
|
+
session = { authoredNoValidate: form.noValidate, roots: new Set(), initialSteps: new Map() };
|
|
945
|
+
wizardForms.set(form, session);
|
|
946
|
+
form.noValidate = true;
|
|
947
|
+
}
|
|
948
|
+
session.roots.add(root);
|
|
949
|
+
if (!session.initialSteps.has(root)) session.initialSteps.set(root, root.dataset.bevelStep);
|
|
950
|
+
};
|
|
951
|
+
const refreshWizard = (root) => {
|
|
952
|
+
const panels = wizardPanels(root), keyed = wizardKeyed(panels);
|
|
953
|
+
const key = (panel, index) => keyed ? panel.dataset.bevelKey : String(index);
|
|
954
|
+
let index = panels.findIndex((panel, i) => key(panel, i) === String(root.dataset.bevelStep ?? "0") && !disabled(panel));
|
|
955
|
+
if (index < 0) index = panels.findIndex((panel) => !disabled(panel));
|
|
956
|
+
if (index < 0) return;
|
|
957
|
+
const active = key(panels[index], index); if (root.dataset.bevelStep !== active) root.dataset.bevelStep = active;
|
|
958
|
+
panels.forEach((panel, i) => { if (panel.hidden !== (i !== index)) panel.hidden = i !== index; panel.toggleAttribute("inert", i !== index); });
|
|
959
|
+
root.querySelectorAll(":scope > .bevel-wizard__steps > .bevel-wizard__step").forEach((el, i) => {
|
|
960
|
+
const state = i < index ? "done" : i === index ? "current" : "next";
|
|
961
|
+
if (el.dataset.bevelState !== state) el.dataset.bevelState = state;
|
|
962
|
+
if (i === index && el.getAttribute("aria-current") !== "step") el.setAttribute("aria-current", "step"); else if (i !== index) el.removeAttribute("aria-current");
|
|
963
|
+
const panel = panels[i];
|
|
964
|
+
el.toggleAttribute("disabled", !panel || disabled(panel));
|
|
965
|
+
const inactive = String(!panel || disabled(panel));
|
|
966
|
+
if (el.getAttribute("aria-disabled") !== inactive) el.setAttribute("aria-disabled", inactive);
|
|
967
|
+
const note = el.querySelector(".bevel-wizard__step-note");
|
|
968
|
+
const label = i < index ? "Complete" : i === index ? "Current step" : "Up next";
|
|
969
|
+
if (note && note.textContent !== label) note.textContent = label;
|
|
970
|
+
});
|
|
971
|
+
setupWizardForm(root);
|
|
972
|
+
};
|
|
973
|
+
const revealWizardControl = (control) => {
|
|
974
|
+
const panel = control?.closest(".bevel-wizard__panel"), root = panel?.closest(".bevel-wizard");
|
|
975
|
+
if (!panel || !root) return false;
|
|
976
|
+
const panels = wizardPanels(root);
|
|
977
|
+
root.dataset.bevelStep = wizardKey(panels, panel);
|
|
978
|
+
refreshWizard(root);
|
|
979
|
+
return true;
|
|
980
|
+
};
|
|
981
|
+
const setFieldError = (message, text) => {
|
|
982
|
+
if (!message) return;
|
|
983
|
+
(message.querySelector("[data-bevel-error-message]") || message).textContent = text;
|
|
984
|
+
message.hidden = !text;
|
|
985
|
+
};
|
|
986
|
+
const refreshErrors = (form, controls = [...form.elements]) => {
|
|
987
|
+
const summary = form.querySelector(".bevel-errors"), list = summary?.querySelector(".bevel-errors__list");
|
|
988
|
+
const candidates = new Set(controls);
|
|
989
|
+
const invalid = invalidControls(candidates);
|
|
990
|
+
if (!summary || !list) return invalid;
|
|
991
|
+
const reported = new Set(invalid);
|
|
992
|
+
for (const control of form.elements) if (control instanceof HTMLElement && candidates.has(control) && control.willValidate && !reported.has(control)) {
|
|
993
|
+
control.removeAttribute("aria-invalid");
|
|
994
|
+
const authored = (control.dataset.bevelAuthoredDescribedby || "").split(/\s+/).filter(Boolean);
|
|
995
|
+
if (control.dataset.bevelErrorId) {
|
|
996
|
+
setFieldError(document.getElementById(control.dataset.bevelErrorId), "");
|
|
997
|
+
const ids = (control.getAttribute("aria-describedby") || "").split(/\s+/).filter((id) => id !== control.dataset.bevelErrorId);
|
|
998
|
+
if (ids.length) control.setAttribute("aria-describedby", ids.join(" ")); else control.removeAttribute("aria-describedby");
|
|
999
|
+
}
|
|
1000
|
+
if (authored.length) control.setAttribute("aria-describedby", authored.join(" "));
|
|
1001
|
+
}
|
|
1002
|
+
list.replaceChildren(...invalid.map((control, index) => {
|
|
1003
|
+
if (!control.id) control.id = `${form.id || "bevel-form"}-invalid-${index + 1}`;
|
|
1004
|
+
control.setAttribute("aria-invalid", "true");
|
|
1005
|
+
let message = form.querySelector(`[data-bevel-error-for="${CSS.escape(control.id)}"]`);
|
|
1006
|
+
if (!message) { message = document.createElement("small"); message.className = "bevel-field-error"; message.dataset.bevelErrorFor = control.id; control.insertAdjacentElement("afterend", message); }
|
|
1007
|
+
if (!message.id) message.id = `${control.id}-error`;
|
|
1008
|
+
if (!control.dataset.bevelAuthoredDescribedby) control.dataset.bevelAuthoredDescribedby = control.getAttribute("aria-describedby") || "";
|
|
1009
|
+
const validationMessage = control.validity.customError ? control.validationMessage : control.dataset.bevelValidationMessage || control.validationMessage;
|
|
1010
|
+
control.dataset.bevelErrorId = message.id; setFieldError(message, validationMessage);
|
|
1011
|
+
const ids = new Set((control.getAttribute("aria-describedby") || "").split(/\s+/).filter(Boolean)); ids.add(message.id); control.setAttribute("aria-describedby", [...ids].join(" "));
|
|
1012
|
+
const item = document.createElement("li"), link = document.createElement("a"); link.href = `#${control.id}`; link.textContent = validationMessage; link.addEventListener("click", event => { event.preventDefault(); const focus = () => { control.focus(); control.scrollIntoView({ block: "center" }); }; if (revealWizardControl(control)) requestAnimationFrame(focus); else focus(); }); item.append(link); return item;
|
|
1013
|
+
}));
|
|
1014
|
+
summary.hidden = invalid.length === 0;
|
|
1015
|
+
return invalid;
|
|
1016
|
+
};
|
|
1017
|
+
const kanbanCards = root => [...root.querySelectorAll(".bevel-kanban__list > .bevel-kanban__card")].filter(card => card.closest(".bevel-kanban") === root);
|
|
1018
|
+
const kanbanSelected = root => kanbanCards(root).find(card => card.dataset.bevelKey === root.dataset.bevelSelected);
|
|
1019
|
+
const kanbanItems = card => [...card.querySelectorAll('.bevel-kanban__checklist-data input[data-bevel-check-key]')];
|
|
1020
|
+
const kanbanDisabled = node => disabled(node) || disabled(node.closest(".bevel-kanban"));
|
|
1021
|
+
const kanbanText = (node, text) => { if (node && node.textContent !== String(text)) node.textContent = String(text); };
|
|
1022
|
+
const kanbanAttr = (node, name, value) => { if (node && node.getAttribute(name) !== String(value)) node.setAttribute(name, String(value)); };
|
|
1023
|
+
const kanbanFlag = (node, name, value) => { if (node && node.hasAttribute(name) !== !!value) node.toggleAttribute(name, !!value); };
|
|
1024
|
+
const kanbanNode = (tag, className, text) => { const node = document.createElement(tag); if (className) node.className = className; if (text !== undefined) node.textContent = text; return node; };
|
|
1025
|
+
const kanbanSnapshot = card => card ? { key: card.dataset.bevelKey, column: card.closest("[data-bevel-column-key]")?.dataset.bevelColumnKey || "", title: card.querySelector(".bevel-kanban__title,h3")?.textContent || "", description: card.querySelector(".bevel-kanban__description,.bevel-kanban__desc")?.textContent || "", assignee: card.dataset.bevelAssignee || "", priority: card.dataset.bevelPriority || "normal", due: card.dataset.bevelDue || "", labels: [...card.querySelectorAll(".bevel-kanban__labels > [data-bevel-label]")].map(label => label.dataset.bevelLabel), archived: card.hasAttribute("data-bevel-archived") } : null;
|
|
1026
|
+
const kanbanToday = root => {
|
|
1027
|
+
const now = new Date();
|
|
1028
|
+
return root.dataset.bevelToday || `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,"0")}-${String(now.getDate()).padStart(2,"0")}`;
|
|
1029
|
+
};
|
|
1030
|
+
const kanbanDue = (root, value, done = false) => {
|
|
1031
|
+
const node = kanbanNode("span", "bevel-kanban__due");
|
|
1032
|
+
node.dataset.bevelDue = done ? "done" : value === kanbanToday(root) ? "today" : value && value < kanbanToday(root) ? "overdue" : "";
|
|
1033
|
+
node.textContent = value ? `${value === kanbanToday(root) && !done ? "Today, " : ""}${new Date(`${value}T12:00:00`).toLocaleDateString("en-US", {month:"short", day:"2-digit"})}` : "No due date";
|
|
1034
|
+
const text=node.textContent;node.innerHTML='<svg aria-hidden="true" width="12" height="12" viewBox="0 0 256 256" fill="currentColor"><path d="M208,32H184V24a8,8,0,0,0-16,0v8H88V24a8,8,0,0,0-16,0v8H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM72,48v8a8,8,0,0,0,16,0V48h80v8a8,8,0,0,0,16,0V48h24V80H48V48ZM208,208H48V96H208V208Z"/></svg>';node.append(document.createTextNode(text));
|
|
1035
|
+
return node;
|
|
1036
|
+
};
|
|
1037
|
+
const kanbanMember = (root, key) => root.querySelector(`[data-bevel-kanban-form] [name="assignee"] option[value="${CSS.escape(key)}"]`)?.textContent || key || "Unassigned";
|
|
1038
|
+
const kanbanLabels = values => values.map(value => { const node = kanbanNode("span", "bevel-kanban__label", value[0]?.toUpperCase() + value.slice(1)); node.dataset.bevelLabel = value; return node; });
|
|
1039
|
+
const kanbanProgress = (scope, items) => {
|
|
1040
|
+
const done = items.filter(item => item.checked).length, percent = items.length ? Math.round(100 * done / items.length) : 0;
|
|
1041
|
+
kanbanText(scope.querySelector(".bevel-kanban__check-count"), `${done}/${items.length}`);
|
|
1042
|
+
const bar = scope.querySelector(".bevel-kanban__check-bar b"); if (bar && bar.style.width !== `${percent}%`) bar.style.width = `${percent}%`;
|
|
1043
|
+
kanbanAttr(scope, "aria-label", `Checklist ${done} of ${items.length}`);
|
|
1044
|
+
};
|
|
1045
|
+
const kanbanInspectorStates = new WeakMap();
|
|
1046
|
+
const refreshKanbanInspector = root => {
|
|
1047
|
+
const panel = root.querySelector("[data-bevel-kanban-inspector]"), template = root.querySelector("template[data-bevel-kanban-inspector-template]"), card = kanbanSelected(root);
|
|
1048
|
+
if (!panel) return;
|
|
1049
|
+
kanbanFlag(panel, "hidden", !card);
|
|
1050
|
+
const work = root.querySelector(".bevel-kanban__work"); if (work && work.classList.contains("bevel-kanban--has-panel") !== !!card) work.classList.toggle("bevel-kanban--has-panel", !!card);
|
|
1051
|
+
if (!card) {
|
|
1052
|
+
panel.querySelectorAll('[data-bevel-transient="kanban-inspector-body"]').forEach(node => node.remove());
|
|
1053
|
+
kanbanInspectorStates.delete(panel);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
if (!template) return;
|
|
1057
|
+
const value = kanbanSnapshot(card), items = kanbanItems(card), details = card.querySelector("template[data-bevel-task-details]");
|
|
1058
|
+
const signature = JSON.stringify([value, items.map(item => [item.dataset.bevelCheckKey, item.checked, item.parentElement.textContent]), details?.innerHTML, card.hidden, disabled(root)]);
|
|
1059
|
+
if (kanbanInspectorStates.get(panel) === signature && panel.querySelector('[data-bevel-transient="kanban-inspector-body"]')) return;
|
|
1060
|
+
kanbanInspectorStates.set(panel, signature);
|
|
1061
|
+
const body = kanbanNode("div"); body.dataset.bevelTransient = "kanban-inspector-body"; body.append(template.content.cloneNode(true));
|
|
1062
|
+
kanbanText(body.querySelector(".bevel-kanban__panel-head .bevel-kanban__id"), value.key);
|
|
1063
|
+
kanbanText(body.querySelector("h2"), value.title);
|
|
1064
|
+
kanbanText(body.querySelector(".bevel-kanban__panel-lede"), value.description || "No description");
|
|
1065
|
+
const status = body.querySelector(".bevel-kanban__status"); status.dataset.bevelColumnKey = value.column;
|
|
1066
|
+
kanbanText(status, card.closest(".bevel-kanban__column")?.querySelector("h2")?.textContent || value.column);
|
|
1067
|
+
const field = name => body.querySelector(`[data-bevel-task-field="${name}"]`);
|
|
1068
|
+
const member = kanbanNode("span", "bevel-avatar bevel-avatar--small", value.assignee || "?"); member.dataset.who = value.assignee; member.title = kanbanMember(root, value.assignee);
|
|
1069
|
+
field("assignee").replaceChildren(member, document.createTextNode(` ${member.title}`));
|
|
1070
|
+
const priority = card.querySelector('.bevel-kanban__priority')?.cloneNode(true) || kanbanNode("span", "bevel-kanban__priority", value.priority); priority.dataset.bevelPriority = value.priority; field("priority").replaceChildren(priority);
|
|
1071
|
+
field("due").replaceChildren(kanbanDue(root, value.due, value.column === "done"));
|
|
1072
|
+
field("labels").replaceChildren(...kanbanLabels(value.labels));
|
|
1073
|
+
const checklist = body.querySelector("[data-bevel-kanban-checklist]");
|
|
1074
|
+
for (const source of items) {
|
|
1075
|
+
const row = kanbanNode("div"); row.dataset.bevelCheckRow = source.dataset.bevelCheckKey;
|
|
1076
|
+
const label = kanbanNode("label", "bevel-checkbox bevel-checkbox--small bevel-kanban__check-item");
|
|
1077
|
+
const input = kanbanNode("input", "bevel-sr-only"); input.type = "checkbox"; input.checked = source.checked; input.toggleAttribute("checked", source.checked); input.dataset.bevelCheckKey = source.dataset.bevelCheckKey;
|
|
1078
|
+
const box = kanbanNode("span", "bevel-checkbox__box"); box.ariaHidden = "true";
|
|
1079
|
+
label.append(input, box, kanbanNode("span", "bevel-checkbox__label", source.parentElement.textContent.trim()));
|
|
1080
|
+
const remove = kanbanNode("button", "bevel-kanban__icon"); remove.type = "button"; remove.dataset.bevelKanbanCheckRemove = source.dataset.bevelCheckKey; remove.setAttribute("aria-label", `Remove ${source.parentElement.textContent.trim()}`); remove.innerHTML = X_SVG;
|
|
1081
|
+
row.append(label, remove); checklist.append(row);
|
|
1082
|
+
}
|
|
1083
|
+
kanbanProgress(checklist, items);
|
|
1084
|
+
const extra = body.querySelector("[data-bevel-task-details]");
|
|
1085
|
+
if (details) extra.append(details.content.cloneNode(true));
|
|
1086
|
+
const estimate=extra.querySelector('[data-bevel-task-field="estimate"]'),created=extra.querySelector('[data-bevel-task-created]');
|
|
1087
|
+
kanbanText(field('estimate'),estimate?.textContent || '');kanbanFlag(field('estimate'),'hidden',!estimate);kanbanFlag(field('estimate').previousElementSibling,'hidden',!estimate);estimate?.closest('dl').remove();
|
|
1088
|
+
kanbanText(body.querySelector('.bevel-kanban__panel-foot [data-bevel-task-created]'),created?.textContent || '');created?.remove();
|
|
1089
|
+
kanbanFlag(body.querySelector("[data-bevel-kanban-outside]"), "hidden", !card.hidden);
|
|
1090
|
+
kanbanText(body.querySelector("[data-bevel-kanban-outside]"), value.archived ? "This task is archived. Restore it to return it to the board." : "This task is outside the current filters.");
|
|
1091
|
+
body.querySelectorAll('[data-bevel-action="open-task"],[data-bevel-action="task-options"],[data-bevel-action="comment"]').forEach(node => node.dataset.bevelKey = value.key);
|
|
1092
|
+
const archive = body.querySelector('[data-bevel-open]'); kanbanFlag(archive, "hidden", value.archived);
|
|
1093
|
+
const confirm = root.querySelector('[data-bevel-kanban-archive]')?.closest("[popover]"); if (archive && confirm) { archive.dataset.bevelOpen = confirm.id; archive.setAttribute("aria-controls", confirm.id); }
|
|
1094
|
+
kanbanFlag(body.querySelector('[data-bevel-kanban-restore]'), "hidden", !value.archived);
|
|
1095
|
+
const destination = body.querySelector('[data-bevel-kanban-move-column]'); destination.replaceChildren(...[...root.querySelectorAll('.bevel-kanban__column')].filter(column => column.closest('.bevel-kanban') === root).map(column => { const option = new Option(column.querySelector('h2')?.textContent || column.dataset.bevelColumnKey, column.dataset.bevelColumnKey); option.selected = option.value === value.column; return option; }));
|
|
1096
|
+
if (disabled(root)) body.querySelectorAll('button,input,select,textarea').forEach(node => node.disabled = true);
|
|
1097
|
+
panel.replaceChildren(body); kanbanAttr(panel, "aria-label", `Task ${value.key}`);
|
|
1098
|
+
};
|
|
1099
|
+
const kanbanFaces = new WeakMap();
|
|
1100
|
+
const refreshKanbanSelection = (root, cards = kanbanCards(root)) => {
|
|
1101
|
+
const loaded = new Set(cards.map(card => card.dataset.bevelKey));
|
|
1102
|
+
if (root.dataset.bevelSelected && !loaded.has(root.dataset.bevelSelected)) root.removeAttribute('data-bevel-selected');
|
|
1103
|
+
let keys = [];
|
|
1104
|
+
if (root.hasAttribute('data-bevel-selected-keys')) {
|
|
1105
|
+
try { const value = JSON.parse(root.dataset.bevelSelectedKeys); if (Array.isArray(value)) keys = [...new Set(value.map(String))].filter(key => loaded.has(key)); } catch {}
|
|
1106
|
+
kanbanAttr(root, 'data-bevel-selected-keys', JSON.stringify(keys));
|
|
1107
|
+
}
|
|
1108
|
+
const selected = new Set([...keys, root.dataset.bevelSelected].filter(Boolean));
|
|
1109
|
+
for (const card of cards) {
|
|
1110
|
+
const key = card.dataset.bevelKey, active = selected.has(key);
|
|
1111
|
+
if (card.classList.contains('bevel-kanban--selected') !== active) card.classList.toggle('bevel-kanban--selected', active);
|
|
1112
|
+
kanbanAttr(card, 'aria-selected', active);
|
|
1113
|
+
if (key === root.dataset.bevelSelected) kanbanAttr(card, 'aria-current', 'true'); else kanbanFlag(card, 'aria-current', false);
|
|
1114
|
+
}
|
|
1115
|
+
refreshKanbanInspector(root);
|
|
1116
|
+
};
|
|
1117
|
+
const kanbanFilterKinds = ["assignee", "label", "priority", "due-from", "due-to"];
|
|
1118
|
+
const kanbanFiltered = root => !!((root.querySelector('.bevel-kanban__search input') || root.querySelector('input.bevel-kanban__search'))?.value.trim() || kanbanFilterKinds.some(kind => root.getAttribute(`data-bevel-filter-${kind}`)) || root.dataset.bevelFilterDue);
|
|
1119
|
+
const refreshKanban = root => {
|
|
1120
|
+
if (root.dataset.bevelDataMode === 'remote') { refreshKanbanSelection(root); return; }
|
|
1121
|
+
const cards = kanbanCards(root), search = root.querySelector(".bevel-kanban__search input") || root.querySelector("input.bevel-kanban__search"), query = (search?.value || "").trim().toLowerCase();
|
|
1122
|
+
const assignee = root.dataset.bevelFilterAssignee || "", label = root.dataset.bevelFilterLabel || "", priority = root.dataset.bevelFilterPriority || "", from = root.dataset.bevelFilterDueFrom || "", to = root.dataset.bevelFilterDueTo || "";
|
|
1123
|
+
for (const card of cards) {
|
|
1124
|
+
const value = kanbanSnapshot(card), done = value.column === "done", items = kanbanItems(card);
|
|
1125
|
+
const faceKey=JSON.stringify([value.priority,value.assignee,value.due,done,kanbanToday(root)]);
|
|
1126
|
+
if(kanbanFaces.get(card)!==faceKey){
|
|
1127
|
+
kanbanFaces.set(card,faceKey);
|
|
1128
|
+
const priority=card.querySelector('.bevel-kanban__priority');if(priority){kanbanAttr(priority,'data-bevel-priority',value.priority);if(priority.textContent.trim().toLowerCase()!==value.priority){for(const node of [...priority.childNodes])if(node.nodeType===Node.TEXT_NODE)node.remove();priority.append(document.createTextNode(value.priority));}}
|
|
1129
|
+
const due=card.querySelector('.bevel-kanban__card-foot .bevel-kanban__due');if(due){const next=kanbanDue(root,value.due,done);if(value.due===kanbanToday(root)&&!done)next.lastChild.textContent='Today';due.replaceWith(next);}
|
|
1130
|
+
const people=card.querySelector('.bevel-kanban__card-foot .bevel-kanban__people');if(people){people.replaceChildren();if(value.assignee){const avatar=kanbanNode('span','bevel-avatar bevel-avatar--small',value.assignee);avatar.dataset.who=value.assignee;avatar.title=kanbanMember(root,value.assignee);avatar.setAttribute('aria-label',avatar.title);people.append(avatar);}}
|
|
1131
|
+
}
|
|
1132
|
+
kanbanFlag(card, "hidden", value.archived || (query && !card.textContent.toLowerCase().includes(query)) || (assignee && value.assignee !== assignee) || (label && !value.labels.includes(label)) || (priority && value.priority !== priority) || (from && (!value.due || value.due < from)) || (to && (!value.due || value.due > to)) || (root.dataset.bevelFilterDue === "set" && !value.due) || (root.dataset.bevelFilterDue === "empty" && !!value.due));
|
|
1133
|
+
if (card.classList.contains("bevel-kanban--done") !== done) card.classList.toggle("bevel-kanban--done", done);
|
|
1134
|
+
let progress = card.querySelector(".bevel-kanban__check");
|
|
1135
|
+
if (!progress && items.length) { progress=kanbanNode('div','bevel-kanban__check');const bar=kanbanNode('i','bevel-kanban__check-bar');bar.append(kanbanNode('b'));progress.append(bar,kanbanNode('span','bevel-kanban__check-count'));card.insertBefore(progress,card.querySelector('.bevel-kanban__card-foot')); }
|
|
1136
|
+
if (progress) { kanbanProgress(progress, items); kanbanFlag(progress, "hidden", !items.length); }
|
|
1137
|
+
}
|
|
1138
|
+
const active = cards.filter(card => !card.hasAttribute("data-bevel-archived")), done = active.filter(card => card.closest("[data-bevel-column-key]")?.dataset.bevelColumnKey === "done").length;
|
|
1139
|
+
for (const column of root.querySelectorAll(".bevel-kanban__column")) {
|
|
1140
|
+
if(column.closest(".bevel-kanban") !== root) continue;
|
|
1141
|
+
const count = active.filter(card => card.closest(".bevel-kanban__column") === column).length, limit = Number(column.dataset.bevelWip || 0);
|
|
1142
|
+
kanbanText(column.querySelector(".bevel-kanban__count"), count);
|
|
1143
|
+
if (limit) { const wip = column.querySelector(".bevel-kanban__wip"); kanbanText(wip, `WIP ${count} / ${limit}`); kanbanAttr(wip, "data-full", count >= limit); }
|
|
1144
|
+
kanbanFlag(column.querySelector('[data-bevel-kanban-column-empty]'), "hidden", count !== 0);
|
|
1145
|
+
}
|
|
1146
|
+
const percent = active.length ? Math.round(100 * done / active.length) : 0;
|
|
1147
|
+
for (const [name, value] of Object.entries({total:active.length, done, open:active.length-done, blocked:active.filter(card => card.classList.contains('bevel-kanban--blocked')).length, progress:`${percent}%`, people:new Set(active.map(card => card.dataset.bevelAssignee).filter(Boolean)).size, working:active.filter(card => card.closest('[data-bevel-column-key]').dataset.bevelColumnKey === 'progress').length})) root.querySelectorAll(`[data-bevel-kanban-${name}]`).forEach(node => kanbanText(node, value));
|
|
1148
|
+
kanbanText(root.querySelector('[data-bevel-kanban-limit]'), `of ${root.querySelector('.bevel-kanban__column[data-bevel-column-key="progress"]')?.dataset.bevelWip || '∞'}`);
|
|
1149
|
+
kanbanText(root.querySelector('[data-bevel-kanban-due-label]'),from || to ? `Due: ${from || 'any'} to ${to || 'any'}` : 'Due: any time');
|
|
1150
|
+
kanbanAttr(root.querySelector('[data-bevel-open="kanban-due-filter"]'),'aria-pressed',!!(from || to));
|
|
1151
|
+
const meter = root.querySelector('[data-bevel-kanban-meter]'); if (meter && meter.style.width !== `${percent}%`) meter.style.width = `${percent}%`;
|
|
1152
|
+
const zero = active.length > 0 && !cards.some(card => !card.hidden);
|
|
1153
|
+
kanbanFlag(root, 'data-bevel-zero-results', zero);
|
|
1154
|
+
kanbanFlag(root.querySelector('[data-bevel-kanban-empty]'), 'hidden', active.length !== 0);
|
|
1155
|
+
kanbanFlag(root.querySelector('[data-bevel-kanban-zero]'), 'hidden', !zero);
|
|
1156
|
+
for (const control of root.querySelectorAll('[data-bevel-kanban-filter]')) {
|
|
1157
|
+
const value = root.getAttribute(`data-bevel-filter-${control.dataset.bevelKanbanFilter}`) || '';
|
|
1158
|
+
if (control.matches('select')) { if (value && ![...control.options].some(option => option.value === value)) control.add(new Option(value, value)); for (const option of control.options) { const selected = option.value === value; if (option.selected !== selected) option.selected = selected; kanbanFlag(option, 'selected', selected); } }
|
|
1159
|
+
else if (control.matches('input')) { if (control.value !== value) control.value = value; kanbanAttr(control, 'value', value); }
|
|
1160
|
+
}
|
|
1161
|
+
const labels = root.querySelector('[data-bevel-kanban-filter="label"]'); if (labels) for (const value of new Set(cards.flatMap(card => kanbanSnapshot(card).labels))) if (![...labels.options].some(option => option.value === value)) labels.add(new Option(value[0].toUpperCase()+value.slice(1), value));
|
|
1162
|
+
if (!kanbanFiltered(root) && root.hasAttribute('data-bevel-move-message')) root.removeAttribute('data-bevel-move-message');
|
|
1163
|
+
refreshKanbanSelection(root, cards);
|
|
1164
|
+
};
|
|
1165
|
+
const kanbanEditor = (root, card = null, column = "") => {
|
|
1166
|
+
const dialog = root.querySelector("[data-bevel-kanban-editor]"), form = dialog?.querySelector("[data-bevel-kanban-form]");
|
|
1167
|
+
if (!form) return false;
|
|
1168
|
+
const value = kanbanSnapshot(card) || { key:"", column, title:"", description:"", assignee:"", priority:"normal", due:"", labels:[] };
|
|
1169
|
+
for (const name of ['key','column','title','description','assignee','priority','due']) form.elements[name].value = value[name];
|
|
1170
|
+
form.elements.labels.value = value.labels.join(', '); form.elements.title.setCustomValidity('');
|
|
1171
|
+
kanbanText(form.querySelector('[data-bevel-kanban-form-heading]'), card ? `Edit ${value.key}` : 'New task');
|
|
1172
|
+
return openOverlay(dialog, {trigger:document.activeElement});
|
|
1173
|
+
};
|
|
1174
|
+
const updateKanbanCard = (card, value) => {
|
|
1175
|
+
for (const name of ['assignee','priority','due']) card.setAttribute(`data-bevel-${name}`, value[name]);
|
|
1176
|
+
kanbanText(card.querySelector('.bevel-kanban__title,h3'), value.title);
|
|
1177
|
+
let description = card.querySelector('.bevel-kanban__description,.bevel-kanban__desc'); if (!description) { description = kanbanNode('p','bevel-kanban__description bevel-kanban__desc'); card.querySelector('h3').after(description); }
|
|
1178
|
+
kanbanText(description, value.description); description.hidden = !value.description;
|
|
1179
|
+
const priority = card.querySelector('.bevel-kanban__priority'); if (priority) { priority.dataset.bevelPriority = value.priority; for(const text of [...priority.childNodes])if(text.nodeType===Node.TEXT_NODE)text.remove();priority.append(document.createTextNode(value.priority)); }
|
|
1180
|
+
let labels = card.querySelector('.bevel-kanban__labels'); if (!labels) { labels = kanbanNode('div','bevel-kanban__labels'); description.after(labels); } labels.replaceChildren(...kanbanLabels(value.labels));
|
|
1181
|
+
const root = card.closest('.bevel-kanban');
|
|
1182
|
+
const foot = card.querySelector('.bevel-kanban__card-foot'); if (foot && root) { const due = foot.querySelector('.bevel-kanban__due'); due?.replaceWith(kanbanDue(root,value.due,value.column === 'done')); const people = foot.querySelector('.bevel-kanban__people'); if (people) { people.replaceChildren(); if (value.assignee) { const avatar = kanbanNode('span','bevel-avatar bevel-avatar--small',value.assignee); avatar.dataset.who=value.assignee; avatar.title=kanbanMember(root,value.assignee); people.append(avatar); } } }
|
|
1183
|
+
};
|
|
1184
|
+
const kanbanCard = value => {
|
|
1185
|
+
const card = kanbanNode('article','bevel-kanban__card'); card.tabIndex=0; card.dataset.bevelKey=value.key;
|
|
1186
|
+
const top = kanbanNode('div','bevel-kanban__card-top'); top.append(kanbanNode('span','bevel-kanban__id',value.key),kanbanNode('span','bevel-kanban__priority'));
|
|
1187
|
+
const foot = kanbanNode('footer','bevel-kanban__card-foot'); foot.append(kanbanNode('span','bevel-kanban__due'),kanbanNode('span','bevel-kanban__people'));
|
|
1188
|
+
card.append(top,kanbanNode('h3','bevel-kanban__title'),foot); updateKanbanCard(card,value); return card;
|
|
1189
|
+
};
|
|
1190
|
+
const kanbanMove = (root, card, list, index) => {
|
|
1191
|
+
if (!card?.isConnected || !list?.isConnected || list.closest('.bevel-kanban') !== root || kanbanDisabled(card)) return false;
|
|
1192
|
+
const source = card.parentElement, siblings = [...list.children].filter(node => node.matches('.bevel-kanban__card') && node !== card), position = Math.max(0,Math.min(index,siblings.length));
|
|
1193
|
+
const previous = {column:source.closest('[data-bevel-column-key]').dataset.bevelColumnKey,index:[...source.children].filter(node=>node.matches('.bevel-kanban__card')).indexOf(card)}, next = {column:list.closest('[data-bevel-column-key]').dataset.bevelColumnKey,index:position};
|
|
1194
|
+
if (previous.column === next.column && previous.index === next.index) return false;
|
|
1195
|
+
return localOrRemote(root,'move',card.dataset.bevelKey,previous,next,()=>{ list.insertBefore(card,siblings[position] || list.querySelector('.bevel-kanban__add')); refreshKanban(root); card.focus(); },`move:${card.dataset.bevelKey}`);
|
|
1196
|
+
};
|
|
1197
|
+
const refreshActivity = (root) => {
|
|
1198
|
+
const expanded = root.hasAttribute("data-bevel-expanded");
|
|
1199
|
+
const groups = [...root.querySelectorAll("[data-bevel-activity-older]")].filter((group) => group.closest(".bevel-activity") === root);
|
|
1200
|
+
for (const group of groups) if (group.hidden === expanded) group.hidden = !expanded;
|
|
1201
|
+
const button = root.querySelector("[data-bevel-activity-more]");
|
|
1202
|
+
if (!button) return;
|
|
1203
|
+
const count = groups.reduce((total, group) => total + group.querySelectorAll("[data-bevel-key]").length, 0);
|
|
1204
|
+
button.setAttribute("aria-expanded", String(expanded));
|
|
1205
|
+
const label = button.querySelector(".bevel-button__label");
|
|
1206
|
+
if (!expanded) {
|
|
1207
|
+
button.disabled = count === 0;
|
|
1208
|
+
if (label) label.textContent = count === 1 ? "Show yesterday (1)" : `Show older (${count})`;
|
|
1209
|
+
} else if (root.hasAttribute("data-bevel-has-more")) {
|
|
1210
|
+
button.disabled = false;
|
|
1211
|
+
if (label) label.textContent = "Load more";
|
|
1212
|
+
} else {
|
|
1213
|
+
button.disabled = true;
|
|
1214
|
+
if (label) label.textContent = "All activity shown";
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
const showAvatarFallback = (image) => {
|
|
1218
|
+
const person = image.closest(".bevel-avatars__person");
|
|
1219
|
+
if (!person) return;
|
|
1220
|
+
person.dataset.bevelImageFailed = "";
|
|
1221
|
+
image.hidden = true;
|
|
1222
|
+
const fallback = person.querySelector(".bevel-avatars__fallback");
|
|
1223
|
+
if (fallback) fallback.hidden = false;
|
|
1224
|
+
};
|
|
1225
|
+
const refreshAvatars = (root) => {
|
|
1226
|
+
for (const person of root.querySelectorAll(".bevel-avatars__person")) {
|
|
1227
|
+
if (person.closest(".bevel-avatars") !== root) continue;
|
|
1228
|
+
const image = person.querySelector(".bevel-avatars__image"), fallback = person.querySelector(".bevel-avatars__fallback");
|
|
1229
|
+
if (!image || !fallback) continue;
|
|
1230
|
+
if (person.hasAttribute("data-bevel-image-failed") || (image.complete && image.naturalWidth === 0)) showAvatarFallback(image);
|
|
1231
|
+
else { image.hidden = false; fallback.hidden = true; }
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
const refreshChart = (root) => {
|
|
1235
|
+
for (const button of root.querySelectorAll("[data-bevel-series]")) {
|
|
1236
|
+
if (button.closest(".bevel-chart") !== root) continue;
|
|
1237
|
+
const visible = button.getAttribute("aria-pressed") !== "false", key = button.dataset.bevelSeries;
|
|
1238
|
+
button.setAttribute("aria-pressed", String(visible));
|
|
1239
|
+
for (const group of root.querySelectorAll(`[data-bevel-series-key="${CSS.escape(key)}"]`)) if (group.closest(".bevel-chart") === root) group.toggleAttribute("hidden", !visible);
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
const refresh = (scope = document, { source = "api" } = {}) => {
|
|
1243
|
+
const roots = (selector) => [...(scope.matches?.(selector) ? [scope] : []), ...scope.querySelectorAll(selector)];
|
|
1244
|
+
for (const [root, session] of [...sessions]) if (session.valid && (root === scope || scope.contains(root) || root.contains(scope)) && !session.valid()) session.cancel();
|
|
1245
|
+
roots(".bevel-grid").forEach((grid) => {
|
|
1246
|
+
if (grid.dataset.bevelDataMode !== "remote") { layoutGrid(grid); return; }
|
|
1247
|
+
syncPick(grid);
|
|
1248
|
+
if (grid.id) document.querySelectorAll(`.bevel-pagination[data-bevel-target="${CSS.escape(grid.id)}"]`).forEach(pager => refreshPager(pager));
|
|
1249
|
+
});
|
|
1250
|
+
roots(".bevel-files").forEach(syncFiles);
|
|
1251
|
+
roots(".bevel-slider__input").forEach(syncSlider);
|
|
1252
|
+
roots(".bevel-combobox").forEach(refreshCombo);
|
|
1253
|
+
roots(".bevel-multiselect").forEach(refreshMulti);
|
|
1254
|
+
roots(".bevel-pagination").forEach((root) => refreshPager(root, source === "api"));
|
|
1255
|
+
roots(".bevel-sidebar").forEach(refreshSidebar);
|
|
1256
|
+
roots(".bevel-app-shell").forEach(refreshShell);
|
|
1257
|
+
roots(".bevel-file-tree").forEach(syncFileTree);
|
|
1258
|
+
roots(".bevel-tree").forEach(refreshTree);
|
|
1259
|
+
roots(".bevel-code").forEach(refreshCode);
|
|
1260
|
+
roots(".bevel-diff").forEach(refreshDiff);
|
|
1261
|
+
roots(".bevel-split").forEach(refreshSplit);
|
|
1262
|
+
roots(".bevel-wizard").forEach(refreshWizard);
|
|
1263
|
+
roots(".bevel-kanban").forEach(refreshKanban);
|
|
1264
|
+
roots(".bevel-activity").forEach(refreshActivity);
|
|
1265
|
+
roots(".bevel-columns").forEach(refreshColumns);
|
|
1266
|
+
roots(".bevel-avatars").forEach(refreshAvatars);
|
|
1267
|
+
roots(".bevel-chart").forEach(refreshChart);
|
|
1268
|
+
roots("[data-bevel-open]:is(dialog,[popover])").sort((a, b) => Number(a.dataset.bevelOpenOrder || 0) - Number(b.dataset.bevelOpenOrder || 0)).forEach((panel) => {
|
|
1269
|
+
if (sessions.has(panel) && (panel.matches("dialog") ? panel.matches(":modal") : panel.matches(":popover-open"))) return;
|
|
1270
|
+
panel.removeAttribute("open");
|
|
1271
|
+
if (panel.matches("[popover]")) panel.hidePopover?.();
|
|
1272
|
+
openOverlay(panel, { trigger: panelTrigger(panel), source: "external" });
|
|
1273
|
+
});
|
|
1274
|
+
return scope;
|
|
1275
|
+
};
|
|
1276
|
+
const destroy = (scope, { preserve = [] } = {}) => {
|
|
1277
|
+
const owns = (node) => (node === scope || scope.contains?.(node)) && !preserve.some((root) => root === node || root.contains?.(node));
|
|
1278
|
+
for (const [root, session] of [...sessions]) if (owns(root) || (isAnchored(root) && session.trigger && owns(session.trigger))) {
|
|
1279
|
+
if (session.cancel) session.cancel(); else session.cleanup?.();
|
|
1280
|
+
sessions.delete(root);
|
|
1281
|
+
if (root.matches?.("dialog,[popover]")) {
|
|
1282
|
+
root.removeAttribute("data-bevel-open");
|
|
1283
|
+
if (root.matches("dialog") && root.open) root.close();
|
|
1284
|
+
else if (root.matches("[popover]") && root.matches(":popover-open")) root.hidePopover();
|
|
1285
|
+
const trigger = session.trigger?.isConnected ? session.trigger : panelTrigger(root);
|
|
1286
|
+
trigger?.setAttribute("aria-expanded", "false");
|
|
1287
|
+
setExpanded(root, false);
|
|
1288
|
+
root.style.removeProperty("left"); root.style.removeProperty("top"); root.style.removeProperty("--bevel-available-height");
|
|
1289
|
+
root.removeAttribute("data-bevel-geometry");
|
|
1290
|
+
queueMicrotask(() => { if (trigger?.isConnected && !disabled(trigger)) trigger.focus(); });
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
for (const root of requestScopes.keys()) if (owns(root)) { requestScopes.delete(root); root.removeAttribute("data-bevel-pending"); }
|
|
1294
|
+
for (const root of codeFeedback.keys()) if (owns(root)) clearCodeFeedback(root);
|
|
1295
|
+
for (const [root, session] of splitSessions) if (owns(root)) { session.drag?.stop(false); session.observer.disconnect(); splitSessions.delete(root); }
|
|
1296
|
+
for (const [form, session] of [...wizardForms]) {
|
|
1297
|
+
for (const root of [...session.roots]) if (owns(root)) session.roots.delete(root);
|
|
1298
|
+
if (!session.roots.size || owns(form)) {
|
|
1299
|
+
form.noValidate = session.authoredNoValidate;
|
|
1300
|
+
wizardForms.delete(form);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
for (const root of [...(scope.matches?.(".bevel-filter,.bevel-grid__cell[data-bevel-edit]") ? [scope] : []), ...scope.querySelectorAll(".bevel-filter,.bevel-grid__cell[data-bevel-edit]")]) if (owns(root)) clearGridDrafts(root);
|
|
1304
|
+
for(const root of [...(scope.matches?.('.bevel-kanban') ? [scope] : []), ...scope.querySelectorAll('.bevel-kanban')])if(owns(root)){root.querySelectorAll('[data-bevel-transient="kanban-inspector-body"]').forEach(node=>node.remove());clearKanbanDrafts(root);}
|
|
1305
|
+
window.Bevel?.richtext?.unmount?.(scope, { preserve });
|
|
1306
|
+
for (const [toast, timer] of [...toastTimers || []]) if (owns(toast)) { if (timer.id) clearTimeout(timer.id); toastTimers.delete(toast); }
|
|
1307
|
+
if ([...(scope.matches?.(".bevel-saved") ? [scope] : []), ...scope.querySelectorAll(".bevel-saved")].some(owns)) { if (savedTimer) clearTimeout(savedTimer); savedTimer = null; }
|
|
1308
|
+
for (const root of [...(scope.matches?.(".bevel-dropzone") ? [scope] : []), ...(scope.querySelectorAll?.(".bevel-dropzone") || [])]) {
|
|
1309
|
+
if (!owns(root)) continue;
|
|
1310
|
+
for (const entry of uploadFiles.get(root)?.values() || []) if (entry.preview) URL.revokeObjectURL(entry.preview);
|
|
1311
|
+
uploadFiles.delete(root);
|
|
1312
|
+
}
|
|
1313
|
+
return scope;
|
|
1314
|
+
};
|
|
1315
|
+
const clearInlineDrafts = (scope) => {
|
|
1316
|
+
const roots = [...(scope.matches?.(".bevel-inline-edit") ? [scope] : []), ...scope.querySelectorAll(".bevel-inline-edit")];
|
|
1317
|
+
for (const root of roots) {
|
|
1318
|
+
root.removeAttribute("data-bevel-editing");
|
|
1319
|
+
clearInlineError(root);
|
|
1320
|
+
const value = inlinePart(root, ".bevel-inline-edit__value"), input = inlinePart(root, ".bevel-inline-edit__input");
|
|
1321
|
+
if (value && input && input.matches("input,textarea")) {
|
|
1322
|
+
input.value = value.textContent;
|
|
1323
|
+
if (input.matches("textarea")) input.textContent = input.value;
|
|
1324
|
+
else input.setAttribute("value", input.value);
|
|
1325
|
+
input.removeAttribute("aria-invalid");
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
const clearPickerDrafts = (scope) => {
|
|
1330
|
+
scope.querySelectorAll("[data-bevel-filtered]").forEach((el) => el.removeAttribute("data-bevel-filtered"));
|
|
1331
|
+
scope.querySelectorAll('.bevel-combobox [role="combobox"][aria-activedescendant]').forEach((el) => el.removeAttribute("aria-activedescendant"));
|
|
1332
|
+
scope.querySelectorAll(".bevel-combobox__empty,.bevel-multiselect__empty").forEach((el) => el.hidden = true);
|
|
1333
|
+
};
|
|
1334
|
+
const expirePendingUploads = (scope) => {
|
|
1335
|
+
for (const row of scope.querySelectorAll('.bevel-dropzone [data-bevel-file-key][data-bevel-status="selected"],.bevel-dropzone [data-bevel-file-key][data-bevel-status="uploading"]')) {
|
|
1336
|
+
row.dataset.bevelStatus = "error";
|
|
1337
|
+
row.dataset.bevelError = "Choose this file again";
|
|
1338
|
+
row.removeAttribute("data-bevel-progress");
|
|
1339
|
+
const status = row.querySelector(".bevel-upload__status");
|
|
1340
|
+
if (status) status.textContent = "Choose this file again";
|
|
1341
|
+
const progress = row.querySelector("progress");
|
|
1342
|
+
if (progress) progress.removeAttribute("value");
|
|
1343
|
+
const retry = row.querySelector("[data-bevel-upload-retry]");
|
|
1344
|
+
if (retry) retry.hidden = true;
|
|
1345
|
+
}
|
|
1346
|
+
for (const image of scope.querySelectorAll('.bevel-upload__preview img[src^="blob:"]')) { image.removeAttribute("src"); image.hidden = true; }
|
|
1347
|
+
};
|
|
1348
|
+
const clearGridDrafts = scope => {
|
|
1349
|
+
const roots = selector => [...(scope.matches?.(selector) ? [scope] : []), ...scope.querySelectorAll(selector)];
|
|
1350
|
+
for (const root of roots(".bevel-filter")) {
|
|
1351
|
+
root.querySelector(".bevel-filter__draft")?.replaceChildren();
|
|
1352
|
+
const draft = root.querySelector(".bevel-filter__draft"), committed = root.querySelector(".bevel-filter__rules");
|
|
1353
|
+
if (draft) draft.hidden = true;
|
|
1354
|
+
if (committed) committed.hidden = false;
|
|
1355
|
+
root.removeAttribute("data-bevel-editing"); root.removeAttribute("data-bevel-invalid");
|
|
1356
|
+
}
|
|
1357
|
+
for (const cell of roots(".bevel-grid__cell[data-bevel-edit]")) {
|
|
1358
|
+
cell.querySelector(".bevel-grid__cell-editor")?.remove();
|
|
1359
|
+
cell.removeAttribute("data-bevel-editing"); cell.removeAttribute("data-bevel-invalid");
|
|
1360
|
+
const error = cell.querySelector(".bevel-grid__cell-error"); if (error) error.hidden = true;
|
|
1361
|
+
}
|
|
1362
|
+
for (const header of roots(".bevel-grid th[data-bevel-width]")) {
|
|
1363
|
+
header.style.width = `${Number(header.dataset.bevelWidth)}px`;
|
|
1364
|
+
const handle = header.querySelector(".bevel-grid__resize"); if (handle) handle.setAttribute("aria-valuenow", header.dataset.bevelWidth);
|
|
1365
|
+
}
|
|
1366
|
+
for (const target of roots("[data-bevel-drop-target]")) target.removeAttribute("data-bevel-drop-target");
|
|
1367
|
+
};
|
|
1368
|
+
const clearKanbanDrafts = scope => {
|
|
1369
|
+
for (const dialog of [...(scope.matches?.('[data-bevel-kanban-editor]') ? [scope] : []), ...scope.querySelectorAll('[data-bevel-kanban-editor]')]) {
|
|
1370
|
+
for (const name of ['open','data-bevel-open','data-bevel-open-order']) dialog.removeAttribute(name);
|
|
1371
|
+
const form=dialog.querySelector('form');if(form)for(const input of form.elements){if(input.matches('input,textarea')){input.value='';input.removeAttribute('value');if(input.matches('textarea'))input.textContent='';}}
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
const prepareForSave = (cloneRoot) => {
|
|
1375
|
+
cloneRoot.querySelectorAll('[data-bevel-transient], [data-bevel-generated]').forEach((el) => el.remove());
|
|
1376
|
+
cloneRoot.querySelectorAll("[data-bevel-pending], [data-bevel-dragging], [data-bevel-editing]").forEach((el) => { el.removeAttribute("data-bevel-pending"); el.removeAttribute("data-bevel-dragging"); el.removeAttribute("data-bevel-editing"); });
|
|
1377
|
+
cloneRoot.querySelectorAll("[data-bevel-geometry]").forEach((el) => { el.style.removeProperty("left"); el.style.removeProperty("top"); el.style.removeProperty("--bevel-available-height"); el.removeAttribute("data-bevel-geometry"); if (!el.getAttribute("style")) el.removeAttribute("style"); });
|
|
1378
|
+
cloneRoot.querySelectorAll("dialog.bevel-modal[open],dialog.bevel-drawer[open]").forEach((el) => el.removeAttribute("open"));
|
|
1379
|
+
[...(cloneRoot.matches?.(".bevel-app-shell") ? [cloneRoot] : []), ...cloneRoot.querySelectorAll(".bevel-app-shell")].forEach((shell) => { shell.removeAttribute("data-bevel-nav-open"); shell.querySelectorAll("[data-bevel-shell-nav]").forEach((button) => button.setAttribute("aria-expanded", "false")); });
|
|
1380
|
+
clearGridDrafts(cloneRoot);
|
|
1381
|
+
clearKanbanDrafts(cloneRoot);
|
|
1382
|
+
clearPickerDrafts(cloneRoot);
|
|
1383
|
+
clearInlineDrafts(cloneRoot);
|
|
1384
|
+
expirePendingUploads(cloneRoot);
|
|
1385
|
+
window.Bevel.richtext?.prepareForSave?.(cloneRoot);
|
|
1386
|
+
for (const root of cloneRoot.querySelectorAll(".bevel-split")) root.style.setProperty("--bevel-split-size", `${splitValue(root)}%`);
|
|
1387
|
+
return cloneRoot;
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1390
|
+
on("click", "[data-bevel-open]", (e, trigger) => { if (trigger.matches("dialog,[popover]")) return; const root = targetById(trigger.dataset.bevelOpen); if (root) { e.preventDefault(); if (root.matches(".bevel-menu:popover-open")) { if (!disabled(trigger)) closeOverlay(root, { reason: "toggle" }); return; } let comboInput = null; if (root.matches(".bevel-combobox__listbox") && !trigger.matches('[role="combobox"]')) { const combo = root.closest(".bevel-combobox"); comboInput = combo?.querySelector('[role="combobox"]'); if (comboInput) { comboInput.value = ""; comboFilter(combo, ""); } } openOverlay(root, { trigger }); if (comboInput) requestAnimationFrame(() => comboInput.focus()); } });
|
|
1391
|
+
on("click", "[data-bevel-close]", (e, button) => { const root = button.closest("dialog,[popover]"); if (root) { e.preventDefault(); closeOverlay(root, { reason: button.dataset.bevelClose || "button" }); } });
|
|
1392
|
+
document.addEventListener("cancel", (e) => { if (e.target.matches("dialog.bevel-modal,dialog.bevel-drawer")) { e.preventDefault(); closeOverlay(e.target, { reason: "escape" }); } }, true);
|
|
1393
|
+
document.addEventListener("close", (e) => { const root = e.target; if (!root.matches?.("dialog.bevel-modal,dialog.bevel-drawer") || root.open) return; const session = sessions.get(root); session?.cleanup?.(); sessions.delete(root); root.removeAttribute("data-bevel-open"); const trigger = session?.trigger || panelTrigger(root); trigger?.setAttribute("aria-expanded", "false"); setExpanded(root, false); }, true);
|
|
1394
|
+
document.addEventListener("toggle", (e) => { const root = e.target; if (!root.matches?.("[popover].bevel-menu,[popover].bevel-popconfirm,[popover].bevel-combobox__listbox,[popover].bevel-multiselect__panel") || root.matches(":popover-open")) return; const session = sessions.get(root); session?.cleanup?.(); sessions.delete(root); root.removeAttribute("data-bevel-open"); root.removeAttribute("data-bevel-geometry"); const trigger = session?.trigger || panelTrigger(root); trigger?.setAttribute("aria-expanded", "false"); setExpanded(root, false); }, true);
|
|
1395
|
+
document.addEventListener("keydown", (e) => { if (e.defaultPrevented || e.key !== "Escape") return; const panels = opened(), modal = panels.filter(isModal).at(-1), top = panels.filter(panel => !modal || panel === modal || modal.contains(panel)).at(-1); if (top) { e.preventDefault(); closeOverlay(top, { reason: "escape" }); } });
|
|
1396
|
+
document.addEventListener("pointerdown", (e) => { for (const root of opened().filter(isAnchored).reverse()) if (!root.contains(e.target) && !sessions.get(root)?.trigger?.contains(e.target)) closeOverlay(root, { reason: "outside", focus: false }); });
|
|
1397
|
+
|
|
1398
|
+
on("keydown", ".bevel-menu [role^='menuitem']", (e, item) => {
|
|
1399
|
+
const menu = item.closest(".bevel-menu"), items = [...menu.querySelectorAll('[role^="menuitem"]')].filter((el) => !disabled(el)), index = items.indexOf(item);
|
|
1400
|
+
const next = e.key === "ArrowDown" ? items[(index + 1) % items.length] : e.key === "ArrowUp" ? items[(index - 1 + items.length) % items.length] : e.key === "Home" ? items[0] : e.key === "End" ? items.at(-1) : null;
|
|
1401
|
+
if (next) { e.preventDefault(); items.forEach((el) => el.tabIndex = el === next ? 0 : -1); next.focus(); }
|
|
1402
|
+
else if (e.key === "Enter" || e.key === " ") { e.preventDefault(); item.click(); }
|
|
1403
|
+
else if (e.key === "Tab") closeOverlay(menu, { reason: "tab", focus: false });
|
|
1404
|
+
else if (e.key.length === 1 && !e.altKey && !e.ctrlKey && !e.metaKey) {
|
|
1405
|
+
const session = sessions.get(menu) || {}, now = performance.now(), buffer = now - (session.typeTime || 0) > 500 ? e.key : `${session.typeBuffer || ""}${e.key}`;
|
|
1406
|
+
session.typeBuffer = buffer.toLowerCase(); session.typeTime = now; sessions.set(menu, session);
|
|
1407
|
+
const match = items.find((candidate) => candidate.textContent.trim().toLowerCase().startsWith(session.typeBuffer));
|
|
1408
|
+
if (match) { e.preventDefault(); items.forEach((el) => el.tabIndex = el === match ? 0 : -1); match.focus(); }
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
on("click", ".bevel-menu [role^='menuitem']", (e, item) => { if (disabled(item)) { e.preventDefault(); return; } const root = item.closest(".bevel-menu"), role = item.getAttribute("role"), previous = item.getAttribute("aria-checked") === "true"; if (role === "menuitemcheckbox" || role === "menuitemradio") { const next = role === "menuitemradio" ? true : !previous; if (!transition(root, "check", item.dataset.bevelKey, previous, next, () => { if (role === "menuitemradio") root.querySelectorAll('[role="menuitemradio"]').forEach((peer) => peer.setAttribute("aria-checked", String(peer === item))); else item.setAttribute("aria-checked", String(next)); })) return; } action(root, item.dataset.bevelAction || "select", item.dataset.bevelKey); if (item.hasAttribute("data-bevel-update-default")) { const split = root.closest(".bevel-split-button"), main = split?.querySelector("[data-bevel-split-default]"), label = main?.querySelector(".bevel-button__label-text,.bevel-button__label"); if (main && label) { main.dataset.bevelSplitDefault = item.dataset.bevelAction || "select"; main.dataset.bevelKey = item.dataset.bevelKey || ""; label.textContent = item.dataset.bevelDefaultLabel || item.textContent.trim(); } } if (role !== "menuitemcheckbox") closeOverlay(root, { reason: "select" }); });
|
|
1412
|
+
on("click", ".bevel-popconfirm [data-bevel-confirm]", (e, button) => { const root = button.closest(".bevel-popconfirm"); if (disabled(button)) return; if (closeOverlay(root, { reason: "confirm" })) action(root, button.dataset.bevelConfirm, button.dataset.bevelKey); });
|
|
1413
|
+
|
|
1414
|
+
const comboOptions = (root) => [...root.querySelectorAll('[role="option"]')].filter((el) => !el.hidden && !el.hasAttribute("data-bevel-filtered") && el.getAttribute("aria-disabled") !== "true");
|
|
1415
|
+
const comboFilter = (root, query) => { let matches = 0; for (const option of root.querySelectorAll('[role="option"]')) { const filtered = !(option.dataset.bevelLabel || option.textContent).toLowerCase().includes(query.toLowerCase()); option.toggleAttribute("data-bevel-filtered", filtered); if (!option.hidden && !filtered && option.getAttribute("aria-disabled") !== "true") matches++; } const empty = root.querySelector(".bevel-combobox__empty"); if (empty) empty.hidden = matches > 0; };
|
|
1416
|
+
const comboCommit = (root, option) => { if (disabled(root) || disabled(root.querySelector('[role="combobox"]')) || (option && disabled(option))) return false; const input = root.querySelector('[role="combobox"]'), hidden = root.querySelector('input[type="hidden"]'), previous = hidden?.value || "", next = option?.dataset.bevelKey || ""; transition(root, "select", next, previous, next, () => { if (hidden) { hidden.value = next; if (hidden.getAttribute("value") !== next) hidden.setAttribute("value", next); } if (root.dataset.bevelValue !== next) root.dataset.bevelValue = next; if (input) { const label = option?.dataset.bevelLabel || option?.textContent.trim() || ""; input.value = label; if (input.getAttribute("value") !== label) input.setAttribute("value", label); input.removeAttribute("aria-activedescendant"); } refreshCombo(root); const panel = root.querySelector('[role="listbox"]'); if (panel?.matches(":popover-open")) closeOverlay(panel, { reason: "select", focus: false }); }); };
|
|
1417
|
+
on("input", ".bevel-combobox [role='combobox']", (e, input) => { if (e.isComposing || disabled(input) || disabled(input.closest(".bevel-combobox"))) return; const root = input.closest(".bevel-combobox"), list = root.querySelector('[role="listbox"]'); comboFilter(root, input.value); refreshCombo(root); if (!list.matches(":popover-open")) openOverlay(list, { trigger: input }); });
|
|
1418
|
+
on("keydown", ".bevel-combobox [role='combobox']", (e, input) => { if (e.isComposing || disabled(input) || disabled(input.closest(".bevel-combobox"))) return; const root = input.closest(".bevel-combobox"), list = root.querySelector('[role="listbox"]'), options = comboOptions(root), active = targetById(input.getAttribute("aria-activedescendant")), index = options.indexOf(active); if (e.key === "ArrowDown" || e.key === "ArrowUp") { e.preventDefault(); if (!list.matches(":popover-open")) openOverlay(list, { trigger: input }); const next = e.key === "ArrowDown" ? options[(index + 1) % options.length] : options[(index - 1 + options.length) % options.length]; if (next) { if (!next.id) next.id = `${root.id || "bevel-combo"}-option-${options.indexOf(next)}`; input.setAttribute("aria-activedescendant", next.id); } } else if (e.key === "Enter" && active) { e.preventDefault(); comboCommit(root, active); } else if (e.key === "Escape") { refreshCombo(root, true); input.removeAttribute("aria-activedescendant"); if (list.matches(":popover-open")) closeOverlay(list, { reason: "escape" }); } else if (e.key === "Tab") { refreshCombo(root, true); input.removeAttribute("aria-activedescendant"); if (list.matches(":popover-open")) closeOverlay(list, { reason: "tab", focus: false }); } });
|
|
1419
|
+
on("pointerdown", ".bevel-combobox [role='option']", (e, option) => { e.preventDefault(); comboCommit(option.closest(".bevel-combobox"), option); });
|
|
1420
|
+
on("click", ".bevel-combobox [data-bevel-combo-clear]", (e, button) => { const root = button.closest(".bevel-combobox"), input = root.querySelector('[role="combobox"]'); if (input?.required) return; comboCommit(root, null); input?.focus(); });
|
|
1421
|
+
on("change", ".bevel-multiselect__panel input[type='checkbox']", (e, input) => { const root = input.closest(".bevel-multiselect"), nextChecked = input.checked, previousChecked = !nextChecked, before = [...root.querySelectorAll('.bevel-multiselect__panel input[type="checkbox"]')].filter((item) => item !== input ? item.checked : previousChecked).map((item) => item.value), after = [...root.querySelectorAll('.bevel-multiselect__panel input[type="checkbox"]')].filter((item) => item.checked).map((item) => item.value); if (!transition(root, "select", input.value, before, after, () => refreshMulti(root))) { input.checked = previousChecked; input.toggleAttribute("checked", previousChecked); refreshMulti(root); } });
|
|
1422
|
+
on("input", ".bevel-multiselect__search", (e, input) => { const root = input.closest(".bevel-multiselect"), query = input.value.toLowerCase(); let matches = 0; for (const label of root.querySelectorAll(".bevel-multiselect__option")) { const checkbox = label.querySelector('input[type="checkbox"]'), filtered = !label.textContent.toLowerCase().includes(query); label.toggleAttribute("data-bevel-filtered", filtered); if (!label.hidden && !filtered && !disabled(checkbox)) matches++; } const empty = root.querySelector(".bevel-multiselect__empty"); if (empty) empty.hidden = matches > 0; });
|
|
1423
|
+
on("keydown", ".bevel-multiselect__search", (e, input) => { if (e.key === "Backspace" && !input.value) { const remove = [...input.closest(".bevel-multiselect").querySelectorAll("[data-bevel-multiselect-remove]:not(:disabled)")].at(-1); if (remove) { e.preventDefault(); remove.focus(); } } });
|
|
1424
|
+
on("click", ".bevel-multiselect [data-bevel-multiselect-remove]", (e, button) => { const root = button.closest(".bevel-multiselect"), buttons = [...root.querySelectorAll("[data-bevel-multiselect-remove]")], index = buttons.indexOf(button), checkbox = [...root.querySelectorAll('.bevel-multiselect__panel input[type="checkbox"]')].find((input) => input.value === button.dataset.bevelMultiselectRemove); if (!checkbox || disabled(checkbox)) return; checkbox.click(); requestAnimationFrame(() => { const remaining = [...root.querySelectorAll("[data-bevel-multiselect-remove]")]; (remaining[index] || remaining[index - 1] || root.querySelector(".bevel-multiselect__summary"))?.focus(); }); });
|
|
1425
|
+
|
|
1426
|
+
const acceptFiles = (root, files) => {
|
|
1427
|
+
if (!root) return { accepted: [], rejected: [] };
|
|
1428
|
+
const limit = Number(root.dataset.bevelMaxBytes) || Infinity, accept = (root.querySelector('input[type="file"]')?.accept || "").split(",").map((s) => s.trim()).filter(Boolean), accepted = [], rejected = [];
|
|
1429
|
+
for (const file of files || []) { const validType = !accept.length || accept.some((rule) => rule.endsWith("/*") ? file.type.startsWith(rule.slice(0, -1)) : rule.startsWith(".") ? file.name.toLowerCase().endsWith(rule.toLowerCase()) : file.type.toLowerCase() === rule.toLowerCase()); if (file.size > limit) rejected.push({ name: file.name, reason: `File exceeds the ${limit.toLocaleString()} byte limit` }); else if (!validType) rejected.push({ name: file.name, reason: "File type is not accepted" }); else accepted.push({ key: crypto.randomUUID?.() || `file-${Date.now()}-${Math.random().toString(36).slice(2)}`, file }); }
|
|
1430
|
+
const store = uploadFiles.get(root) || new Map();
|
|
1431
|
+
const list = root.querySelector("[data-bevel-upload-list]") || (() => { const node = document.createElement("div"); node.className = "bevel-upload"; node.dataset.bevelUploadList = ""; root.append(node); return node; })();
|
|
1432
|
+
for (const item of accepted) {
|
|
1433
|
+
const preview = item.file.type.startsWith("image/") ? URL.createObjectURL(item.file) : "";
|
|
1434
|
+
store.set(item.key, { file: item.file, preview });
|
|
1435
|
+
let row = list.querySelector(`[data-bevel-file-key="${CSS.escape(item.key)}"]`);
|
|
1436
|
+
if (!row) { row = document.createElement("article"); row.className = "bevel-upload__row"; row.dataset.bevelFileKey = item.key; row.innerHTML = '<div class="bevel-upload__preview" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="square" stroke-linejoin="miter"><path d="M5 3h9l5 5v13H5ZM14 3v6h5"/></svg><img alt="" hidden></div><div class="bevel-upload__copy"><strong class="bevel-upload__name"></strong><span class="bevel-upload__meta"></span><span class="bevel-upload__status" role="status"></span><progress class="bevel-upload__progress" max="100" value="0" aria-label="Upload progress"></progress></div><span class="bevel-upload__state" aria-hidden="true"></span><div class="bevel-upload__actions"><button class="bevel-button bevel-button--small" type="button" data-bevel-upload-retry hidden>Retry</button><button class="bevel-button bevel-button--small" type="button" data-bevel-upload-remove>Remove</button></div>'; list.append(row); }
|
|
1437
|
+
row.dataset.bevelStatus = "selected";
|
|
1438
|
+
row.dataset.bevelName = item.file.name;
|
|
1439
|
+
row.querySelector(".bevel-upload__name").textContent = item.file.name;
|
|
1440
|
+
row.querySelector(".bevel-upload__meta").textContent = uploadSize(item.file.size);
|
|
1441
|
+
row.querySelector(".bevel-upload__status").textContent = "Selected";
|
|
1442
|
+
const image = row.querySelector(".bevel-upload__preview img");
|
|
1443
|
+
if (preview) { image.src = preview; image.hidden = false; }
|
|
1444
|
+
}
|
|
1445
|
+
for (const item of rejected) { const row = document.createElement("p"); row.className = "bevel-upload__rejection"; row.dataset.bevelRejected = ""; row.setAttribute("role", "alert"); row.textContent = `${item.name}: ${item.reason}`; list.append(row); }
|
|
1446
|
+
uploadFiles.set(root, store); emit(root, "bevel:files", { files: accepted, rejected }); return { accepted, rejected };
|
|
1447
|
+
};
|
|
1448
|
+
on("change", ".bevel-dropzone input[type='file']", (e, input) => { acceptFiles(input.closest(".bevel-dropzone"), input.files); input.value = ""; });
|
|
1449
|
+
on("dragover", ".bevel-dropzone", (e, root) => { e.preventDefault(); root.dataset.bevelDragging = ""; });
|
|
1450
|
+
on("dragleave", ".bevel-dropzone", (e, root) => root.removeAttribute("data-bevel-dragging"));
|
|
1451
|
+
on("drop", ".bevel-dropzone", (e, root) => { e.preventDefault(); root.removeAttribute("data-bevel-dragging"); acceptFiles(root, e.dataTransfer.files); });
|
|
1452
|
+
on("click", ".bevel-dropzone [data-bevel-upload-remove]", (e, button) => upload.remove(button.closest(".bevel-dropzone"), button.closest("[data-bevel-file-key]").dataset.bevelFileKey));
|
|
1453
|
+
on("click", ".bevel-dropzone [data-bevel-upload-retry]", (e, button) => { const root = button.closest(".bevel-dropzone"), key = button.closest("[data-bevel-file-key]").dataset.bevelFileKey; if (uploadFiles.get(root)?.has(key)) action(root, "retry", key); });
|
|
1454
|
+
|
|
1455
|
+
const inlinePart = (root, selector) => [...root.querySelectorAll(selector)].find(node => node.closest(".bevel-inline-edit") === root);
|
|
1456
|
+
const inlineReadOnly = root => disabled(root) || root.getAttribute("aria-readonly") === "true" || root.hasAttribute("data-bevel-readonly");
|
|
1457
|
+
const clearInlineError = root => {
|
|
1458
|
+
const message = inlinePart(root, "[data-bevel-edit-error]");
|
|
1459
|
+
setFieldError(message, "");
|
|
1460
|
+
};
|
|
1461
|
+
const startEdit = (root) => {
|
|
1462
|
+
if (!root?.matches(".bevel-inline-edit") || inlineReadOnly(root)) return false;
|
|
1463
|
+
if (sessions.get(root)?.input) return true;
|
|
1464
|
+
const value = inlinePart(root, ".bevel-inline-edit__value"), container = inlinePart(root, ".bevel-inline-edit__editor"), trigger = inlinePart(root, "[data-bevel-edit-start]");
|
|
1465
|
+
if (!value || !container || (trigger && disabled(trigger))) return false;
|
|
1466
|
+
const template = inlinePart(root, "template[data-bevel-edit-template]");
|
|
1467
|
+
const draft = template?.content.cloneNode(true);
|
|
1468
|
+
let input = draft?.querySelector(".bevel-inline-edit__input") || inlinePart(root, ".bevel-inline-edit__input");
|
|
1469
|
+
if (!input || disabled(input)) return false;
|
|
1470
|
+
const rich = root.dataset.bevelEditKind === "rich";
|
|
1471
|
+
if (rich && !window.Bevel.richtext) { error(root, "missing-richclay", "Load the Richclay integration before editing rich content."); return false; }
|
|
1472
|
+
if (rich && typeof window.RichClay !== "function") { error(root, "missing-richclay", "Load Richclay before editing rich content."); return false; }
|
|
1473
|
+
if (draft) {
|
|
1474
|
+
for (const child of draft.children) child.setAttribute("data-bevel-transient", "inline-draft");
|
|
1475
|
+
(inlinePart(root, "[data-bevel-edit-slot]") || container).prepend(draft);
|
|
1476
|
+
}
|
|
1477
|
+
clearInlineError(root);
|
|
1478
|
+
let editor, toolbarHost;
|
|
1479
|
+
const cleanup = (focus = false) => {
|
|
1480
|
+
if (rich) window.Bevel.richtext?.unmount(input);
|
|
1481
|
+
toolbarHost?.remove();
|
|
1482
|
+
if (template) container.querySelectorAll('[data-bevel-transient="inline-draft"]').forEach(node => node.remove());
|
|
1483
|
+
else { input.value = value.textContent; input.removeAttribute("aria-invalid"); }
|
|
1484
|
+
root.removeAttribute("data-bevel-editing");
|
|
1485
|
+
clearInlineError(root);
|
|
1486
|
+
sessions.delete(root);
|
|
1487
|
+
if (focus && trigger?.isConnected && !disabled(trigger)) trigger.focus();
|
|
1488
|
+
};
|
|
1489
|
+
root.setAttribute("data-bevel-editing", "");
|
|
1490
|
+
try {
|
|
1491
|
+
if (rich) {
|
|
1492
|
+
input.setAttribute("data-bevel-richtext", "");
|
|
1493
|
+
if (input.hasAttribute("required")) input.setAttribute("aria-required", "true");
|
|
1494
|
+
input.innerHTML = value.innerHTML;
|
|
1495
|
+
const singleLine = root.hasAttribute("data-bevel-single-line");
|
|
1496
|
+
const insideModal = !!root.closest("dialog");
|
|
1497
|
+
if (insideModal && !singleLine) {
|
|
1498
|
+
toolbarHost = document.createElement("div");
|
|
1499
|
+
toolbarHost.setAttribute("data-bevel-transient", "inline-draft");
|
|
1500
|
+
container.prepend(toolbarHost);
|
|
1501
|
+
}
|
|
1502
|
+
[editor] = window.Bevel.richtext.mount(input, { inline: !insideModal || singleLine, singleLine, toolbar: singleLine ? false : "inline", toolbarContainer: toolbarHost, placeholder: input.dataset.bevelPlaceholder || "", hyperclay: false });
|
|
1503
|
+
editor.focus();
|
|
1504
|
+
} else {
|
|
1505
|
+
input.value = value.textContent;
|
|
1506
|
+
input.focus();
|
|
1507
|
+
if (input.type !== "number") input.select();
|
|
1508
|
+
}
|
|
1509
|
+
const original = rich ? value.innerHTML : value.textContent;
|
|
1510
|
+
const valid = () => inlinePart(root, ".bevel-inline-edit__value") === value && inlinePart(root, ".bevel-inline-edit__editor") === container && container.contains(input) && (rich ? value.innerHTML : value.textContent) === original;
|
|
1511
|
+
sessions.set(root, { input, editor, rich, trigger, cleanup, valid, cancel: () => cleanup() });
|
|
1512
|
+
return true;
|
|
1513
|
+
} catch (cause) {
|
|
1514
|
+
cleanup();
|
|
1515
|
+
error(root, "editor-start", cause.message);
|
|
1516
|
+
return false;
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
const endEdit = (root, commit) => {
|
|
1520
|
+
if (!root) return false;
|
|
1521
|
+
const session = sessions.get(root);
|
|
1522
|
+
if (!session?.input) return false;
|
|
1523
|
+
if (!session.valid()) { session.cleanup(); return false; }
|
|
1524
|
+
if (!commit) { session.cleanup(true); return true; }
|
|
1525
|
+
if (inlineReadOnly(root) || disabled(session.input)) return false;
|
|
1526
|
+
const { input, rich, editor } = session;
|
|
1527
|
+
const valid = rich ? !(input.hasAttribute("required") && !input.textContent.trim()) : input.checkValidity();
|
|
1528
|
+
if (!valid) {
|
|
1529
|
+
input.setAttribute("aria-invalid", "true");
|
|
1530
|
+
const message = inlinePart(root, "[data-bevel-edit-error]");
|
|
1531
|
+
if (message) {
|
|
1532
|
+
setFieldError(message, root.dataset.bevelValidationMessage || (rich ? "Enter a value before saving." : input.validationMessage));
|
|
1533
|
+
}
|
|
1534
|
+
input.focus();
|
|
1535
|
+
if (!rich && !message) input.reportValidity();
|
|
1536
|
+
return false;
|
|
1537
|
+
}
|
|
1538
|
+
const value = inlinePart(root, ".bevel-inline-edit__value");
|
|
1539
|
+
const previous = rich ? value.innerHTML : value.textContent;
|
|
1540
|
+
const next = rich ? editor.getHTML() : input.value;
|
|
1541
|
+
return transition(root, "edit", root.dataset.bevelKey, previous, next, () => {
|
|
1542
|
+
if (rich) value.innerHTML = next;
|
|
1543
|
+
else value.textContent = next;
|
|
1544
|
+
session.cleanup(true);
|
|
1545
|
+
});
|
|
1546
|
+
};
|
|
1547
|
+
on("click", ".bevel-inline-edit [data-bevel-edit-start]", (event, button) => { event.preventDefault(); startEdit(button.closest(".bevel-inline-edit")); });
|
|
1548
|
+
on("click", ".bevel-inline-edit [data-bevel-edit-save]", (event, button) => { event.preventDefault(); if (!disabled(button)) endEdit(button.closest(".bevel-inline-edit"), true); });
|
|
1549
|
+
on("click", ".bevel-inline-edit [data-bevel-edit-cancel]", (event, button) => { event.preventDefault(); if (!disabled(button)) endEdit(button.closest(".bevel-inline-edit"), false); });
|
|
1550
|
+
on("keydown", ".bevel-inline-edit__input", (event, input) => {
|
|
1551
|
+
if (event.isComposing) return;
|
|
1552
|
+
const root = input.closest(".bevel-inline-edit"), session = sessions.get(root);
|
|
1553
|
+
if (!session || session.input !== input) return;
|
|
1554
|
+
if (event.key === "Escape") {
|
|
1555
|
+
if (event.defaultPrevented) return;
|
|
1556
|
+
event.preventDefault(); endEdit(root, false);
|
|
1557
|
+
} else if (event.key === "Enter" && (event.ctrlKey || event.metaKey || (session.rich ? root.hasAttribute("data-bevel-single-line") : input.tagName !== "TEXTAREA"))) {
|
|
1558
|
+
event.preventDefault(); endEdit(root, true);
|
|
1559
|
+
}
|
|
1560
|
+
}, true);
|
|
1561
|
+
|
|
1562
|
+
on("click", ".bevel-split-button [data-bevel-split-default]", (e, button) => { if (!disabled(button)) action(button.closest(".bevel-split-button"), button.dataset.bevelSplitDefault, button.dataset.bevelKey); });
|
|
1563
|
+
|
|
1564
|
+
on("click", ".bevel-toolbar [data-bevel-toggle]", (event, button) => { if (disabled(button)) return; const root = button.closest(".bevel-toolbar"), group = button.closest("[data-bevel-toggle-group]"); if (group) { const peers = [...group.querySelectorAll("[data-bevel-toggle]")], previous = peers.find((peer) => peer.getAttribute("aria-pressed") === "true")?.dataset.bevelToggle || "", next = button.dataset.bevelToggle; if (previous === next) return; transition(root, "toggle", next, previous, next, () => peers.forEach((peer) => peer.setAttribute("aria-pressed", String(peer === button)))); } else { const previous = button.getAttribute("aria-pressed") === "true", next = !previous; transition(root, "toggle", button.dataset.bevelToggle, previous, next, () => button.setAttribute("aria-pressed", String(next))); } });
|
|
1565
|
+
|
|
1566
|
+
on("click", ".bevel-pagination [data-bevel-page]", (e, button) => { const root = button.closest(".bevel-pagination"); if (disabled(button)) return; const target = root.dataset.bevelTarget && document.getElementById(root.dataset.bevelTarget), grid = target?.matches(".bevel-grid") ? target : null, previous = Number(grid?.dataset.page || root.dataset.bevelPage) || 1, size = Number(grid?.dataset.pageSize || root.dataset.bevelPageSize) || 10, total = grid ? (grid.hasAttribute("data-bevel-total") ? Number(grid.dataset.bevelTotal) : null) : Number(root.dataset.bevelTotal) || 0, pages = total == null ? null : Math.max(1, Math.ceil(Math.max(0, total) / size)), token = button.dataset.bevelPage, next = token === "prev" ? previous - 1 : token === "next" ? previous + 1 : Number(token); if (!next || next < 1 || (pages != null && next > pages)) return; if (grid) changeGridPage(grid, token); else transition(root, "page", next, previous, next, () => { root.dataset.bevelPage = String(next); refreshPager(root); }); });
|
|
1567
|
+
on("change", ".bevel-pagination select[data-bevel-page-size]", (event, control) => { const root = control.closest(".bevel-pagination"), target = root.dataset.bevelTarget && document.getElementById(root.dataset.bevelTarget), grid = target?.matches(".bevel-grid") ? target : null, previous = Number(grid?.dataset.pageSize || root.dataset.bevelPageSize) || 10, next = Number(control.value); if (!Number.isFinite(next) || next < 1 || next === previous) { refreshPager(root); return; } const before = { page: Number(grid?.dataset.page || root.dataset.bevelPage) || 1, pageSize: previous }, after = { page: 1, pageSize: next }; if (grid?.dataset.bevelDataMode === "remote") { remoteRequest(grid, "page", "size", before, after); refreshPager(root); return; } if (grid) { grid.dataset.pageSize = String(next); grid.dataset.page = "1"; layoutGrid(grid); emit(grid, "bevel:page", { page: Number(grid.dataset.page), pageSize: next }); } else transition(root, "page-size", next, before, after, () => { root.dataset.bevelPageSize = String(next); root.dataset.bevelPage = "1"; refreshPager(root); }); });
|
|
1568
|
+
on("click", ".bevel-sidebar [data-bevel-sidebar-toggle]", (event, button) => { if (disabled(button)) return; event.preventDefault(); const root = button.closest(".bevel-sidebar"), previous = root.hasAttribute("data-bevel-collapsed"), next = !previous; transition(root, "collapse", "sidebar", previous, next, () => { root.toggleAttribute("data-bevel-collapsed", next); refreshSidebar(root); }); });
|
|
1569
|
+
on("click", ".bevel-app-shell [data-bevel-shell-nav]", (event, button) => { if (disabled(button)) return; event.preventDefault(); const root = button.closest(".bevel-app-shell"), previous = root.hasAttribute("data-bevel-nav-open"), next = !previous; transition(root, "open", "sidebar", previous, next, () => { root.toggleAttribute("data-bevel-nav-open", next); refreshShell(root); }); });
|
|
1570
|
+
on("click", ".bevel-app-shell .bevel-app-shell__side [data-bevel-shell-collapse]", (event, button) => { if (disabled(button)) return; event.preventDefault(); const root = button.closest(".bevel-app-shell"), side = button.closest(".bevel-app-shell__side"), previous = side.hasAttribute("data-bevel-collapsed"), next = !previous; transition(root, "collapse", "sidebar", previous, next, () => { side.toggleAttribute("data-bevel-collapsed", next); refreshShell(root); }); });
|
|
1571
|
+
const selectTreeItem = (root, item) => { const previous = root.dataset.bevelSelected || "", next = item.dataset.bevelKey; return transition(root, "select", next, previous, next, () => { root.dataset.bevelSelected = next; refreshTree(root); }); };
|
|
1572
|
+
const setTreeExpanded = (root, item, next) => { const branch = item.closest("details"); if (!branch || branch.querySelector(":scope > summary") !== item) return false; const previous = branch.open; if (previous === next) return true; return transition(root, next ? "expand" : "collapse", item.dataset.bevelKey, previous, next, () => { branch.open = next; branch.dataset.bevelExpanded = String(next); refreshTree(root); }); };
|
|
1573
|
+
on("click", '.bevel-tree [role="treeitem"][data-bevel-key]', (event, item) => { const root = item.closest(".bevel-tree"); if (disabled(item)) { event.preventDefault(); return; } if (item.tagName === "SUMMARY") { event.preventDefault(); setTreeExpanded(root, item, !item.closest("details").open); } else selectTreeItem(root, item); });
|
|
1574
|
+
on("keydown", '.bevel-tree [role="treeitem"][data-bevel-key]', (event, item) => {
|
|
1575
|
+
if (event.isComposing || disabled(item)) return;
|
|
1576
|
+
const root = item.closest(".bevel-tree"), visible = treeVisible(root), index = visible.indexOf(item);
|
|
1577
|
+
const focus = (next) => { if (!next) return; event.preventDefault(); for (const node of treeItems(root)) node.tabIndex = node === next ? 0 : -1; next.focus(); };
|
|
1578
|
+
if (event.key === "ArrowDown") return focus(visible[index + 1] || visible[0]);
|
|
1579
|
+
if (event.key === "ArrowUp") return focus(visible[index - 1] || visible.at(-1));
|
|
1580
|
+
if (event.key === "Home") return focus(visible[0]);
|
|
1581
|
+
if (event.key === "End") return focus(visible.at(-1));
|
|
1582
|
+
if (event.key === "ArrowRight" && item.tagName === "SUMMARY") { const branch = item.closest("details"); if (!branch.open) { event.preventDefault(); setTreeExpanded(root, item, true); } else focus(treeVisible(root).find((candidate) => candidate !== item && branch.contains(candidate))); return; }
|
|
1583
|
+
if (event.key === "ArrowLeft") { if (item.tagName === "SUMMARY" && item.closest("details").open) { event.preventDefault(); setTreeExpanded(root, item, false); return; } const owner = item.tagName === "SUMMARY" ? item.closest("details")?.parentElement : item.parentElement, parent = owner?.closest("details")?.querySelector(":scope > summary"); if (parent) focus(parent); return; }
|
|
1584
|
+
if (event.key === "Enter") { if (item.tagName !== "A") event.preventDefault(); selectTreeItem(root, item); return; }
|
|
1585
|
+
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) { const now = performance.now(), prior = root._bevelTypeahead || { value: "", at: 0 }, value = now - prior.at > 700 ? event.key : prior.value + event.key; root._bevelTypeahead = { value: value.toLocaleLowerCase(), at: now }; const ordered = [...visible.slice(index + 1), ...visible.slice(0, index + 1)], match = ordered.find((candidate) => (candidate.dataset.bevelLabel || candidate.textContent).trim().toLocaleLowerCase().startsWith(root._bevelTypeahead.value)); if (match) focus(match); }
|
|
1586
|
+
});
|
|
1587
|
+
document.addEventListener("toggle", (event) => { const branch = event.target; if (!(branch instanceof HTMLDetailsElement)) return; const root = branch.closest(".bevel-tree"), item = branch.querySelector(":scope > summary"); if (!root || !item) return; if (!branch.open && branch.contains(document.activeElement) && document.activeElement !== item) item.focus(); refreshTree(root); }, true);
|
|
1588
|
+
/* ---------- file tree ---------- */
|
|
1589
|
+
const fileTreeRows = (root) => [...root.querySelectorAll(".bevel-file-tree__row")].filter((row) => row.closest(".bevel-file-tree") === root);
|
|
1590
|
+
const fileTreeVisible = (root) => fileTreeRows(root).filter((row) => !row.hidden && !row.closest("[hidden]"));
|
|
1591
|
+
const fileTreeGroup = (row) => targetById(row.getAttribute("aria-controls"));
|
|
1592
|
+
const syncFileTree = (root) => {
|
|
1593
|
+
if (root.getAttribute("role") !== "tree") root.setAttribute("role", "tree");
|
|
1594
|
+
const selected = root.dataset.bevelSelected || "";
|
|
1595
|
+
for (const row of fileTreeRows(root)) {
|
|
1596
|
+
if (row.getAttribute("role") !== "treeitem") row.setAttribute("role", "treeitem");
|
|
1597
|
+
const group = fileTreeGroup(row);
|
|
1598
|
+
if (group) group.hidden = row.hidden || row.getAttribute("aria-expanded") !== "true";
|
|
1599
|
+
if (!group) { const value = String(row.dataset.bevelKey === selected); if (row.getAttribute("aria-selected") !== value) row.setAttribute("aria-selected", value); }
|
|
1600
|
+
}
|
|
1601
|
+
const visible = fileTreeVisible(root);
|
|
1602
|
+
const current = visible.find((row) => row === document.activeElement) || visible.find((row) => row.tabIndex === 0) || visible.find((row) => row.dataset.bevelKey === selected) || visible[0];
|
|
1603
|
+
for (const row of fileTreeRows(root)) { const value = row === current ? "0" : "-1"; if (row.getAttribute("tabindex") !== value) row.setAttribute("tabindex", value); }
|
|
1604
|
+
const empty = root.querySelector(".bevel-file-tree__empty");
|
|
1605
|
+
if (empty) empty.hidden = visible.length > 0;
|
|
1606
|
+
};
|
|
1607
|
+
const setFileTreeExpanded = (root, row, next) => {
|
|
1608
|
+
const previous = row.getAttribute("aria-expanded") === "true";
|
|
1609
|
+
if (previous === next) return true;
|
|
1610
|
+
return transition(root, next ? "expand" : "collapse", row.dataset.bevelKey, previous, next, () => {
|
|
1611
|
+
row.setAttribute("aria-expanded", String(next));
|
|
1612
|
+
row.dataset.bevelExpanded = String(next);
|
|
1613
|
+
syncFileTree(root);
|
|
1614
|
+
});
|
|
1615
|
+
};
|
|
1616
|
+
const selectFileTreeRow = (root, row) => {
|
|
1617
|
+
const previous = root.dataset.bevelSelected || "", next = row.dataset.bevelKey;
|
|
1618
|
+
return transition(root, "select", next, previous, next, () => { root.dataset.bevelSelected = next; syncFileTree(root); });
|
|
1619
|
+
};
|
|
1620
|
+
|
|
1621
|
+
const filterFileTree = (root, query) => {
|
|
1622
|
+
const value = query.trim().toLocaleLowerCase(), rows = fileTreeRows(root);
|
|
1623
|
+
for (const row of rows) {
|
|
1624
|
+
const label = (row.dataset.bevelLabel || row.textContent).trim().toLocaleLowerCase();
|
|
1625
|
+
row.hidden = Boolean(value) && !label.includes(value);
|
|
1626
|
+
if (!value && row.hasAttribute("aria-expanded")) row.setAttribute("aria-expanded", row.dataset.bevelExpanded || "false");
|
|
1627
|
+
}
|
|
1628
|
+
if (value) for (const row of rows) {
|
|
1629
|
+
if (row.hidden) continue;
|
|
1630
|
+
for (let group = row.parentElement?.closest(".bevel-file-tree__group"); group && root.contains(group); group = group.parentElement?.closest(".bevel-file-tree__group")) {
|
|
1631
|
+
const owner = rows.find((candidate) => candidate.getAttribute("aria-controls") === group.id);
|
|
1632
|
+
if (!owner) break;
|
|
1633
|
+
owner.hidden = false;
|
|
1634
|
+
owner.setAttribute("aria-expanded", "true");
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
syncFileTree(root);
|
|
1638
|
+
emit(root, "bevel:filter", { component: "file-tree", value: query, matches: fileTreeVisible(root).filter((row) => !row.hasAttribute("aria-controls")).length });
|
|
1639
|
+
};
|
|
1640
|
+
on("click", ".bevel-file-tree__row", (event, row) => {
|
|
1641
|
+
const root = row.closest(".bevel-file-tree");
|
|
1642
|
+
if (!root || disabled(row)) { event.preventDefault(); return; }
|
|
1643
|
+
if (row.hasAttribute("aria-expanded")) { event.preventDefault(); setFileTreeExpanded(root, row, row.getAttribute("aria-expanded") !== "true"); return; }
|
|
1644
|
+
selectFileTreeRow(root, row);
|
|
1645
|
+
});
|
|
1646
|
+
on("keydown", ".bevel-file-tree__row", (event, row) => {
|
|
1647
|
+
if (event.isComposing || disabled(row)) return;
|
|
1648
|
+
const root = row.closest(".bevel-file-tree");
|
|
1649
|
+
if (!root) return;
|
|
1650
|
+
const visible = fileTreeVisible(root), index = visible.indexOf(row);
|
|
1651
|
+
const focus = (next) => { if (!next) return; event.preventDefault(); for (const item of fileTreeRows(root)) item.tabIndex = item === next ? 0 : -1; next.focus(); };
|
|
1652
|
+
if (event.key === "ArrowDown") return focus(visible[index + 1] || visible[0]);
|
|
1653
|
+
if (event.key === "ArrowUp") return focus(visible[index - 1] || visible.at(-1));
|
|
1654
|
+
if (event.key === "Home") return focus(visible[0]);
|
|
1655
|
+
if (event.key === "End") return focus(visible.at(-1));
|
|
1656
|
+
if (event.key === "ArrowRight" && row.hasAttribute("aria-expanded")) {
|
|
1657
|
+
if (row.getAttribute("aria-expanded") !== "true") { event.preventDefault(); setFileTreeExpanded(root, row, true); }
|
|
1658
|
+
else focus(fileTreeGroup(row)?.querySelector(".bevel-file-tree__row"));
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
if (event.key === "ArrowLeft") {
|
|
1662
|
+
if (row.hasAttribute("aria-expanded") && row.getAttribute("aria-expanded") === "true") { event.preventDefault(); setFileTreeExpanded(root, row, false); return; }
|
|
1663
|
+
const group = row.parentElement?.closest(".bevel-file-tree__group");
|
|
1664
|
+
if (group) focus(fileTreeRows(root).find((candidate) => candidate.getAttribute("aria-controls") === group.id));
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
if (event.key === "Enter" && row.tagName !== "A") { event.preventDefault(); row.click(); }
|
|
1668
|
+
});
|
|
1669
|
+
on("input", "[data-bevel-file-tree]", (event, input) => {
|
|
1670
|
+
const root = targetById(input.dataset.bevelFileTree);
|
|
1671
|
+
if (root) filterFileTree(root, input.value);
|
|
1672
|
+
});
|
|
1673
|
+
|
|
1674
|
+
const reportFormErrors = (form, controls) => {
|
|
1675
|
+
if (!form) {
|
|
1676
|
+
const invalid = invalidControls(controls);
|
|
1677
|
+
if (invalid.length) requestAnimationFrame(() => invalid[0].focus());
|
|
1678
|
+
return invalid;
|
|
1679
|
+
}
|
|
1680
|
+
const invalid = refreshErrors(form, controls);
|
|
1681
|
+
if (!invalid.length) return invalid;
|
|
1682
|
+
revealWizardControl(invalid[0]);
|
|
1683
|
+
const summary = form.querySelector(".bevel-errors");
|
|
1684
|
+
if (summary && !summary.hasAttribute("tabindex")) summary.tabIndex = -1;
|
|
1685
|
+
requestAnimationFrame(() => (summary || invalid[0]).focus());
|
|
1686
|
+
return invalid;
|
|
1687
|
+
};
|
|
1688
|
+
on("click", ".bevel-wizard [data-bevel-step]", (event, button) => {
|
|
1689
|
+
const root = button.closest(".bevel-wizard");
|
|
1690
|
+
if (button === root || !button.matches("button,[role='button']") || disabled(button)) return;
|
|
1691
|
+
const panels = wizardPanels(root), current = panels.findIndex((panel, index) => wizardKey(panels, panel, index) === String(root.dataset.bevelStep));
|
|
1692
|
+
const token = button.dataset.bevelStep;
|
|
1693
|
+
const target = token === "next" ? current + 1 : token === "prev" ? current - 1 : panels.findIndex((panel, index) => wizardKey(panels, panel, index) === token);
|
|
1694
|
+
if (current < 0 || target < 0 || target >= panels.length || disabled(panels[target])) return;
|
|
1695
|
+
if (target > current) {
|
|
1696
|
+
const requiredPanels = (token === "next" ? [panels[current]] : panels.slice(0, target)).filter((panel) => !disabled(panel));
|
|
1697
|
+
const invalid = wizardInvalid(requiredPanels);
|
|
1698
|
+
if (invalid.length) {
|
|
1699
|
+
event.preventDefault();
|
|
1700
|
+
reportFormErrors(root.closest("form"), wizardControls(invalid[0].closest(".bevel-wizard__panel")));
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
const previous = wizardKey(panels, panels[current], current), next = wizardKey(panels, panels[target], target);
|
|
1705
|
+
transition(root, "step", next, previous, next, () => { root.dataset.bevelStep = next; refreshWizard(root); });
|
|
1706
|
+
});
|
|
1707
|
+
document.addEventListener("submit", (event) => {
|
|
1708
|
+
const form = event.target.closest?.("form[data-bevel-validate]");
|
|
1709
|
+
if (!form) return;
|
|
1710
|
+
const root = [...form.querySelectorAll(".bevel-wizard")].find((wizard) => wizard.closest("form") === form);
|
|
1711
|
+
const invalid = reportFormErrors(form, [...form.elements]);
|
|
1712
|
+
if (invalid.length) {
|
|
1713
|
+
event.preventDefault();
|
|
1714
|
+
event.stopImmediatePropagation();
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
if (root) action(root, "finish", root.dataset.bevelStep || "");
|
|
1718
|
+
}, true);
|
|
1719
|
+
document.addEventListener("invalid", (event) => { const form = event.target.form?.matches("[data-bevel-validate]") ? event.target.form : null; if (form) requestAnimationFrame(() => refreshErrors(form)); }, true);
|
|
1720
|
+
document.addEventListener("input", (event) => { const form = event.target.form?.matches("[data-bevel-validate]") ? event.target.form : null; if (form && event.target.hasAttribute("aria-invalid")) queueMicrotask(() => { if (event.target.validity.valid) event.target.removeAttribute("aria-invalid"); refreshErrors(form); }); }, true);
|
|
1721
|
+
document.addEventListener("reset", (event) => { const form = event.target.closest?.("form[data-bevel-validate]"); if (form) requestAnimationFrame(() => { for (const control of form.querySelectorAll('[aria-invalid="true"]')) control.removeAttribute("aria-invalid"); for (const message of form.querySelectorAll("[data-bevel-error-for]")) setFieldError(message, ""); const summary = form.querySelector(".bevel-errors"); if (summary) summary.hidden = true; summary?.querySelector(".bevel-errors__list")?.replaceChildren(); const session = wizardForms.get(form); for (const [root, step] of session?.initialSteps || []) { if (step == null) delete root.dataset.bevelStep; else root.dataset.bevelStep = step; refreshWizard(root); } }); }, true);
|
|
1722
|
+
document.addEventListener("reset", (event) => requestAnimationFrame(() => refresh(event.target)), true);
|
|
1723
|
+
on("click", "[data-bevel-action]", (e, button) => { if (disabled(button) || button.closest(".bevel-menu") || (button.hasAttribute("data-bevel-toggle") && button.closest(".bevel-toolbar"))) return; const root = button.closest(".bevel-action-banner,.bevel-bulk-bar,.bevel-toolbar,.bevel-breadcrumbs,.bevel-kanban,.bevel-settings,.bevel-popconfirm,.bevel-sidebar,.bevel-files") || button; action(root, button.dataset.bevelAction, button.dataset.bevelKey); });
|
|
1724
|
+
on("click", ".bevel-action-banner [data-bevel-banner-dismiss]", (event, button) => { const root = button.closest(".bevel-action-banner"); if (disabled(button)) return; transition(root, "dismiss", root.dataset.bevelKey || "", false, true, () => { root.hidden = true; root.dataset.bevelDismissed = ""; }); });
|
|
1725
|
+
on("keydown", ".bevel-bulk-bar button", (event, button) => {
|
|
1726
|
+
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
|
|
1727
|
+
const buttons = [...button.closest(".bevel-bulk-bar").querySelectorAll("button")].filter(item => !disabled(item) && !item.hidden), index = buttons.indexOf(button), count = buttons.length;
|
|
1728
|
+
if (index < 0) return;
|
|
1729
|
+
event.preventDefault();
|
|
1730
|
+
buttons[event.key === "Home" ? 0 : event.key === "End" ? count - 1 : (index + (event.key === "ArrowRight" ? 1 : -1) + count) % count].focus();
|
|
1731
|
+
});
|
|
1732
|
+
|
|
1733
|
+
on("click", ".bevel-code [data-bevel-code-wrap]", (e, button) => { if (disabled(button)) return; const root = button.closest(".bevel-code"), previous = root.hasAttribute("data-bevel-wrap"); transition(root, "wrap", "wrap", previous, !previous, () => { root.toggleAttribute("data-bevel-wrap", !previous); refreshCode(root); }); });
|
|
1734
|
+
on("click", ".bevel-code [data-bevel-copy]", async (e, button) => {
|
|
1735
|
+
if (disabled(button)) return;
|
|
1736
|
+
const root = button.closest(".bevel-code"), code = root.querySelector("pre code") || root.querySelector("code"), text = code?.textContent || "";
|
|
1737
|
+
clearCodeFeedback(root);
|
|
1738
|
+
const session = {}; codeFeedback.set(root, session);
|
|
1739
|
+
let copied = false;
|
|
1740
|
+
try { await navigator.clipboard.writeText(text); copied = true; } catch {}
|
|
1741
|
+
if (!root.isConnected || codeFeedback.get(root) !== session) return;
|
|
1742
|
+
const notice = document.createElement("p"); notice.className = "bevel-code__notice"; notice.dataset.bevelTransient = "clipboard"; notice.setAttribute("role", "status");
|
|
1743
|
+
notice.textContent = copied ? "Copied" : "Could not copy. Select the code below and copy it manually."; root.append(notice);
|
|
1744
|
+
if (copied) session.timer = setTimeout(() => clearCodeFeedback(root), 1500);
|
|
1745
|
+
else {
|
|
1746
|
+
const fallback = document.createElement("textarea"); fallback.className = "bevel-code__fallback"; fallback.dataset.bevelTransient = "clipboard"; fallback.readOnly = true; fallback.setAttribute("aria-label", "Code to copy manually"); fallback.value = text; root.append(fallback); fallback.focus(); fallback.select();
|
|
1747
|
+
error(root, "clipboard", "Could not copy code to the clipboard.");
|
|
1748
|
+
}
|
|
1749
|
+
});
|
|
1750
|
+
on("click", ".bevel-diff button[data-bevel-layout]", (e, button) => { if (disabled(button)) return; const root = button.closest(".bevel-diff"), previous = root.dataset.bevelLayout || "unified", next = button.dataset.bevelLayout; if (!["unified", "split"].includes(next) || previous === next) return; transition(root, "layout", next, previous, next, () => { root.dataset.bevelLayout = next; refreshDiff(root); }); });
|
|
1751
|
+
on("click", ".bevel-activity [data-bevel-activity-more]", (event, button) => {
|
|
1752
|
+
if (disabled(button)) return;
|
|
1753
|
+
const root = button.closest(".bevel-activity");
|
|
1754
|
+
if (root.hasAttribute("data-bevel-expanded")) action(root, "load-more", root.dataset.bevelKey || "");
|
|
1755
|
+
else transition(root, "reveal", root.dataset.bevelKey || "", false, true, () => { root.dataset.bevelExpanded = ""; refreshActivity(root); });
|
|
1756
|
+
});
|
|
1757
|
+
document.addEventListener("error", (event) => { if (event.target?.matches?.(".bevel-avatars__image")) showAvatarFallback(event.target); }, true);
|
|
1758
|
+
on("click", ".bevel-chart [data-bevel-series]", (event, button) => {
|
|
1759
|
+
if (disabled(button)) return;
|
|
1760
|
+
const root = button.closest(".bevel-chart"), previous = button.getAttribute("aria-pressed") !== "false", next = !previous;
|
|
1761
|
+
transition(root, "series", button.dataset.bevelSeries, previous, next, () => { button.setAttribute("aria-pressed", String(next)); refreshChart(root); });
|
|
1762
|
+
});
|
|
1763
|
+
const kanbanSelect = (root, key) => {
|
|
1764
|
+
if (disabled(root) || root.dataset.bevelSelected === key) return;
|
|
1765
|
+
transition(root,'select',key,root.dataset.bevelSelected || '',key,()=>{ if (key) root.dataset.bevelSelected=key; else delete root.dataset.bevelSelected; if (root.hasAttribute('data-bevel-selected-keys')) root.dataset.bevelSelectedKeys=JSON.stringify(key ? [key] : []); refreshKanban(root); });
|
|
1766
|
+
};
|
|
1767
|
+
on('input','.bevel-kanban__search input,input.bevel-kanban__search',(event,input)=>{
|
|
1768
|
+
const root=input.closest('.bevel-kanban'),previous=input.getAttribute('value') || '',next=input.value;
|
|
1769
|
+
if (kanbanDisabled(input)) { input.value=previous; return; }
|
|
1770
|
+
const committed=localOrRemote(root,'search','',previous,next,()=>{input.setAttribute('value',next);root.dataset.bevelQuery=next;refreshKanban(root);});
|
|
1771
|
+
if (!committed || root.dataset.bevelDataMode==='remote') input.value=previous;
|
|
1772
|
+
});
|
|
1773
|
+
on('click','.bevel-kanban__card',(event,card)=>{if (!kanbanDisabled(card) && !event.target.closest('button,a,input,select,textarea')) kanbanSelect(card.closest('.bevel-kanban'),card.dataset.bevelKey);});
|
|
1774
|
+
on('keydown','.bevel-kanban__card',(event,card)=>{
|
|
1775
|
+
if (event.target !== card || kanbanDisabled(card)) return;
|
|
1776
|
+
const root=card.closest('.bevel-kanban');
|
|
1777
|
+
if (event.key==='Enter' || event.key===' ') {event.preventDefault();kanbanSelect(root,card.dataset.bevelKey);return;}
|
|
1778
|
+
if (!event.altKey || !['ArrowLeft','ArrowRight','ArrowUp','ArrowDown'].includes(event.key)) return;
|
|
1779
|
+
event.preventDefault();const list=card.parentElement,columns=[...root.querySelectorAll('.bevel-kanban__list')].filter(node=>node.closest('.bevel-kanban')===root),ci=columns.indexOf(list),siblings=[...list.children].filter(node=>node.matches('.bevel-kanban__card')),index=siblings.indexOf(card);
|
|
1780
|
+
if (event.key==='ArrowUp' || event.key==='ArrowDown') kanbanMove(root,card,list,Math.max(0,Math.min(siblings.length-1,index+(event.key==='ArrowUp'?-1:1))));
|
|
1781
|
+
else {const destination=columns[ci+(event.key==='ArrowLeft'?-1:1)];if(destination)kanbanMove(root,card,destination,[...destination.children].filter(node=>node.matches('.bevel-kanban__card')).length);}
|
|
1782
|
+
});
|
|
1783
|
+
on('click','[data-bevel-kanban-create]',(event,button)=>{if(kanbanDisabled(button))return;const root=button.closest('.bevel-kanban');kanbanEditor(root,null,button.dataset.bevelColumn || button.closest('.bevel-kanban__column')?.dataset.bevelColumnKey || root.querySelector('.bevel-kanban__column')?.dataset.bevelColumnKey);});
|
|
1784
|
+
on('click','[data-bevel-kanban-edit]',(event,button)=>{if(kanbanDisabled(button))return;const root=button.closest('.bevel-kanban'),card=kanbanSelected(root);if(card)kanbanEditor(root,card);});
|
|
1785
|
+
on('click','[data-bevel-kanban-close]',(event,button)=>{if(kanbanDisabled(button))return;const root=button.closest('.bevel-kanban'),card=kanbanSelected(root);kanbanSelect(root,'');if(!root.dataset.bevelSelected)card?.focus();});
|
|
1786
|
+
on('input','[data-bevel-kanban-form] [name="title"]',(event,input)=>input.setCustomValidity(''));
|
|
1787
|
+
on('keydown','[data-bevel-kanban-form]',(event)=>{if(event.isComposing && event.key==='Enter')event.preventDefault();});
|
|
1788
|
+
on('submit','[data-bevel-kanban-form]',(event,form)=>{
|
|
1789
|
+
event.preventDefault();event.stopPropagation();const root=form.closest('.bevel-kanban');
|
|
1790
|
+
if(disabled(root)||disabled(event.submitter || form.querySelector('[data-bevel-kanban-save]')))return;
|
|
1791
|
+
const key=form.elements.key.value,existing=kanbanCards(root).find(card=>card.dataset.bevelKey===key),title=form.elements.title;
|
|
1792
|
+
title.setCustomValidity(title.value.trim() ? '' : 'Enter a task title.');if(!form.reportValidity())return;
|
|
1793
|
+
const value={key:key || `FN-${Math.max(100,...kanbanCards(root).map(card=>Number(card.dataset.bevelKey.replace(/\D/g,''))||0))+1}`,column:form.elements.column.value,title:title.value.trim(),description:form.elements.description.value.trim(),assignee:form.elements.assignee.value,priority:form.elements.priority.value,due:form.elements.due.value,labels:[...new Set(form.elements.labels.value.split(',').map(label=>label.trim().toLowerCase()).filter(Boolean))]};
|
|
1794
|
+
const column=root.querySelector(`.bevel-kanban__column[data-bevel-column-key="${CSS.escape(value.column)}"] .bevel-kanban__list`);if(!column)return;
|
|
1795
|
+
const operation=existing?'edit':'create';
|
|
1796
|
+
localOrRemote(root,operation,value.key,kanbanSnapshot(existing),value,()=>{
|
|
1797
|
+
if(existing)updateKanbanCard(existing,value);else{const card=kanbanCard(value);column.insertBefore(card,column.querySelector('.bevel-kanban__add'));updateKanbanCard(card,value);}
|
|
1798
|
+
root.dataset.bevelSelected=value.key;closeOverlay(form.closest('dialog'),{focus:false});refreshKanban(root);root.querySelector('[data-bevel-kanban-edit]')?.focus();
|
|
1799
|
+
},`${operation}:${value.key}`);
|
|
1800
|
+
});
|
|
1801
|
+
on('click','[data-bevel-kanban-archive],[data-bevel-kanban-restore]',(event,button)=>{
|
|
1802
|
+
if(kanbanDisabled(button))return;const root=button.closest('.bevel-kanban'),card=kanbanSelected(root);if(!card)return;
|
|
1803
|
+
const previous=card.hasAttribute('data-bevel-archived'),next=!button.hasAttribute('data-bevel-kanban-restore');if(previous===next)return;
|
|
1804
|
+
localOrRemote(root,next?'archive':'restore',card.dataset.bevelKey,previous,next,()=>{card.toggleAttribute('data-bevel-archived',next);if(button.closest('[popover]'))closeOverlay(button.closest('[popover]'),{focus:false});refreshKanban(root);root.querySelector(next?'[data-bevel-kanban-restore]':'[data-bevel-kanban-edit]')?.focus();},`archive:${card.dataset.bevelKey}`);
|
|
1805
|
+
});
|
|
1806
|
+
on('change','[data-bevel-kanban-inspector] [data-bevel-check-key]',(event,input)=>{
|
|
1807
|
+
const root=input.closest('.bevel-kanban'),card=kanbanSelected(root),source=card && kanbanItems(card).find(node=>node.dataset.bevelCheckKey===input.dataset.bevelCheckKey);if(!source)return;
|
|
1808
|
+
const previous=source.checked,next=input.checked;
|
|
1809
|
+
const committed=!kanbanDisabled(input)&&localOrRemote(root,'checklist',card.dataset.bevelKey,{key:source.dataset.bevelCheckKey,checked:previous},{key:source.dataset.bevelCheckKey,checked:next},()=>{source.checked=next;source.toggleAttribute('checked',next);refreshKanban(root);root.querySelector(`[data-bevel-kanban-inspector] [data-bevel-check-key="${CSS.escape(source.dataset.bevelCheckKey)}"]`)?.focus();},`checklist:${card.dataset.bevelKey}:${source.dataset.bevelCheckKey}`);
|
|
1810
|
+
if(!committed || root.dataset.bevelDataMode==='remote'){input.checked=previous;input.toggleAttribute('checked',previous);}
|
|
1811
|
+
});
|
|
1812
|
+
on('click','[data-bevel-kanban-check-add],[data-bevel-kanban-check-cancel]',(event,button)=>{
|
|
1813
|
+
if(kanbanDisabled(button))return;const root=button.closest('.bevel-kanban'),form=root.querySelector('[data-bevel-kanban-check-form]');if(!form)return;
|
|
1814
|
+
form.hidden=button.hasAttribute('data-bevel-kanban-check-cancel');if(form.hidden){form.reset();root.querySelector('[data-bevel-kanban-check-add]')?.focus();}else form.elements.text.focus();
|
|
1815
|
+
});
|
|
1816
|
+
on('input','[data-bevel-kanban-check-form] [name="text"]',(event,input)=>input.setCustomValidity(''));
|
|
1817
|
+
on('submit','[data-bevel-kanban-check-form]',(event,form)=>{
|
|
1818
|
+
event.preventDefault();event.stopPropagation();const root=form.closest('.bevel-kanban'),card=kanbanSelected(root);if(!card||disabled(root)||disabled(event.submitter))return;
|
|
1819
|
+
const text=form.elements.text.value.trim();form.elements.text.setCustomValidity(text ? '' : 'Enter a checklist item.');if(!form.reportValidity())return;const next={key:`check-${crypto.randomUUID()}`,text,checked:false};
|
|
1820
|
+
localOrRemote(root,'checklist-add',card.dataset.bevelKey,null,next,()=>{let list=card.querySelector('.bevel-kanban__checklist-data');if(!list){list=kanbanNode('div','bevel-kanban__checklist-data');list.hidden=true;card.append(list);}const label=kanbanNode('label'),input=kanbanNode('input');input.type='checkbox';input.dataset.bevelCheckKey=next.key;label.append(input,document.createTextNode(text));list.append(label);refreshKanban(root);root.querySelector('[data-bevel-kanban-check-add]')?.focus();},`checklist:${card.dataset.bevelKey}`);
|
|
1821
|
+
});
|
|
1822
|
+
on('click','[data-bevel-kanban-check-remove]',(event,button)=>{
|
|
1823
|
+
if(kanbanDisabled(button))return;const root=button.closest('.bevel-kanban'),card=kanbanSelected(root),input=card && kanbanItems(card).find(item=>item.dataset.bevelCheckKey===button.dataset.bevelKanbanCheckRemove);if(!input)return;
|
|
1824
|
+
localOrRemote(root,'checklist-remove',card.dataset.bevelKey,{key:input.dataset.bevelCheckKey,text:input.parentElement.textContent.trim(),checked:input.checked},null,()=>{input.parentElement.remove();refreshKanban(root);root.querySelector('[data-bevel-kanban-check-add]')?.focus();},`checklist:${card.dataset.bevelKey}`);
|
|
1825
|
+
});
|
|
1826
|
+
on('click','[data-bevel-kanban-move]',(event,button)=>{
|
|
1827
|
+
if(kanbanDisabled(button))return;const root=button.closest('.bevel-kanban'),card=kanbanSelected(root),key=root.querySelector('[data-bevel-kanban-move-column]').value,list=root.querySelector(`.bevel-kanban__column[data-bevel-column-key="${CSS.escape(key)}"] .bevel-kanban__list`);
|
|
1828
|
+
if(list)kanbanMove(root,card,list,root.querySelector('[data-bevel-kanban-move-position]').value==='first'?0:[...list.children].filter(node=>node.matches('.bevel-kanban__card')).length);
|
|
1829
|
+
});
|
|
1830
|
+
on('change','[data-bevel-kanban-filter]',(event,control)=>{
|
|
1831
|
+
const root=control.closest('.bevel-kanban'),kind=control.dataset.bevelKanbanFilter;if(!kanbanFilterKinds.includes(kind))return;
|
|
1832
|
+
const attribute=`data-bevel-filter-${kind}`,previous=root.getAttribute(attribute)||'',next=control.value;
|
|
1833
|
+
const committed=!kanbanDisabled(control)&&localOrRemote(root,'filter',kind,previous,next,()=>{if(next)root.setAttribute(attribute,next);else root.removeAttribute(attribute);refreshKanban(root);},`filter:${kind}`);
|
|
1834
|
+
if(!committed||root.dataset.bevelDataMode==='remote'){control.value=previous;if(control.matches('input'))control.setAttribute('value',previous);else for(const option of control.options)option.toggleAttribute('selected',option.value===previous);}
|
|
1835
|
+
});
|
|
1836
|
+
on('click','[data-bevel-kanban-filter-clear]',(event,button)=>{
|
|
1837
|
+
if(kanbanDisabled(button))return;const root=button.closest('.bevel-kanban'),previous={from:root.dataset.bevelFilterDueFrom||'',to:root.dataset.bevelFilterDueTo||''};
|
|
1838
|
+
localOrRemote(root,'filter','due',previous,{from:'',to:''},()=>{delete root.dataset.bevelFilterDueFrom;delete root.dataset.bevelFilterDueTo;refreshKanban(root);},'filter:due');
|
|
1839
|
+
});
|
|
1840
|
+
on('keydown','.bevel-kanban',(event,root)=>{if(event.metaKey || event.ctrlKey){if(event.key.toLowerCase()==='k'){event.preventDefault();(root.querySelector('.bevel-kanban__search input')||root.querySelector('input.bevel-kanban__search'))?.focus();}else if(event.key==='Enter' && event.target.closest('.bevel-kanban__composer')){event.preventDefault();event.target.closest('.bevel-kanban__composer').click();}}});
|
|
1841
|
+
on('pointerdown','.bevel-kanban__card' ,(event,card)=>{
|
|
1842
|
+
if(event.button!==0||kanbanDisabled(card)||event.target.closest('button,a,input,select,textarea')||sessions.has(card))return;
|
|
1843
|
+
const root=card.closest('.bevel-kanban');if(kanbanFiltered(root)){root.dataset.bevelMoveMessage='Clear filters to drag tasks. Use Move while filters are active.';return;}
|
|
1844
|
+
const pointerId=event.pointerId,startX=event.clientX,startY=event.clientY,session={active:false,list:null,target:null,before:false,cancel:()=>stop(false)};
|
|
1845
|
+
const clear=()=>{session.target?.removeAttribute('data-bevel-drop-target');session.target=null;session.list=null;};
|
|
1846
|
+
const move=current=>{
|
|
1847
|
+
if(current.pointerId!==pointerId)return;if(!session.active&&Math.hypot(current.clientX-startX,current.clientY-startY)<5)return;
|
|
1848
|
+
session.active=true;card.dataset.bevelDragging='';clear();const hit=document.elementFromPoint(current.clientX,current.clientY),target=hit?.closest('.bevel-kanban__card'),list=hit?.closest('.bevel-kanban__list')||hit?.closest('.bevel-kanban__column')?.querySelector('.bevel-kanban__list');
|
|
1849
|
+
if(!list||list.closest('.bevel-kanban')!==root||target===card)return;session.list=list;
|
|
1850
|
+
if(target&&target!==card){session.target=target;const rect=target.getBoundingClientRect();session.before=current.clientY<rect.top+rect.height/2;target.dataset.bevelDropTarget=session.before?'before':'after';}
|
|
1851
|
+
};
|
|
1852
|
+
const stop=commit=>{
|
|
1853
|
+
if(!sessions.has(card))return;document.removeEventListener('pointermove',move);document.removeEventListener('pointerup',up);document.removeEventListener('pointercancel',cancel);document.removeEventListener('lostpointercapture',cancel);document.removeEventListener('keydown',escape,true);sessions.delete(card);card.removeAttribute('data-bevel-dragging');
|
|
1854
|
+
const {list,target,before}=session;clear();if(!commit||!session.active||!list||!root.isConnected||!card.isConnected||target&&!target.isConnected)return;
|
|
1855
|
+
const siblings=[...list.children].filter(node=>node.matches('.bevel-kanban__card')&&node!==card),index=target?siblings.indexOf(target)+(before?0:1):siblings.length;
|
|
1856
|
+
kanbanMove(root,card,list,index);
|
|
1857
|
+
};
|
|
1858
|
+
const up=current=>{if(current.pointerId===pointerId)stop(true);},cancel=current=>{if(current.pointerId===pointerId)stop(false);},escape=current=>{if(current.key==='Escape'){current.preventDefault();stop(false);}};
|
|
1859
|
+
sessions.set(card,session);document.addEventListener('pointermove',move);document.addEventListener('pointerup',up);document.addEventListener('pointercancel',cancel);document.addEventListener('lostpointercapture',cancel);document.addEventListener('keydown',escape,true);
|
|
1860
|
+
});
|
|
1861
|
+
|
|
1862
|
+
const cancelGridEdit = (cell, focus = true) => { const session=sessions.get(cell); if(!session)return; session.editor.remove(); cell.removeAttribute("data-bevel-editing"); cell.removeAttribute("data-bevel-invalid"); sessions.delete(cell); const error=cell.querySelector(".bevel-grid__cell-error");if(error)error.hidden=true;if (focus && session.button?.isConnected) session.button.focus(); };
|
|
1863
|
+
const beginGridEdit = cell => { const grid=gridOf(cell); if(!grid||disabled(cell))return; const active=grid.querySelector('.bevel-grid__cell[data-bevel-editing]'); if(active&&active!==cell)cancelGridEdit(active); if(sessions.has(cell))return; const button=cell.querySelector('[data-bevel-cell-edit]'), value=cell.dataset.value ?? button?.querySelector('.bevel-grid__cell-value')?.textContent ?? cell.textContent.trim(), type=cell.dataset.bevelEdit;
|
|
1864
|
+
const editor=document.createElement("span"); editor.className="bevel-grid__cell-editor"; editor.dataset.bevelTransient="grid-editor"; let input;
|
|
1865
|
+
if(type==="enum"){ input=document.createElement("select"); input.className="bevel-select__field"; for(const option of JSON.parse(cell.dataset.bevelOptions||"[]")){ const node=document.createElement("option"); node.value=option.id; node.textContent=option.label; input.append(node); } }
|
|
1866
|
+
else { input=document.createElement("input"); input.className="bevel-grid__cell-input"; input.type="text"; if(type==="number") input.inputMode="decimal"; }
|
|
1867
|
+
input.value=value; input.setAttribute("aria-label",cell.dataset.bevelLabel||`Edit ${cell.dataset.bevelColumn||"cell"}`); editor.append(input);
|
|
1868
|
+
for (const [name, icon] of [["save", '<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.71" stroke-linecap="square" stroke-linejoin="miter"><path d="m5 12 4 4L19 6"/></svg>'], ["cancel", '<svg aria-hidden="true" width="14" height="14" viewBox="0 0 256 256" fill="currentColor"><path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"/></svg>']]) {
|
|
1869
|
+
const control=document.createElement("button");control.type="button";control.className="bevel-grid__cell-action";control.dataset.bevelCellAction=name;control.setAttribute("aria-label", name==="save"?"Save cell":"Cancel cell");control.innerHTML=icon;editor.append(control);
|
|
1870
|
+
} cell.append(editor); cell.dataset.bevelEditing=""; const canonical = [cell.dataset.value, cell.dataset.sort, button?.querySelector(".bevel-grid__cell-value")?.textContent]; const valid = () => cell.contains(editor) && cell.querySelector("[data-bevel-cell-edit]") === button && JSON.stringify([cell.dataset.value, cell.dataset.sort, button?.querySelector(".bevel-grid__cell-value")?.textContent]) === JSON.stringify(canonical); sessions.set(cell,{editor,input,button,value,valid,cancel:()=>cancelGridEdit(cell,false)}); input.focus(); input.select?.(); };
|
|
1871
|
+
const gridEditable = grid => [...grid.querySelectorAll('.bevel-grid__cell[data-bevel-edit]')].filter(cell=>!cell.hidden&&!cell.closest("tr")?.hidden&&!disabled(cell));
|
|
1872
|
+
const resortGrid = grid => { const th=grid.querySelector('thead th[aria-sort="ascending"],thead th[aria-sort="descending"]'); if(!th)return; const index=[...th.parentElement.children].indexOf(th),type=th.dataset.type||"text",sign=th.getAttribute("aria-sort")==="ascending"?1:-1,tbody=grid.querySelector("tbody"),units=[]; for(const row of [...tbody.children])if(row.classList.contains("bevel-grid__row")){const unit=[row],detail=detailOf(row);if(detail)unit.push(detail);units.push(unit);} units.sort((a,b)=>{const x=cellKey(a[0],index,type),y=cellKey(b[0],index,type);return x<y?-sign:x>y?sign:0;});tbody.append(...units.flat()); };
|
|
1873
|
+
const commitGridEdit = (cell,direction=0) => { const session=sessions.get(cell),grid=gridOf(cell); if(!session||!grid)return false; if(!session.valid()){cancelGridEdit(cell,false);return false;} const input=session.input,next=input.value,type=cell.dataset.bevelEdit,key=cell.closest("tr")?.dataset.bevelKey||"",column=cell.dataset.bevelColumn||""; if((type==="number"&&(next.trim()===""||!Number.isFinite(Number(next))))||!input.checkValidity()){ cell.dataset.bevelInvalid=""; input.setAttribute("aria-invalid","true"); const error=cell.querySelector('.bevel-grid__cell-error'); if(error){error.hidden=false;if(!error.id)error.id=`bevel-grid-error-${crypto.randomUUID()}`;input.setAttribute("aria-describedby",error.id);} input.focus(); return false; } const previous=cell.dataset.value??session.value, display=type==="number"&&cell.dataset.bevelFormat==="currency"?new Intl.NumberFormat("en-US",{style:"currency",currency:"USD"}).format(Number(next)):type==="enum"?input.selectedOptions[0]?.textContent||next:next; const finish=()=>{ const value=cell.querySelector('.bevel-grid__cell-value'); if(value)value.textContent=display; cell.dataset.value=next; cell.dataset.sort=next; session.editor.remove(); cell.removeAttribute("data-bevel-editing"); cell.removeAttribute("data-bevel-invalid"); sessions.delete(cell); const error=cell.querySelector(".bevel-grid__cell-error");if(error)error.hidden=true;resortGrid(grid); layoutGrid(grid); const target=grid.querySelector(`tr[data-bevel-key="${CSS.escape(key)}"] [data-bevel-column="${CSS.escape(column)}"]`); if(target?.closest("tr")?.hidden){ (gridEditable(grid).find(candidate=>!candidate.closest("tr")?.hidden)?.querySelector("[data-bevel-cell-edit]")||grid.querySelector(".bevel-grid__status,[tabindex]")||grid)?.focus(); } else if(direction){ const cells=gridEditable(grid),index=cells.indexOf(target); (cells[index+direction]||target)?.querySelector("[data-bevel-cell-edit]")?.focus(); } else target?.querySelector("[data-bevel-cell-edit]")?.focus(); };
|
|
1874
|
+
return grid.dataset.bevelDataMode==="remote" ? (remoteRequest(grid,"edit",key,{column,value:previous},{column,value:next},`edit:${key}:${column}`),cancelGridEdit(cell),true) : transition(grid,"edit",key,{column,value:previous},{column,value:next},finish); };
|
|
1875
|
+
on("click", ".bevel-grid__cell [data-bevel-cell-action]", (event, button) => { const cell=button.closest(".bevel-grid__cell");button.dataset.bevelCellAction==="save"?commitGridEdit(cell):cancelGridEdit(cell); });
|
|
1876
|
+
on("click", ".bevel-grid__cell [data-bevel-cell-edit]",(e,button)=>beginGridEdit(button.closest(".bevel-grid__cell")));
|
|
1877
|
+
on("dblclick", ".bevel-grid__cell[data-bevel-edit]",(e,cell)=>beginGridEdit(cell));
|
|
1878
|
+
on("keydown", ".bevel-grid__cell[data-bevel-edit]",(e,cell)=>{ if(!cell.hasAttribute("data-bevel-editing")&&(e.key==="Enter"||e.key==="F2")){e.preventDefault();beginGridEdit(cell);} });
|
|
1879
|
+
on("keydown", ".bevel-grid__cell[data-bevel-edit] :is(input,select)",(e,input)=>{ if(e.isComposing)return; const cell=input.closest(".bevel-grid__cell"); if(e.key==="Escape"){e.preventDefault();cancelGridEdit(cell);} else if(e.key==="Enter"){e.preventDefault();commitGridEdit(cell);} else if(e.key==="Tab"){e.preventDefault();commitGridEdit(cell,e.shiftKey?-1:1);} });
|
|
1880
|
+
on("pointerdown","body",(e)=>{ for(const cell of document.querySelectorAll('.bevel-grid__cell[data-bevel-editing]')) if(!cell.contains(e.target))cancelGridEdit(cell); });
|
|
1881
|
+
const filterDraft = root => root.querySelector(".bevel-filter__draft");
|
|
1882
|
+
on("click",".bevel-filter [data-bevel-filter-edit]",(e,button)=>{ const root=button.closest(".bevel-filter"),draft=filterDraft(root),committed=root.querySelector(".bevel-filter__rules"); draft.replaceChildren(...[...committed.children].map(node=>node.cloneNode(true))); draft.querySelectorAll(".bevel-filter__rule").forEach(refreshFilterRule); draft.hidden=false; committed.hidden=true; root.dataset.bevelEditing=""; });
|
|
1883
|
+
on("change",".bevel-filter__draft [data-bevel-filter-field]",(e,select)=>{const row=select.closest(".bevel-filter__rule");row.dataset.bevelField=select.value;refreshFilterRule(row);});
|
|
1884
|
+
on("change",".bevel-filter__draft [data-bevel-filter-operator]",(e,select)=>{const row=select.closest(".bevel-filter__rule");row.dataset.bevelOperator=select.value;const input=row.querySelector("[data-bevel-filter-value]");if(input)input.disabled=select.value==="is-empty";});
|
|
1885
|
+
on("click",".bevel-filter [data-bevel-filter-add]",(e,button)=>{const root=button.closest(".bevel-filter"),template=root.querySelector("template[data-bevel-filter-rule]"),rule=template?.content.firstElementChild?.cloneNode(true);if(rule){rule.dataset.bevelKey=crypto.randomUUID?.()||String(Date.now());filterDraft(root).append(rule);refreshFilterRule(rule);rule.querySelector("select,input")?.focus();}});
|
|
1886
|
+
on("click",".bevel-filter [data-bevel-filter-remove]",(e,button)=>button.closest(".bevel-filter__rule")?.remove());
|
|
1887
|
+
on("click",".bevel-filter [data-bevel-filter-cancel]",(e,button)=>{ const root=button.closest(".bevel-filter"); filterDraft(root).replaceChildren(); filterDraft(root).hidden=true; root.querySelector(".bevel-filter__rules").hidden=false; root.removeAttribute("data-bevel-editing"); });
|
|
1888
|
+
on("click", ".bevel-filter [data-bevel-filter-apply]", (e, button) => { const root=button.closest(".bevel-filter"),draft=filterDraft(root),committed=root.querySelector(".bevel-filter__rules"),definitions=filterDefinitions(root),expression={match:root.dataset.bevelMatch==="any"?"any":"all",rules:[...draft.children].map(row=>({key:row.dataset.bevelKey,field:row.dataset.bevelField,operator:row.dataset.bevelOperator,value:row.querySelector('[data-bevel-filter-value]')?.value??""}))}; if(expression.rules.some(rule=>!filterRuleValid(rule,definitions.find(item=>item.id===rule.field)))){root.dataset.bevelInvalid="";return;} const grid=targetById(root.dataset.bevelGrid),previous=filterRead(root); const commit=()=>{committed.replaceChildren(...[...draft.children].map(node=>node.cloneNode(true))); draft.replaceChildren(); draft.hidden=true; committed.hidden=false; root.removeAttribute("data-bevel-editing"); root.removeAttribute("data-bevel-invalid"); if(grid){grid.dataset.page="1";layoutGrid(grid);} }; if(grid)localOrRemote(grid,"filter","",previous,expression,commit); else transition(root,"filter","",previous,expression,commit); });
|
|
1889
|
+
const columnHeaders = grid => [...grid.querySelectorAll("thead > tr:first-child > [data-bevel-column]")];
|
|
1890
|
+
const rememberGridFields = grid => {
|
|
1891
|
+
const cells = [...grid.querySelector("thead > tr")?.children || []];
|
|
1892
|
+
for (const node of grid.querySelectorAll("[data-grid-column]")) if (!node.dataset.bevelField) {
|
|
1893
|
+
const key = cells[Number(node.dataset.gridColumn)]?.dataset.bevelColumn;
|
|
1894
|
+
if (key) node.dataset.bevelField = key;
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1897
|
+
const orderColumn = (manager, key, destination, before) => {
|
|
1898
|
+
const grid = targetById(manager.dataset.bevelGrid), headers = columnHeaders(grid);
|
|
1899
|
+
rememberGridFields(grid);
|
|
1900
|
+
const header = headers.find(node => node.dataset.bevelColumn === key), target = headers.find(node => node.dataset.bevelColumn === destination);
|
|
1901
|
+
if (header && target) before ? target.before(header) : target.after(header);
|
|
1902
|
+
refreshColumns(manager);
|
|
1903
|
+
};
|
|
1904
|
+
const refreshColumns = manager => {
|
|
1905
|
+
const grid = targetById(manager.dataset.bevelGrid), table = grid?.querySelector(".bevel-grid__table");
|
|
1906
|
+
if (!table) return;
|
|
1907
|
+
rememberGridFields(grid);
|
|
1908
|
+
const headers = columnHeaders(grid), order = headers.map(node => node.dataset.bevelColumn);
|
|
1909
|
+
const attr = (node, name, value) => { if (node.getAttribute(name) !== String(value)) node.setAttribute(name, String(value)); };
|
|
1910
|
+
const flag = (node, name, value) => { if (node.hasAttribute(name) !== Boolean(value)) node.toggleAttribute(name, Boolean(value)); };
|
|
1911
|
+
const style = (node, name, value) => { if (node.style[name] !== value) node.style[name] = value; };
|
|
1912
|
+
const managerItems = [...manager.querySelectorAll(":scope > .bevel-columns__item")];
|
|
1913
|
+
const arranged = order.map(key => managerItems.find(node => node.dataset.bevelColumn === key)).filter(Boolean);
|
|
1914
|
+
if (arranged.some((node, index) => node !== managerItems[index])) manager.append(...arranged);
|
|
1915
|
+
for (const row of table.querySelectorAll("tbody > tr,tfoot > tr")) {
|
|
1916
|
+
const cells = [...row.children].filter(node => node.dataset.bevelColumn), arrangedCells = order.map(key => cells.find(node => node.dataset.bevelColumn === key)).filter(Boolean);
|
|
1917
|
+
if (arrangedCells.some((node, index) => node !== cells[index])) row.append(...arrangedCells);
|
|
1918
|
+
}
|
|
1919
|
+
let colgroup = table.querySelector(":scope > colgroup");
|
|
1920
|
+
if (!colgroup) { colgroup = document.createElement("colgroup"); table.prepend(colgroup); }
|
|
1921
|
+
flag(colgroup, "data-bevel-columns", true);
|
|
1922
|
+
const allHeaders = [...table.querySelector("thead > tr").children];
|
|
1923
|
+
while (colgroup.children.length < allHeaders.length) colgroup.append(document.createElement("col"));
|
|
1924
|
+
for (const node of grid.querySelectorAll("[data-bevel-field][data-grid-column]")) {
|
|
1925
|
+
const index = allHeaders.findIndex(header => header.dataset.bevelColumn === node.dataset.bevelField);
|
|
1926
|
+
if (index >= 0) attr(node, "data-grid-column", index);
|
|
1927
|
+
}
|
|
1928
|
+
let left = 0, visibleCount = 0;
|
|
1929
|
+
for (const [index, header] of allHeaders.entries()) {
|
|
1930
|
+
const key = header.dataset.bevelColumn, col = colgroup.children[index];
|
|
1931
|
+
if (!key) continue;
|
|
1932
|
+
const item = arranged.find(node => node.dataset.bevelColumn === key), required = item?.hasAttribute("data-bevel-column-required");
|
|
1933
|
+
if (required) { flag(header, "hidden", false); flag(header, "data-bevel-pinned", true); }
|
|
1934
|
+
const visible = !header.hidden, pinned = header.hasAttribute("data-bevel-pinned"), width = header.dataset.bevelWidth;
|
|
1935
|
+
attr(col, "data-bevel-column", key); style(col, "width", visible ? width ? `${Number(width)}px` : "" : "0px");
|
|
1936
|
+
for (const cell of table.querySelectorAll(`[data-bevel-column="${CSS.escape(key)}"]`)) {
|
|
1937
|
+
if (cell.tagName === "COL") continue;
|
|
1938
|
+
flag(cell, "hidden", !visible); flag(cell, "data-bevel-pinned", pinned);
|
|
1939
|
+
style(cell, "width", width ? `${Number(width)}px` : "");
|
|
1940
|
+
style(cell, "left", pinned && visible ? `${left}px` : "");
|
|
1941
|
+
}
|
|
1942
|
+
if (visible) { visibleCount++; if (pinned) left += header.getBoundingClientRect().width; }
|
|
1943
|
+
const input = item?.querySelector("[data-bevel-column-visible]");
|
|
1944
|
+
if (input) { if (input.checked !== visible) input.checked = visible; flag(input, "checked", visible); if (required) input.disabled = true; }
|
|
1945
|
+
const number = item?.querySelector("[data-bevel-column-position]");
|
|
1946
|
+
if (number && number.textContent !== String(index + 1)) number.textContent = String(index + 1);
|
|
1947
|
+
const up = item?.querySelector('[data-bevel-column-move="up"]'), down = item?.querySelector('[data-bevel-column-move="down"]');
|
|
1948
|
+
if (up) up.disabled = Boolean(required || index === 0 || arranged[index - 1]?.hasAttribute("data-bevel-column-required"));
|
|
1949
|
+
if (down) down.disabled = Boolean(required || index === allHeaders.length - 1);
|
|
1950
|
+
const pin = item?.querySelector("[data-bevel-column-pin]"); if (pin) { attr(pin, "aria-pressed", pinned); pin.disabled = Boolean(required); }
|
|
1951
|
+
const handle = header.querySelector(".bevel-grid__resize");
|
|
1952
|
+
if (handle) { attr(handle, "aria-valuemin", Number(header.dataset.columnMin) || 80); attr(handle, "aria-valuemax", Number(header.dataset.columnMax) || 800); attr(handle, "aria-valuenow", Number(width) || Math.round(header.getBoundingClientRect().width)); }
|
|
1953
|
+
}
|
|
1954
|
+
left = 0;
|
|
1955
|
+
for (const header of headers) {
|
|
1956
|
+
const pinned = header.hasAttribute("data-bevel-pinned") && !header.hidden;
|
|
1957
|
+
for (const cell of table.querySelectorAll(`[data-bevel-column="${CSS.escape(header.dataset.bevelColumn)}"]`)) if (cell.tagName !== "COL") style(cell, "left", pinned ? `${left}px` : "");
|
|
1958
|
+
if (pinned) left += header.getBoundingClientRect().width;
|
|
1959
|
+
}
|
|
1960
|
+
const span = allHeaders.filter(header => !header.hidden).length;
|
|
1961
|
+
for (const cell of table.querySelectorAll(".bevel-grid__detail > td,.bevel-grid__group > td,.bevel-grid__empty > td")) if (cell.colSpan !== span) cell.colSpan = span;
|
|
1962
|
+
const summary = manager.closest(".bevel-columns-panel")?.querySelector("[data-bevel-column-summary]"), text = `${visibleCount} of ${headers.length} shown`;
|
|
1963
|
+
if (summary && summary.textContent !== text) summary.textContent = text;
|
|
1964
|
+
attr(grid, "data-bevel-column-order", order.join(" "));
|
|
1965
|
+
layoutGrid(grid);
|
|
1966
|
+
};
|
|
1967
|
+
on("click", ".bevel-columns [data-bevel-column-move]", (event, button) => {
|
|
1968
|
+
if (disabled(button)) return;
|
|
1969
|
+
const manager = button.closest(".bevel-columns"), item = button.closest(".bevel-columns__item"), up = button.dataset.bevelColumnMove === "up", sibling = up ? item.previousElementSibling : item.nextElementSibling;
|
|
1970
|
+
if (!sibling || item.hasAttribute("data-bevel-column-required") || sibling.hasAttribute("data-bevel-column-required")) return;
|
|
1971
|
+
transition(manager, "column-order", item.dataset.bevelColumn, [...manager.children].indexOf(item), [...manager.children].indexOf(sibling), () => orderColumn(manager, item.dataset.bevelColumn, sibling.dataset.bevelColumn, up));
|
|
1972
|
+
});
|
|
1973
|
+
on("change", ".bevel-columns [data-bevel-column-visible]", (event, input) => {
|
|
1974
|
+
const manager = input.closest(".bevel-columns"), item = input.closest(".bevel-columns__item"), grid = targetById(manager.dataset.bevelGrid), header = columnHeaders(grid).find(node => node.dataset.bevelColumn === item.dataset.bevelColumn), checked = input.checked, previous = !header.hidden;
|
|
1975
|
+
const remaining = columnHeaders(grid).filter(node => !node.hidden && node !== header && !manager.querySelector(`[data-bevel-column="${CSS.escape(node.dataset.bevelColumn)}"]`)?.hasAttribute("data-bevel-column-required"));
|
|
1976
|
+
if (item.hasAttribute("data-bevel-column-required") || (!checked && !remaining.length) || !transition(manager, "column-visibility", item.dataset.bevelColumn, previous, checked, () => { header.hidden = !checked; refreshColumns(manager); })) {
|
|
1977
|
+
input.checked = previous; input.toggleAttribute("checked", previous);
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1980
|
+
on("click", ".bevel-columns [data-bevel-column-pin]", (event, button) => {
|
|
1981
|
+
if (disabled(button)) return;
|
|
1982
|
+
const manager = button.closest(".bevel-columns"), item = button.closest(".bevel-columns__item"), grid = targetById(manager.dataset.bevelGrid), header = columnHeaders(grid).find(node => node.dataset.bevelColumn === item.dataset.bevelColumn);
|
|
1983
|
+
if (item.hasAttribute("data-bevel-column-required")) return;
|
|
1984
|
+
const previous = header.hasAttribute("data-bevel-pinned");
|
|
1985
|
+
transition(manager, "column-pin", item.dataset.bevelColumn, previous, !previous, () => { header.toggleAttribute("data-bevel-pinned", !previous); refreshColumns(manager); });
|
|
1986
|
+
});
|
|
1987
|
+
const resizeColumn=(handle,width)=>{const header=handle.closest("th"),grid=gridOf(header),min=Number(header.dataset.columnMin)||80,max=Number(header.dataset.columnMax)||800,next=Math.min(max,Math.max(min,width)),previous=Number(header.dataset.bevelWidth)||Math.round(header.getBoundingClientRect().width); return transition(grid,"column-resize",header.dataset.bevelColumn,previous,next,()=>{header.dataset.bevelWidth=String(Math.round(next));handle.setAttribute("aria-valuenow",String(Math.round(next)));const manager=document.querySelector(`.bevel-columns[data-bevel-grid="${CSS.escape(grid.id)}"]`);if(manager)refreshColumns(manager);});};
|
|
1988
|
+
on("keydown", ".bevel-grid__resize",(e,handle)=>{if(!["ArrowLeft","ArrowRight","Home","End"].includes(e.key))return;e.preventDefault();const header=handle.closest("th"),min=Number(header.dataset.columnMin)||80,max=Number(header.dataset.columnMax)||800,current=Number(header.dataset.bevelWidth)||Math.round(header.getBoundingClientRect().width),step=e.shiftKey?32:8;resizeColumn(handle,e.key==="Home"?min:e.key==="End"?max:current+(e.key==="ArrowRight"?step:-step));});
|
|
1989
|
+
on("pointerdown", ".bevel-grid__resize",(e,handle)=>{if(e.button!==0||sessions.has(handle))return;const header=handle.closest("th"),start=e.clientX,pointerId=e.pointerId,previous=Number(header.dataset.bevelWidth)||Math.round(header.getBoundingClientRect().width);e.preventDefault();handle.setPointerCapture(pointerId);const session={current:previous,cancel:()=>stop(false)},move=current=>{if(current.pointerId===pointerId){session.current=Math.min(Number(header.dataset.columnMax)||800,Math.max(Number(header.dataset.columnMin)||80,previous+current.clientX-start));header.style.width=`${session.current}px`;handle.setAttribute("aria-valuenow",String(Math.round(session.current)));}},stop=commit=>{if(!sessions.has(handle))return;handle.removeEventListener("pointermove",move);handle.removeEventListener("pointerup",up);handle.removeEventListener("pointercancel",cancel);handle.removeEventListener("lostpointercapture",lost);document.removeEventListener("keydown",escape,true);header.style.width="";handle.setAttribute("aria-valuenow",String(previous));sessions.delete(handle);if(handle.hasPointerCapture(pointerId))handle.releasePointerCapture(pointerId);if(commit&&handle.isConnected)resizeColumn(handle,session.current);},up=current=>{if(current.pointerId===pointerId)stop(true);},cancel=current=>{if(current.pointerId===pointerId)stop(false);},lost=()=>stop(false),escape=current=>{if(current.key==="Escape"){current.preventDefault();stop(false);}};sessions.set(handle,session);handle.addEventListener("pointermove",move);handle.addEventListener("pointerup",up);handle.addEventListener("pointercancel",cancel);handle.addEventListener("lostpointercapture",lost);document.addEventListener("keydown",escape,true);});
|
|
1990
|
+
on("pointerdown", ".bevel-columns__grip",(e,grip)=>{if(e.button!==0||sessions.has(grip))return;const item=grip.closest(".bevel-columns__item"),manager=grip.closest(".bevel-columns");if(item.hasAttribute("data-bevel-column-required"))return;const pointerId=e.pointerId,previous=[...manager.children].indexOf(item),session={target:null,before:false,cancel:()=>stop(false)},clear=()=>{session.target?.removeAttribute("data-bevel-drop-target");session.target=null;},move=current=>{if(current.pointerId!==pointerId)return;const target=[...manager.querySelectorAll(":scope > .bevel-columns__item")].find(node=>{const rect=node.getBoundingClientRect();return current.clientY>=rect.top&¤t.clientY<=rect.bottom;});clear();if(target&&target!==item&&!target.hasAttribute("data-bevel-column-required")){session.target=target;const rect=target.getBoundingClientRect();session.before=current.clientY<rect.top+rect.height/2;target.dataset.bevelDropTarget=session.before?"before":"after";}},stop=commit=>{if(!sessions.has(grip))return;document.removeEventListener("pointermove",move);document.removeEventListener("pointerup",up);document.removeEventListener("pointercancel",cancel);document.removeEventListener("keydown",escape,true);sessions.delete(grip);const target=session.target,before=session.before;clear();if(commit&&grip.isConnected&&target?.isConnected){const children=[...manager.children],targetIndex=children.indexOf(target)+(before?0:1),next=targetIndex>(children.indexOf(item))?targetIndex-1:targetIndex;if(next!==previous)transition(manager,"column-order",item.dataset.bevelColumn,previous,next,()=>orderColumn(manager,item.dataset.bevelColumn,target.dataset.bevelColumn,before));}},up=current=>{if(current.pointerId===pointerId)stop(true);},cancel=current=>{if(current.pointerId===pointerId)stop(false);},escape=current=>{if(current.key==="Escape"){current.preventDefault();stop(false);}};e.preventDefault();sessions.set(grip,session);document.addEventListener("pointermove",move);document.addEventListener("pointerup",up);document.addEventListener("pointercancel",cancel);document.addEventListener("keydown",escape,true);});
|
|
1991
|
+
on("keydown", ".bevel-split__divider", (event, divider) => {
|
|
1992
|
+
if (disabled(divider) || event.isComposing) return;
|
|
1993
|
+
const root = divider.closest(".bevel-split"), vertical = root.dataset.bevelOrientation === "vertical", forward = vertical ? "ArrowDown" : "ArrowRight", backward = vertical ? "ArrowUp" : "ArrowLeft";
|
|
1994
|
+
if (![forward, backward, "Home", "End"].includes(event.key)) return;
|
|
1995
|
+
event.preventDefault(); const [min, max] = splitBounds(root), step = event.shiftKey ? 10 : 2;
|
|
1996
|
+
const value = event.key === "Home" ? min : event.key === "End" ? max : splitClamped(root, splitValue(root) + (event.key === forward ? step : -step));
|
|
1997
|
+
commitSplit(root, Math.round(value * 100) / 100);
|
|
1998
|
+
});
|
|
1999
|
+
on("pointerdown", ".bevel-split__divider", (event, divider) => {
|
|
2000
|
+
if (event.button !== 0 || disabled(divider)) return;
|
|
2001
|
+
const root = divider.closest(".bevel-split"); refreshSplit(root);
|
|
2002
|
+
const session = splitSessions.get(root); if (session.drag || disabled(divider)) return;
|
|
2003
|
+
event.preventDefault(); divider.focus();
|
|
2004
|
+
const previous = splitValue(root), vertical = root.dataset.bevelOrientation === "vertical", extent = vertical ? root.clientHeight : root.clientWidth;
|
|
2005
|
+
const start = vertical ? event.clientY : event.clientX, pointerId = event.pointerId;
|
|
2006
|
+
const drag = { previous, orientation: root.dataset.bevelOrientation, current: splitClamped(root, previous), stop(commit) {
|
|
2007
|
+
if (session.drag !== drag) return;
|
|
2008
|
+
session.drag = null; root.removeAttribute("data-bevel-dragging");
|
|
2009
|
+
divider.removeEventListener("pointermove", move); divider.removeEventListener("pointerup", up); divider.removeEventListener("pointercancel", cancel); divider.removeEventListener("lostpointercapture", cancel); document.removeEventListener("keydown", escape, true);
|
|
2010
|
+
if (divider.hasPointerCapture(pointerId)) divider.releasePointerCapture(pointerId);
|
|
2011
|
+
if (commit && root.isConnected) commitSplit(root, Math.round(drag.current * 100) / 100);
|
|
2012
|
+
refreshSplit(root);
|
|
2013
|
+
} };
|
|
2014
|
+
const move = current => { if (current.pointerId !== pointerId) return; if (!root.isConnected || splitMedia.matches || disabled(divider)) return drag.stop(false); const offset = (vertical ? current.clientY : current.clientX) - start; drag.current = splitClamped(root, splitClamped(root, previous) + offset / extent * 100); root.style.setProperty("--bevel-split-size", `${drag.current}%`); divider.setAttribute("aria-valuenow", String(Math.round(drag.current * 100) / 100)); };
|
|
2015
|
+
const up = current => { if (current.pointerId === pointerId) drag.stop(true); };
|
|
2016
|
+
const cancel = current => { if (current.pointerId === pointerId) drag.stop(false); };
|
|
2017
|
+
const escape = current => { if (current.key === "Escape") { current.preventDefault(); current.stopPropagation(); drag.stop(false); } };
|
|
2018
|
+
session.drag = drag; root.dataset.bevelDragging = "";
|
|
2019
|
+
divider.addEventListener("pointermove", move); divider.addEventListener("pointerup", up); divider.addEventListener("pointercancel", cancel); divider.addEventListener("lostpointercapture", cancel); document.addEventListener("keydown", escape, true);
|
|
2020
|
+
divider.setPointerCapture(pointerId);
|
|
2021
|
+
});
|
|
2022
|
+
|
|
2023
|
+
const toastTimers = new Map(), toastReturns = new WeakMap();
|
|
2024
|
+
const toastStack = () => { let stack = document.querySelector(".bevel-toasts"); if (!stack) { stack = document.createElement("div"); stack.className = "bevel-toasts"; stack.setAttribute("aria-live", "polite"); document.body.append(stack); } return stack; };
|
|
2025
|
+
const restoreToastFocus = ({ previous, fallback } = {}) => { for (const target of [previous, fallback]) { if (!target?.isConnected || typeof target.focus !== "function") continue; target.focus(); if (document.activeElement === target) return; } };
|
|
2026
|
+
const dismissToast = toast => {
|
|
2027
|
+
const timer = toastTimers.get(toast); if (timer?.id) clearTimeout(timer.id); toastTimers.delete(toast);
|
|
2028
|
+
const focused = toast.contains(document.activeElement), next = toast.nextElementSibling || toast.previousElementSibling, entry = toastReturns.get(toast);
|
|
2029
|
+
toast.remove(); if (!focused) return; const target = next ? focusable(next) : null; target ? target.focus() : restoreToastFocus(entry);
|
|
2030
|
+
};
|
|
2031
|
+
const scheduleToast = (toast, delay) => {
|
|
2032
|
+
const timer = toastTimers.get(toast); if (!timer || delay <= 0) return;
|
|
2033
|
+
timer.remaining = delay; timer.started = performance.now(); timer.id = setTimeout(() => dismissToast(toast), delay);
|
|
2034
|
+
};
|
|
2035
|
+
const pauseToast = toast => { const timer = toastTimers.get(toast); if (!timer?.id) return; clearTimeout(timer.id); timer.id = null; timer.remaining = Math.max(0, timer.remaining - (performance.now() - timer.started)); };
|
|
2036
|
+
const resumeToast = toast => { const timer = toastTimers.get(toast); if (timer && !timer.id && !toast.matches(":hover") && !toast.contains(document.activeElement) && !document.hidden) scheduleToast(toast, timer.remaining); };
|
|
2037
|
+
const toastIcon = tone => tone === "success" ? '<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="square" stroke-linejoin="miter"><path d="m5 12 4 4L19 6"/></svg>' : tone === "error" ? '<svg aria-hidden="true" viewBox="0 0 256 256" fill="currentColor"><path d="M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM222.93,203.8a8.5,8.5,0,0,1-7.48,4.2H40.55a8.5,8.5,0,0,1-7.48-4.2,7.59,7.59,0,0,1,0-7.72L120.52,44.21a8.75,8.75,0,0,1,15,0l87.45,151.87A7.59,7.59,0,0,1,222.93,203.8ZM120,144V104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,180Z"/></svg>' : '<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="square" stroke-linejoin="miter"><path d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0M12 7.75v.85M10.3 11.15H12v5.1M10.3 16.25h3.4"/></svg>';
|
|
2038
|
+
const notify = ({ title = "Notice", message, tone = "info", key = crypto.randomUUID?.() || String(Date.now()), action: item, duration, focus = false, returnFocus } = {}) => {
|
|
2039
|
+
const stack = toastStack(), template = stack.querySelector("template[data-bevel-toast-template]"), toast = template?.content.firstElementChild?.cloneNode(true) || document.createElement("article"); if (!toast.classList.contains("bevel-toast")) toast.className = "bevel-toast"; toast.dataset.bevelTone = tone; toast.dataset.bevelKey = String(key); toast.dataset.bevelTransient = "toast";
|
|
2040
|
+
const holder = [...stack.children].find(node => toastReturns.has(node) && node.contains(document.activeElement)), inherited = holder && toastReturns.get(holder); toastReturns.set(toast, { previous: inherited ? inherited.previous : document.activeElement, fallback: returnFocus ?? inherited?.fallback });
|
|
2041
|
+
if (tone === "error") toast.setAttribute("role", "alert");
|
|
2042
|
+
if (focus) toast.setAttribute("aria-live", "off");
|
|
2043
|
+
if (!template) toast.innerHTML = '<span class="bevel-toast__icon"></span><div class="bevel-toast__body"><h2 class="bevel-toast__title"></h2><p class="bevel-toast__message"></p></div><button type="button" class="bevel-toast__close"></button>';
|
|
2044
|
+
toast.querySelector(".bevel-toast__icon").innerHTML = toastIcon(tone); const close = toast.querySelector(".bevel-toast__close"); close.innerHTML = '<svg aria-hidden="true" viewBox="0 0 256 256" fill="currentColor"><path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"/></svg>'; close.setAttribute("aria-label", `Dismiss ${title}`);
|
|
2045
|
+
toast.querySelector(".bevel-toast__title").textContent = title; toast.querySelector(".bevel-toast__message").textContent = message || "";
|
|
2046
|
+
let button = toast.querySelector(".bevel-toast__action"); if (item) { if (!button) { button = document.createElement("button"); button.type = "button"; button.className = "bevel-button bevel-button--small bevel-toast__action"; toast.querySelector(".bevel-toast__body").append(button); } button.replaceChildren(); const actionIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); actionIcon.setAttribute("aria-hidden", "true"); actionIcon.setAttribute("viewBox", "0 0 256 256"); actionIcon.setAttribute("fill", "currentColor"); actionIcon.innerHTML = '<path d="M224,128a96,96,0,0,1-94.71,96H128A95.38,95.38,0,0,1,62.1,197.8a8,8,0,0,1,11-11.63A80,80,0,1,0,71.43,71.39a3.07,3.07,0,0,1-.26.25L44.59,96H72a8,8,0,0,1,0,16H24a8,8,0,0,1-8-8V56a8,8,0,0,1,16,0V85.8L60.25,60A96,96,0,0,1,224,128Z"/>'; if ((item.icon || item.id) === "retry") actionIcon.style.transform = "scaleX(-1)"; const label = document.createElement("span"); label.className = "bevel-button__label"; label.textContent = item.label; button.append(actionIcon, label); button.addEventListener("click", () => { action(stack, item.id, key); dismissToast(toast); }, { once: true }); } else button?.remove();
|
|
2047
|
+
toast.querySelector(".bevel-toast__close").addEventListener("click", () => dismissToast(toast), { once: true });
|
|
2048
|
+
toast.addEventListener("mouseenter", () => pauseToast(toast)); toast.addEventListener("mouseleave", () => resumeToast(toast)); toast.addEventListener("focusin", () => pauseToast(toast)); toast.addEventListener("focusout", () => queueMicrotask(() => resumeToast(toast)));
|
|
2049
|
+
stack.append(toast); const timeout = duration ?? (tone === "error" || item || focus ? 0 : 4000); if (timeout > 0) { toastTimers.set(toast, { remaining: timeout, id: null, started: 0 }); scheduleToast(toast, timeout); } if (focus) { toast.tabIndex = -1; toast.focus({ preventScroll: true }); } return toast;
|
|
2050
|
+
};
|
|
2051
|
+
document.addEventListener("visibilitychange", () => { for (const toast of toastTimers.keys()) document.hidden ? pauseToast(toast) : resumeToast(toast); });
|
|
2052
|
+
let savedTimer = null;
|
|
2053
|
+
const saved = ({ duration = 2000 } = {}) => { let chip = document.querySelector(".bevel-saved"); if (!chip) { chip = document.createElement("div"); chip.className = "bevel-saved"; chip.dataset.bevelTransient = "saved"; chip.setAttribute("role", "status"); chip.setAttribute("aria-live", "polite"); chip.textContent = "Saved"; document.body.append(chip); } if (savedTimer) clearTimeout(savedTimer); chip.hidden = false; savedTimer = duration > 0 ? setTimeout(() => { chip.hidden = true; savedTimer = null; }, duration) : null; return chip; };
|
|
2054
|
+
const upload = {
|
|
2055
|
+
update(root, key, state) {
|
|
2056
|
+
const row = root?.querySelector(`[data-bevel-file-key="${CSS.escape(String(key))}"]`);
|
|
2057
|
+
if (!row) return false;
|
|
2058
|
+
const next = { ...state }, allowed = new Set(["selected", "uploading", "uploaded", "error"]);
|
|
2059
|
+
if (next.status && !allowed.has(next.status)) return false;
|
|
2060
|
+
if ("progress" in next) next.progress = Math.min(100, Math.max(0, Number(next.progress) || 0));
|
|
2061
|
+
const candidateStatus = next.status || row.dataset.bevelStatus;
|
|
2062
|
+
const candidateUrl = "url" in next ? next.url : row.dataset.bevelUrl;
|
|
2063
|
+
const candidateError = "error" in next ? next.error : row.dataset.bevelError;
|
|
2064
|
+
if (candidateStatus === "uploaded") {
|
|
2065
|
+
let protocol; try { protocol = new URL(candidateUrl, document.baseURI).protocol; } catch { return false; }
|
|
2066
|
+
if (!candidateUrl || !["http:", "https:", "data:"].includes(protocol)) return false;
|
|
2067
|
+
}
|
|
2068
|
+
for (const name of ["status", "progress", "url", "name", "error"]) if (name in next) { const value = next[name]; if (value == null || value === "") row.removeAttribute(`data-bevel-${name}`); else row.setAttribute(`data-bevel-${name}`, String(value)); }
|
|
2069
|
+
const progress = row.querySelector("progress");
|
|
2070
|
+
if (progress && "progress" in next) progress.value = next.progress;
|
|
2071
|
+
const stateMark = row.querySelector(".bevel-upload__state");
|
|
2072
|
+
if (stateMark) stateMark.textContent = candidateStatus === "uploading" ? `${"progress" in next ? next.progress : row.dataset.bevelProgress || 0}%` : candidateStatus === "uploaded" ? "✓" : candidateStatus === "error" ? "Failed" : "";
|
|
2073
|
+
const status = row.querySelector(".bevel-upload__status");
|
|
2074
|
+
if (status && ("status" in next || "error" in next)) status.textContent = candidateError || (candidateStatus === "uploaded" ? "Uploaded" : candidateStatus[0].toUpperCase() + candidateStatus.slice(1));
|
|
2075
|
+
const name = row.querySelector(".bevel-upload__name");
|
|
2076
|
+
if (name && next.name) name.textContent = next.name;
|
|
2077
|
+
const retry = row.querySelector("[data-bevel-upload-retry]");
|
|
2078
|
+
if (retry) retry.hidden = candidateStatus !== "error" || !uploadFiles.get(root)?.has(String(key));
|
|
2079
|
+
let link = row.querySelector(".bevel-upload__link");
|
|
2080
|
+
if (candidateStatus === "uploaded" && ("status" in next || "url" in next)) {
|
|
2081
|
+
if (!link) { link = document.createElement("a"); link.className = "bevel-upload__link"; link.textContent = "Open file"; row.querySelector(".bevel-upload__actions")?.prepend(link); }
|
|
2082
|
+
link.href = candidateUrl; link.rel = "noopener noreferrer"; if (root.hasAttribute("data-bevel-download")) link.download = next.name || row.dataset.bevelName || "";
|
|
2083
|
+
const entry = uploadFiles.get(root)?.get(String(key)); if (entry?.preview) URL.revokeObjectURL(entry.preview); uploadFiles.get(root)?.delete(String(key));
|
|
2084
|
+
const preview = row.querySelector(".bevel-upload__preview img"); if (preview?.src.startsWith("blob:")) { preview.removeAttribute("src"); preview.hidden = true; }
|
|
2085
|
+
}
|
|
2086
|
+
return true;
|
|
2087
|
+
},
|
|
2088
|
+
remove(root, key) {
|
|
2089
|
+
const id = String(key), row = root?.querySelector(`[data-bevel-file-key="${CSS.escape(id)}"]`);
|
|
2090
|
+
if (!row) return false;
|
|
2091
|
+
const files = uploadFiles.get(root), entry = files?.get(id);
|
|
2092
|
+
if (entry?.preview) URL.revokeObjectURL(entry.preview);
|
|
2093
|
+
files?.delete(id); row.remove(); action(root, "remove", id); return true;
|
|
2094
|
+
}
|
|
2095
|
+
};
|
|
2096
|
+
const edit = {
|
|
2097
|
+
start: startEdit,
|
|
2098
|
+
commit(root) { return endEdit(root, true); },
|
|
2099
|
+
cancel(root) { return endEdit(root, false); }
|
|
2100
|
+
};
|
|
2101
|
+
const remote = { resolve(root, requestId, { status, message = "" }) { if (!root?.isConnected || !["accepted", "rejected"].includes(status)) return false; const scopes = requestScopes.get(root); if (!scopes || ![...scopes.values()].includes(requestId)) return false; for (const [scope, id] of scopes) if (id === requestId) scopes.delete(scope); if (!scopes.size) root.removeAttribute("data-bevel-pending"); root.dataset.bevelResolution = status; if (message) root.dataset.bevelMessage = message; return true; } };
|
|
2102
|
+
window.Bevel = { open: openOverlay, close: closeOverlay, refresh, destroy, prepareForSave, notify, saved, upload, edit, filter:{ read:filterRead, matches:filterMatches }, remote, _transition: transition, _request: remoteRequest };
|
|
2103
|
+
|
|
2104
|
+
/* ---------- first paint: fill any slider whose markup forgot --bevel-value ---------- */
|
|
2105
|
+
const boot = () => {
|
|
2106
|
+
document.querySelectorAll("[data-bevel-transient]").forEach((el) => el.remove());
|
|
2107
|
+
clearGridDrafts(document);
|
|
2108
|
+
clearKanbanDrafts(document);
|
|
2109
|
+
clearPickerDrafts(document);
|
|
2110
|
+
clearInlineDrafts(document);
|
|
2111
|
+
for (const grid of document.querySelectorAll(".bevel-grid")) if (grid.dataset.bevelDataMode !== "remote") layoutGrid(grid);
|
|
2112
|
+
for (const input of document.querySelectorAll(".bevel-slider__input")) {
|
|
2113
|
+
const wrap = input.closest(".bevel-slider");
|
|
2114
|
+
if (wrap && !wrap.style.getPropertyValue("--bevel-value")) syncSlider(input);
|
|
2115
|
+
}
|
|
2116
|
+
for (const name of document.querySelectorAll(".bevel-file__name[data-filled][data-empty]")) {
|
|
2117
|
+
const wrap = name.closest(".bevel-file");
|
|
2118
|
+
const input = wrap?.querySelector(".bevel-file__input");
|
|
2119
|
+
if (input && !input.files.length) clearFile(wrap, name);
|
|
2120
|
+
}
|
|
2121
|
+
refresh(document, { source: "boot" });
|
|
2122
|
+
expirePendingUploads(document);
|
|
2123
|
+
window.addEventListener("beforeunload", () => { for (const root of document.querySelectorAll(".bevel-dropzone")) for (const entry of uploadFiles.get(root)?.values() || []) if (entry.preview) URL.revokeObjectURL(entry.preview); }, { once: true });
|
|
2124
|
+
};
|
|
2125
|
+
document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", boot) : boot();
|
|
2126
|
+
})();
|