@livenetworks/ashlar 1.6.1 → 2.0.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 +641 -0
- package/README.md +202 -86
- package/{js → components}/COMPONENTS.md +1116 -1108
- package/{js → components}/index.js +81 -58
- package/{js → components}/ln-accordion/README.md +7 -6
- package/components/ln-accordion/ln-accordion-dev.scss +8 -0
- package/components/ln-accordion/ln-accordion.js +1 -0
- package/components/ln-accordion/ln-accordion.schema.json +30 -0
- package/components/ln-accordion/ln-accordion.scss +8 -0
- package/{js → components}/ln-accordion/src/ln-accordion.js +8 -2
- package/{js → components}/ln-ajax/README.md +171 -160
- package/components/ln-ajax/ln-ajax-dev.scss +8 -0
- package/components/ln-ajax/ln-ajax.js +1 -0
- package/components/ln-ajax/ln-ajax.schema.json +30 -0
- package/{js → components}/ln-ajax/src/ln-ajax.js +65 -30
- package/{js → components}/ln-api-connector/README.md +27 -5
- package/components/ln-api-connector/ln-api-connector.js +1 -0
- package/components/ln-api-connector/ln-api-connector.schema.json +96 -0
- package/components/ln-api-connector/src/connector-core.js +97 -0
- package/components/ln-api-connector/src/ln-api-connector.js +447 -0
- package/{js → components}/ln-api-queue/README.md +12 -9
- package/components/ln-api-queue/ln-api-queue.js +1 -0
- package/components/ln-api-queue/ln-api-queue.schema.json +34 -0
- package/{js → components}/ln-api-queue/src/ln-api-queue.js +33 -14
- package/{js → components}/ln-api-queue/src/queue-storage.js +7 -3
- package/{js → components}/ln-autoresize/README.md +129 -127
- package/components/ln-autoresize/ln-autoresize-dev.scss +8 -0
- package/components/ln-autoresize/ln-autoresize.js +1 -0
- package/components/ln-autoresize/ln-autoresize.schema.json +22 -0
- package/{js → components}/ln-autoresize/src/ln-autoresize.js +8 -1
- package/{js → components}/ln-autosave/README.md +127 -124
- package/components/ln-autosave/ln-autosave-dev.scss +13 -0
- package/components/ln-autosave/ln-autosave.js +1 -0
- package/components/ln-autosave/ln-autosave.schema.json +48 -0
- package/components/ln-autosave/src/autosave-model.js +34 -0
- package/{js → components}/ln-autosave/src/ln-autosave.js +65 -58
- package/components/ln-chart/README.md +145 -0
- package/components/ln-chart/ln-chart-dev.scss +20 -0
- package/components/ln-chart/ln-chart.js +1 -0
- package/components/ln-chart/ln-chart.schema.json +146 -0
- package/components/ln-chart/ln-chart.scss +8 -0
- package/components/ln-chart/src/chart-model.js +154 -0
- package/components/ln-chart/src/ln-chart.js +176 -0
- package/{js → components}/ln-circular-progress/README.md +15 -14
- package/components/ln-circular-progress/ln-circular-progress-dev.scss +8 -0
- package/components/ln-circular-progress/ln-circular-progress.js +1 -0
- package/components/ln-circular-progress/ln-circular-progress.schema.json +42 -0
- package/{js → components}/ln-circular-progress/src/ln-circular-progress.js +32 -50
- package/{js → components}/ln-confirm/README.md +69 -18
- package/components/ln-confirm/ln-confirm-dev.scss +24 -0
- package/components/ln-confirm/ln-confirm.js +1 -0
- package/components/ln-confirm/ln-confirm.schema.json +66 -0
- package/components/ln-confirm/ln-confirm.scss +24 -0
- package/{js → components}/ln-confirm/src/ln-confirm.js +180 -171
- package/{js → components}/ln-core/README.md +374 -149
- package/components/ln-core/attrs.js +273 -0
- package/components/ln-core/compare.js +40 -0
- package/components/ln-core/crypto.js +265 -0
- package/components/ln-core/date.js +50 -0
- package/components/ln-core/dom.js +115 -0
- package/components/ln-core/events.js +122 -0
- package/components/ln-core/form.js +136 -0
- package/{js → components}/ln-core/hash.js +149 -92
- package/components/ln-core/helpers.js +33 -0
- package/components/ln-core/http.js +48 -0
- package/components/ln-core/index.js +11 -0
- package/components/ln-core/lifecycle.js +403 -0
- package/components/ln-core/ln-core.schema.json +109 -0
- package/components/ln-core/locale.js +55 -0
- package/components/ln-core/matching.js +78 -0
- package/components/ln-core/number.js +125 -0
- package/components/ln-core/progress.js +30 -0
- package/components/ln-core/template.js +206 -0
- package/{js → components}/ln-core/window-cache.js +247 -213
- package/{js → components}/ln-couchdb-connector/README.md +7 -5
- package/components/ln-couchdb-connector/ln-couchdb-connector.js +1 -0
- package/components/ln-couchdb-connector/ln-couchdb-connector.schema.json +50 -0
- package/{js → components}/ln-couchdb-connector/src/ln-couchdb-connector.js +29 -43
- package/{js → components}/ln-data-coordinator/README.md +109 -73
- package/components/ln-data-coordinator/ln-data-coordinator-dev.scss +8 -0
- package/components/ln-data-coordinator/ln-data-coordinator.js +1 -0
- package/components/ln-data-coordinator/ln-data-coordinator.schema.json +196 -0
- package/components/ln-data-coordinator/src/data-read-policy.js +41 -0
- package/{js → components}/ln-data-coordinator/src/ln-data-coordinator.js +411 -154
- package/{js → components}/ln-data-coordinator/src/mutation-receipts.js +0 -5
- package/{js → components}/ln-data-store/README.md +42 -17
- package/components/ln-data-store/ln-data-store-dev.scss +13 -0
- package/components/ln-data-store/ln-data-store.js +1 -0
- package/components/ln-data-store/ln-data-store.schema.json +84 -0
- package/components/ln-data-store/src/data-store-model.js +155 -0
- package/{js → components}/ln-data-store/src/ln-data-store.js +407 -105
- package/components/ln-data-store/src/window-index.js +119 -0
- package/{js → components}/ln-date/README.md +17 -7
- package/{js → components}/ln-date/ln-date-dev.scss +14 -14
- package/components/ln-date/ln-date.js +1 -0
- package/components/ln-date/ln-date.schema.json +92 -0
- package/components/ln-date/src/date-model.js +186 -0
- package/components/ln-date/src/ln-date.js +486 -0
- package/components/ln-debug/README.md +132 -0
- package/components/ln-debug/ln-debug.js +1 -0
- package/components/ln-debug/ln-debug.schema.json +28 -0
- package/components/ln-debug/src/console-sink.js +19 -0
- package/components/ln-debug/src/debug-verifier.js +331 -0
- package/components/ln-debug/src/gate.js +95 -0
- package/components/ln-debug/src/generated-attributes.js +299 -0
- package/components/ln-debug/src/ln-debug.js +60 -0
- package/{js → components}/ln-dropdown/README.md +150 -140
- package/components/ln-dropdown/ln-dropdown-dev.scss +13 -0
- package/components/ln-dropdown/ln-dropdown.js +1 -0
- package/components/ln-dropdown/ln-dropdown.schema.json +90 -0
- package/components/ln-dropdown/ln-dropdown.scss +18 -0
- package/{js → components}/ln-dropdown/src/ln-dropdown.js +114 -11
- package/{js → components}/ln-editor/README.md +11 -9
- package/components/ln-editor/ln-editor-dev.scss +18 -0
- package/components/ln-editor/ln-editor.js +1 -0
- package/components/ln-editor/ln-editor.schema.json +58 -0
- package/components/ln-editor/ln-editor.scss +13 -0
- package/{js → components}/ln-editor/src/ln-editor.js +59 -11
- package/{js → components}/ln-external-links/README.md +10 -8
- package/components/ln-external-links/ln-external-links.js +1 -0
- package/components/ln-external-links/ln-external-links.schema.json +18 -0
- package/{js → components}/ln-external-links/src/ln-external-links.js +13 -11
- package/{js → components}/ln-fill/README.md +6 -8
- package/components/ln-fill/ln-fill.js +1 -0
- package/components/ln-fill/ln-fill.schema.json +23 -0
- package/{js → components}/ln-fill/src/ln-fill.js +7 -1
- package/components/ln-filter/README.md +131 -0
- package/components/ln-filter/ln-filter-dev.scss +13 -0
- package/components/ln-filter/ln-filter.js +1 -0
- package/components/ln-filter/ln-filter.schema.json +153 -0
- package/components/ln-filter/src/filter-model.js +185 -0
- package/components/ln-filter/src/ln-filter.js +435 -0
- package/{js → components}/ln-form/README.md +260 -264
- package/components/ln-form/ln-form-dev.scss +8 -0
- package/components/ln-form/ln-form.js +1 -0
- package/components/ln-form/ln-form.schema.json +45 -0
- package/{js → components}/ln-form/src/ln-form.js +107 -99
- package/{js → components}/ln-http/README.md +11 -6
- package/components/ln-http/ln-http.js +1 -0
- package/components/ln-http/ln-http.schema.json +6 -0
- package/components/ln-http/src/http-core.js +51 -0
- package/{js → components}/ln-http/src/ln-http.js +28 -33
- package/{js → components}/ln-icon/README.md +16 -9
- package/components/ln-icon/ln-icon.schema.json +6 -0
- package/components/ln-include/README.md +75 -0
- package/components/ln-include/ln-include-dev.scss +13 -0
- package/components/ln-include/ln-include.js +1 -0
- package/components/ln-include/ln-include.schema.json +23 -0
- package/components/ln-include/src/ln-include.js +111 -0
- package/components/ln-key/README.md +130 -0
- package/components/ln-key/ln-key-dev.scss +18 -0
- package/components/ln-key/ln-key.js +1 -0
- package/components/ln-key/ln-key.schema.json +56 -0
- package/components/ln-key/src/key-model.js +146 -0
- package/components/ln-key/src/ln-key.js +201 -0
- package/{js → components}/ln-link/README.md +7 -9
- package/components/ln-link/ln-link-dev.scss +8 -0
- package/components/ln-link/ln-link.js +1 -0
- package/components/ln-link/ln-link.schema.json +22 -0
- package/{js → components}/ln-link/src/ln-link.js +16 -11
- package/components/ln-list/README.md +152 -0
- package/components/ln-list/ln-list-dev.scss +8 -0
- package/components/ln-list/ln-list.js +1 -0
- package/components/ln-list/ln-list.schema.json +166 -0
- package/components/ln-list/ln-list.scss +25 -0
- package/{js → components}/ln-list/src/ln-list.js +1301 -1216
- package/{js → components}/ln-modal/README.md +13 -10
- package/components/ln-modal/ln-modal-dev.scss +18 -0
- package/components/ln-modal/ln-modal.js +1 -0
- package/components/ln-modal/ln-modal.schema.json +54 -0
- package/components/ln-modal/ln-modal.scss +51 -0
- package/{js → components}/ln-modal/src/ln-modal.js +164 -137
- package/{js → components}/ln-nav/README.md +11 -11
- package/components/ln-nav/ln-nav.js +1 -0
- package/components/ln-nav/ln-nav.schema.json +25 -0
- package/{js → components}/ln-nav/src/ln-nav.js +25 -14
- package/components/ln-number/README.md +205 -0
- package/{js → components}/ln-number/ln-number-dev.scss +13 -13
- package/components/ln-number/ln-number.js +1 -0
- package/components/ln-number/ln-number.schema.json +63 -0
- package/components/ln-number/src/ln-number.js +400 -0
- package/components/ln-number/src/number-model.js +31 -0
- package/components/ln-obfuscator/README.md +60 -0
- package/components/ln-obfuscator/ln-obfuscator.js +1 -0
- package/components/ln-obfuscator/ln-obfuscator.schema.json +49 -0
- package/components/ln-obfuscator/src/ln-obfuscator.js +159 -0
- package/components/ln-obfuscator/src/obfuscator-model.js +250 -0
- package/{js → components}/ln-options/README.md +3 -1
- package/components/ln-options/ln-options-dev.scss +13 -0
- package/components/ln-options/ln-options.js +1 -0
- package/components/ln-options/ln-options.schema.json +41 -0
- package/{js → components}/ln-options/src/ln-options.js +13 -5
- package/components/ln-persist/README.md +151 -0
- package/components/ln-persist/ln-persist.js +1 -0
- package/components/ln-persist/ln-persist.schema.json +28 -0
- package/components/ln-persist/src/ln-persist.js +83 -0
- package/components/ln-picklist/README.md +230 -0
- package/components/ln-picklist/ln-picklist-dev.scss +22 -0
- package/components/ln-picklist/ln-picklist.js +1 -0
- package/components/ln-picklist/ln-picklist.schema.json +49 -0
- package/components/ln-picklist/src/ln-picklist.js +228 -0
- package/{js → components}/ln-popover/README.md +164 -162
- package/components/ln-popover/ln-popover-dev.scss +13 -0
- package/components/ln-popover/ln-popover.js +1 -0
- package/components/ln-popover/ln-popover.schema.json +82 -0
- package/components/ln-popover/ln-popover.scss +18 -0
- package/{js → components}/ln-popover/src/ln-popover.js +42 -36
- package/{js → components}/ln-progress/README.md +410 -413
- package/components/ln-progress/ln-progress-dev.scss +8 -0
- package/components/ln-progress/ln-progress.js +1 -0
- package/components/ln-progress/ln-progress.schema.json +40 -0
- package/components/ln-progress/src/ln-progress.js +91 -0
- package/components/ln-progress/src/progress-model.js +20 -0
- package/{js → components}/ln-router/README.md +443 -432
- package/components/ln-router/ln-router-dev.scss +13 -0
- package/components/ln-router/ln-router.js +1 -0
- package/components/ln-router/ln-router.schema.json +68 -0
- package/{js → components}/ln-router/src/ln-router.js +585 -539
- package/components/ln-router/src/router-model.js +50 -0
- package/components/ln-scroll/README.md +45 -0
- package/components/ln-scroll/ln-scroll.js +1 -0
- package/components/ln-scroll/ln-scroll.schema.json +85 -0
- package/components/ln-scroll/src/ln-scroll.js +150 -0
- package/components/ln-search/README.md +188 -0
- package/components/ln-search/ln-search-dev.scss +13 -0
- package/components/ln-search/ln-search.js +1 -0
- package/components/ln-search/ln-search.schema.json +103 -0
- package/components/ln-search/src/ln-search.js +330 -0
- package/{js → components}/ln-slug/README.md +6 -3
- package/components/ln-slug/ln-slug-dev.scss +18 -0
- package/components/ln-slug/ln-slug.js +1 -0
- package/components/ln-slug/ln-slug.schema.json +23 -0
- package/{js → components}/ln-slug/src/ln-slug.js +20 -13
- package/components/ln-slug/src/slug-model.js +21 -0
- package/{js → components}/ln-sort/README.md +178 -170
- package/{js → components}/ln-sort/ln-sort-dev.scss +8 -8
- package/components/ln-sort/ln-sort.js +1 -0
- package/components/ln-sort/ln-sort.schema.json +104 -0
- package/components/ln-sort/src/ln-sort.js +321 -0
- package/components/ln-sort/src/sort-model.js +117 -0
- package/{js → components}/ln-sortable/README.md +7 -11
- package/components/ln-sortable/ln-sortable-dev.scss +13 -0
- package/components/ln-sortable/ln-sortable.js +1 -0
- package/components/ln-sortable/ln-sortable.schema.json +36 -0
- package/{js → components}/ln-sortable/src/ln-sortable.js +8 -13
- package/{js → components}/ln-stat/README.md +3 -1
- package/components/ln-stat/ln-stat-dev.scss +8 -0
- package/components/ln-stat/ln-stat.js +1 -0
- package/components/ln-stat/ln-stat.schema.json +32 -0
- package/{js → components}/ln-stat/src/ln-stat.js +14 -18
- package/components/ln-stat/src/stat-model.js +28 -0
- package/{js → components}/ln-table/README.md +37 -24
- package/{js → components}/ln-table/ln-table-dev.scss +17 -17
- package/components/ln-table/ln-table.js +1 -0
- package/components/ln-table/ln-table.schema.json +211 -0
- package/components/ln-table/ln-table.scss +15 -0
- package/{js → components}/ln-table/src/ln-table.js +1365 -1493
- package/components/ln-table/src/table-model.js +98 -0
- package/components/ln-table-coordinator/README.md +61 -0
- package/components/ln-table-coordinator/ln-table-coordinator.js +1 -0
- package/components/ln-table-coordinator/ln-table-coordinator.schema.json +93 -0
- package/components/ln-table-coordinator/src/ln-table-coordinator.js +193 -0
- package/{js → components}/ln-tabs/README.md +32 -14
- package/components/ln-tabs/ln-tabs-dev.scss +24 -0
- package/components/ln-tabs/ln-tabs.js +1 -0
- package/components/ln-tabs/ln-tabs.schema.json +75 -0
- package/components/ln-tabs/ln-tabs.scss +8 -0
- package/components/ln-tabs/src/ln-tabs.js +207 -0
- package/components/ln-tabs/src/tabs-model.js +82 -0
- package/{js → components}/ln-time/README.md +96 -95
- package/components/ln-time/ln-time-dev.scss +13 -0
- package/components/ln-time/ln-time.js +1 -0
- package/components/ln-time/ln-time.schema.json +38 -0
- package/{js → components}/ln-time/src/ln-time.js +206 -223
- package/components/ln-time/src/time-model.js +64 -0
- package/{js → components}/ln-toast/README.md +23 -5
- package/components/ln-toast/ln-toast-dev.scss +8 -0
- package/components/ln-toast/ln-toast.js +1 -0
- package/components/ln-toast/ln-toast.schema.json +65 -0
- package/components/ln-toast/ln-toast.scss +59 -0
- package/{js → components}/ln-toast/src/ln-toast.js +233 -232
- package/{js → components}/ln-toggle/README.md +198 -196
- package/{js → components}/ln-toggle/ln-toggle-dev.scss +13 -13
- package/components/ln-toggle/ln-toggle.js +1 -0
- package/components/ln-toggle/ln-toggle.schema.json +52 -0
- package/components/ln-toggle/ln-toggle.scss +37 -0
- package/{js → components}/ln-toggle/src/ln-toggle.js +61 -46
- package/{js → components}/ln-tooltip/README.md +6 -6
- package/{js → components}/ln-tooltip/ln-tooltip-dev.scss +9 -9
- package/components/ln-tooltip/ln-tooltip.js +1 -0
- package/components/ln-tooltip/ln-tooltip.schema.json +86 -0
- package/components/ln-tooltip/ln-tooltip.scss +9 -0
- package/{js → components}/ln-tooltip/src/ln-tooltip.js +14 -3
- package/{js → components}/ln-translations/README.md +3 -1
- package/components/ln-translations/ln-translations-dev.scss +13 -0
- package/components/ln-translations/ln-translations.js +1 -0
- package/components/ln-translations/ln-translations.schema.json +120 -0
- package/{js → components}/ln-translations/src/ln-translations.js +25 -10
- package/components/ln-ui-coordinator/README.md +101 -0
- package/components/ln-ui-coordinator/ln-ui-coordinator-dev.scss +8 -0
- package/components/ln-ui-coordinator/ln-ui-coordinator.js +1 -0
- package/components/ln-ui-coordinator/ln-ui-coordinator.schema.json +60 -0
- package/{js/ln-modal-coordinator/src/ln-modal-coordinator.js → components/ln-ui-coordinator/src/ln-ui-coordinator.js} +403 -345
- package/components/ln-upload/README.md +133 -0
- package/components/ln-upload/ln-upload-dev.scss +18 -0
- package/components/ln-upload/ln-upload.js +1 -0
- package/components/ln-upload/ln-upload.schema.json +193 -0
- package/components/ln-upload/ln-upload.scss +20 -0
- package/components/ln-upload/src/ln-upload.js +652 -0
- package/components/ln-upload/src/upload-model.js +78 -0
- package/{js → components}/ln-validate/README.md +182 -180
- package/{js → components}/ln-validate/ln-validate-dev.scss +15 -15
- package/components/ln-validate/ln-validate.js +1 -0
- package/components/ln-validate/ln-validate.schema.json +50 -0
- package/components/ln-validate/ln-validate.scss +7 -0
- package/{js → components}/ln-validate/src/ln-validate.js +23 -45
- package/components/ln-validate/src/validate-model.js +64 -0
- package/components/ln-websocket-connector/README.md +166 -0
- package/components/ln-websocket-connector/ln-websocket-connector.js +1 -0
- package/components/ln-websocket-connector/ln-websocket-connector.schema.json +29 -0
- package/components/ln-websocket-connector/src/ln-websocket-connector.js +279 -0
- package/ln-ashlar.html-data.json +1796 -0
- package/package.json +84 -61
- package/{scss → theme}/base/_global.scss +1 -2
- package/{scss → theme}/base/_reset.scss +1 -1
- package/{scss → theme}/base/_typography.scss +2 -0
- package/theme/brand.css +73 -0
- package/{scss → theme}/components/_app-shell.scss +8 -0
- package/theme/components/_button.scss +24 -0
- package/theme/components/_chart.scss +5 -0
- package/theme/components/_confirm.scss +6 -0
- package/{scss → theme}/components/_date.scss +30 -22
- package/{scss → theme}/components/_dropdown.scss +6 -1
- package/theme/components/_form.scss +140 -0
- package/{scss → theme}/components/_layout.scss +6 -0
- package/{scss → theme}/components/_ln-table.scss +1 -6
- package/theme/components/_picklist.scss +3 -0
- package/{scss → theme}/components/_sort.scss +10 -10
- package/theme/components/_status-badge.scss +6 -0
- package/{scss → theme}/components/_table.scss +16 -0
- package/{scss → theme}/components/_tabs.scss +0 -4
- package/theme/components/_toast.scss +48 -0
- package/{scss → theme}/components/_toggle.scss +1 -1
- package/theme/components/_upload.scss +45 -0
- package/{scss → theme}/config/_breakpoints.scss +1 -1
- package/theme/config/_contrast.scss +128 -0
- package/{scss → theme}/config/_density.scss +191 -205
- package/theme/config/_palette.scss +274 -0
- package/theme/config/_theme.scss +293 -0
- package/{scss → theme}/config/_tokens.scss +285 -441
- package/{scss → theme}/config/mixins/_accordion.scss +1 -1
- package/{scss → theme}/config/mixins/_alert.scss +1 -0
- package/{scss → theme}/config/mixins/_app-shell.scss +4 -2
- package/{scss → theme}/config/mixins/_borders.scss +2 -0
- package/theme/config/mixins/_btn.scss +175 -0
- package/{scss → theme}/config/mixins/_card.scss +55 -46
- package/theme/config/mixins/_chart.scss +82 -0
- package/{scss → theme}/config/mixins/_collapsible.scss +2 -1
- package/{scss → theme}/config/mixins/_colors.scss +8 -14
- package/{scss → theme}/config/mixins/_diagnostics.scss +48 -48
- package/theme/config/mixins/_dropdown.scss +169 -0
- package/{scss → theme}/config/mixins/_editor.scss +0 -7
- package/{scss → theme}/config/mixins/_filter.scss +47 -47
- package/{scss → theme}/config/mixins/_footer.scss +1 -1
- package/{scss → theme}/config/mixins/_form.scss +49 -18
- package/{scss → theme}/config/mixins/_index.scss +62 -61
- package/{scss → theme}/config/mixins/_layout.scss +6 -2
- package/{scss → theme}/config/mixins/_ln-table.scss +31 -10
- package/{scss → theme}/config/mixins/_loader.scss +1 -1
- package/{scss → theme}/config/mixins/_modal.scss +145 -149
- package/{scss → theme}/config/mixins/_motion.scss +13 -0
- package/{scss → theme}/config/mixins/_nav.scss +7 -7
- package/theme/config/mixins/_picklist.scss +66 -0
- package/theme/config/mixins/_popover.scss +96 -0
- package/{scss → theme}/config/mixins/_sidebar.scss +7 -4
- package/{scss → theme}/config/mixins/_sort.scss +43 -42
- package/{scss → theme}/config/mixins/_status-badge.scss +15 -2
- package/{scss → theme}/config/mixins/_table.scss +25 -14
- package/{scss → theme}/config/mixins/_tabs.scss +1 -1
- package/{scss → theme}/config/mixins/_toast.scss +174 -179
- package/{scss → theme}/config/mixins/_tooltip.scss +2 -2
- package/{scss → theme}/config/mixins/_typography.scss +16 -5
- package/{scss → theme}/config/mixins/_upload.scss +8 -6
- package/theme/ln-ashlar-core.scss +24 -0
- package/theme/ln-ashlar-dev.scss +66 -0
- package/theme/ln-ashlar-landing.scss +56 -0
- package/{scss/ln-ashlar.scss → theme/ln-ashlar-theme.scss} +63 -69
- package/theme/ln-ashlar.scss +8 -0
- package/theme/presentation/_bento.scss +151 -0
- package/theme/presentation/_cta.scss +111 -0
- package/theme/presentation/_effects.scss +216 -0
- package/theme/presentation/_features.scss +108 -0
- package/theme/presentation/_footer.scss +129 -0
- package/theme/presentation/_hero.scss +158 -0
- package/theme/presentation/_layout.scss +170 -0
- package/theme/presentation/_navbar.scss +140 -0
- package/theme/presentation/_pricing.scss +262 -0
- package/theme/presentation/_stats.scss +87 -0
- package/theme/presentation/_testimonials.scss +124 -0
- package/theme/presentation/_tokens.scss +39 -0
- package/theme/presentation/_typography.scss +163 -0
- package/{scss → theme}/utilities/_animations.scss +38 -0
- package/{scss → theme}/utilities/_utilities.scss +6 -0
- package/web-types.json +2312 -0
- package/js/ln-accordion/ln-accordion.js +0 -1
- package/js/ln-ajax/ln-ajax.js +0 -1
- package/js/ln-api-connector/ln-api-connector.js +0 -1
- package/js/ln-api-connector/src/ln-api-connector.js +0 -368
- package/js/ln-api-queue/ln-api-queue.js +0 -1
- package/js/ln-autoresize/ln-autoresize.js +0 -1
- package/js/ln-autosave/ln-autosave.js +0 -1
- package/js/ln-circular-progress/ln-circular-progress.js +0 -1
- package/js/ln-confirm/_ln-confirm.scss +0 -24
- package/js/ln-confirm/ln-confirm.js +0 -1
- package/js/ln-core/crypto.js +0 -83
- package/js/ln-core/helpers.js +0 -788
- package/js/ln-core/index.js +0 -7
- package/js/ln-core/persist.js +0 -97
- package/js/ln-couchdb-connector/ln-couchdb-connector.js +0 -1
- package/js/ln-data-coordinator/ln-data-coordinator.js +0 -1
- package/js/ln-data-coordinator/src/data-read-policy.js +0 -18
- package/js/ln-data-store/ln-data-store.js +0 -1
- package/js/ln-date/ln-date.js +0 -1
- package/js/ln-date/src/ln-date.js +0 -603
- package/js/ln-debug/README.md +0 -58
- package/js/ln-debug/ln-debug.js +0 -1
- package/js/ln-debug/src/ln-debug.js +0 -19
- package/js/ln-dropdown/ln-dropdown.js +0 -1
- package/js/ln-dropdown/ln-dropdown.scss +0 -15
- package/js/ln-editor/ln-editor.js +0 -1
- package/js/ln-editor/ln-editor.scss +0 -43
- package/js/ln-external-links/ln-external-links.js +0 -1
- package/js/ln-fill/ln-fill.js +0 -1
- package/js/ln-filter/README.md +0 -189
- package/js/ln-filter/ln-filter-dev.scss +0 -8
- package/js/ln-filter/ln-filter.js +0 -1
- package/js/ln-filter/src/ln-filter.js +0 -419
- package/js/ln-form/ln-form.js +0 -1
- package/js/ln-http/ln-http.js +0 -1
- package/js/ln-link/ln-link.js +0 -1
- package/js/ln-list/README.md +0 -129
- package/js/ln-list/ln-list.js +0 -1
- package/js/ln-list/ln-list.scss +0 -28
- package/js/ln-modal/ln-modal-dev.scss +0 -13
- package/js/ln-modal/ln-modal.js +0 -1
- package/js/ln-modal/ln-modal.scss +0 -35
- package/js/ln-modal-coordinator/README.md +0 -99
- package/js/ln-modal-coordinator/ln-modal-coordinator-dev.scss +0 -8
- package/js/ln-modal-coordinator/ln-modal-coordinator.js +0 -1
- package/js/ln-nav/ln-nav.js +0 -1
- package/js/ln-number/README.md +0 -149
- package/js/ln-number/ln-number.js +0 -1
- package/js/ln-number/src/ln-number.js +0 -443
- package/js/ln-options/ln-options.js +0 -1
- package/js/ln-popover/ln-popover.js +0 -1
- package/js/ln-progress/ln-progress.js +0 -1
- package/js/ln-progress/src/ln-progress.js +0 -98
- package/js/ln-router/ln-router.js +0 -1
- package/js/ln-search/README.md +0 -158
- package/js/ln-search/ln-search-dev.scss +0 -8
- package/js/ln-search/ln-search.js +0 -1
- package/js/ln-search/src/ln-search.js +0 -117
- package/js/ln-slug/ln-slug.js +0 -1
- package/js/ln-sort/ln-sort.js +0 -1
- package/js/ln-sort/src/ln-sort.js +0 -178
- package/js/ln-sortable/ln-sortable.js +0 -1
- package/js/ln-stat/ln-stat.js +0 -1
- package/js/ln-table/ln-table.js +0 -1
- package/js/ln-table/ln-table.scss +0 -11
- package/js/ln-table-coordinator/README.md +0 -73
- package/js/ln-table-coordinator/ln-table-coordinator.js +0 -1
- package/js/ln-table-coordinator/src/ln-table-coordinator.js +0 -160
- package/js/ln-tabs/ln-tabs-dev.scss +0 -18
- package/js/ln-tabs/ln-tabs.js +0 -1
- package/js/ln-tabs/src/ln-tabs.js +0 -188
- package/js/ln-time/ln-time.js +0 -1
- package/js/ln-toast/ln-toast.js +0 -1
- package/js/ln-toggle/ln-toggle.js +0 -1
- package/js/ln-tooltip/ln-tooltip.js +0 -1
- package/js/ln-tooltip/ln-tooltip.scss +0 -9
- package/js/ln-translations/ln-translations.js +0 -1
- package/js/ln-upload/README.md +0 -255
- package/js/ln-upload/ln-upload.js +0 -1
- package/js/ln-upload/ln-upload.scss +0 -20
- package/js/ln-upload/src/ln-upload.js +0 -423
- package/js/ln-validate/ln-validate.js +0 -1
- package/scss/components/_button.scss +0 -20
- package/scss/components/_confirm.scss +0 -5
- package/scss/components/_form.scss +0 -107
- package/scss/components/_status-badge.scss +0 -4
- package/scss/components/_toast.scss +0 -68
- package/scss/components/_upload.scss +0 -51
- package/scss/config/_theme.scss +0 -216
- package/scss/config/mixins/_btn.scss +0 -179
- package/scss/config/mixins/_confirm.scss +0 -31
- package/scss/config/mixins/_dropdown.scss +0 -141
- package/scss/config/mixins/_popover.scss +0 -64
- package/scss/ln-ashlar-dev.scss +0 -39
- /package/{js → components}/ln-core/positioning.js +0 -0
- /package/{js → components}/ln-core/reactive.js +0 -0
- /package/{js → components}/ln-filter/ln-filter.scss +0 -0
- /package/{js → components}/ln-icon/ln-icon.js +0 -0
- /package/{js → components}/ln-icon/src/ln-icon.js +0 -0
- /package/{js → components}/ln-search/ln-search.scss +0 -0
- /package/{js → components}/ln-toast/template.html +0 -0
- /package/{scss → theme}/components/_accordion.scss +0 -0
- /package/{scss → theme}/components/_ajax.scss +0 -0
- /package/{scss → theme}/components/_alert.scss +0 -0
- /package/{scss → theme}/components/_avatar.scss +0 -0
- /package/{scss → theme}/components/_breadcrumbs.scss +0 -0
- /package/{scss → theme}/components/_card.scss +0 -0
- /package/{scss → theme}/components/_chip.scss +0 -0
- /package/{scss → theme}/components/_circular-progress.scss +0 -0
- /package/{scss → theme}/components/_editor.scss +0 -0
- /package/{scss → theme}/components/_empty-state.scss +0 -0
- /package/{scss → theme}/components/_filter.scss +0 -0
- /package/{scss → theme}/components/_link.scss +0 -0
- /package/{scss → theme}/components/_loader.scss +0 -0
- /package/{scss → theme}/components/_modal.scss +0 -0
- /package/{scss → theme}/components/_nav.scss +0 -0
- /package/{scss → theme}/components/_page-header.scss +0 -0
- /package/{scss → theme}/components/_popover.scss +0 -0
- /package/{scss → theme}/components/_progress.scss +0 -0
- /package/{scss → theme}/components/_prose.scss +0 -0
- /package/{scss → theme}/components/_scrollbar.scss +0 -0
- /package/{scss → theme}/components/_sections.scss +0 -0
- /package/{scss → theme}/components/_sidebar.scss +0 -0
- /package/{scss → theme}/components/_stat-card.scss +0 -0
- /package/{scss → theme}/components/_stepper.scss +0 -0
- /package/{scss → theme}/components/_timeline.scss +0 -0
- /package/{scss → theme}/components/_tooltip.scss +0 -0
- /package/{scss → theme}/components/_translations.scss +0 -0
- /package/{scss → theme}/config/_icons.scss +0 -0
- /package/{scss → theme}/config/_mixins.scss +0 -0
- /package/{scss → theme}/config/mixins/_ajax.scss +0 -0
- /package/{scss → theme}/config/mixins/_avatar.scss +0 -0
- /package/{scss → theme}/config/mixins/_breadcrumbs.scss +0 -0
- /package/{scss → theme}/config/mixins/_breakpoints.scss +0 -0
- /package/{scss → theme}/config/mixins/_chip.scss +0 -0
- /package/{scss → theme}/config/mixins/_circular-progress.scss +0 -0
- /package/{scss → theme}/config/mixins/_display.scss +0 -0
- /package/{scss → theme}/config/mixins/_empty-state.scss +0 -0
- /package/{scss → theme}/config/mixins/_focus.scss +0 -0
- /package/{scss → theme}/config/mixins/_interaction.scss +0 -0
- /package/{scss → theme}/config/mixins/_kbd.scss +0 -0
- /package/{scss → theme}/config/mixins/_link.scss +0 -0
- /package/{scss → theme}/config/mixins/_page-header.scss +0 -0
- /package/{scss → theme}/config/mixins/_position.scss +0 -0
- /package/{scss → theme}/config/mixins/_progress.scss +0 -0
- /package/{scss → theme}/config/mixins/_prose.scss +0 -0
- /package/{scss → theme}/config/mixins/_shadows.scss +0 -0
- /package/{scss → theme}/config/mixins/_sizing.scss +0 -0
- /package/{scss → theme}/config/mixins/_spacing.scss +0 -0
- /package/{scss → theme}/config/mixins/_stat-card.scss +0 -0
- /package/{scss → theme}/config/mixins/_stepper.scss +0 -0
- /package/{scss → theme}/config/mixins/_timeline.scss +0 -0
- /package/{scss → theme}/config/mixins/_transitions.scss +0 -0
- /package/{scss → theme}/config/mixins/_translations.scss +0 -0
- /package/{scss → theme}/tabler-icons.txt +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates virtual scrolling slicing indices and padding heights.
|
|
3
|
+
* @param {number} scrollTop
|
|
4
|
+
* @param {number} viewportHeight
|
|
5
|
+
* @param {number} rowHeight
|
|
6
|
+
* @param {number} totalCount
|
|
7
|
+
* @param {number} [bufferRows=15]
|
|
8
|
+
* @returns {{ start: number, end: number, topPadding: number, bottomPadding: number }}
|
|
9
|
+
*/
|
|
10
|
+
export function calculateVirtualWindow(scrollTop, viewportHeight, rowHeight, totalCount, bufferRows = 15) {
|
|
11
|
+
if (totalCount <= 0 || rowHeight <= 0) {
|
|
12
|
+
return { start: 0, end: 0, topPadding: 0, bottomPadding: 0 };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const safeScrollTop = Math.max(0, scrollTop || 0);
|
|
16
|
+
const safeViewportHeight = Math.max(0, viewportHeight || 0);
|
|
17
|
+
|
|
18
|
+
const visibleStartIndex = Math.floor(safeScrollTop / rowHeight);
|
|
19
|
+
const visibleCount = Math.ceil(safeViewportHeight / rowHeight);
|
|
20
|
+
|
|
21
|
+
const start = Math.max(0, visibleStartIndex - bufferRows);
|
|
22
|
+
const end = Math.min(totalCount, visibleStartIndex + visibleCount + bufferRows);
|
|
23
|
+
|
|
24
|
+
const topPadding = start * rowHeight;
|
|
25
|
+
const bottomPadding = Math.max(0, (totalCount - end) * rowHeight);
|
|
26
|
+
|
|
27
|
+
return { start, end, topPadding, bottomPadding };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Computes selection state metadata across a dataset.
|
|
32
|
+
* @param {string[]|number[]} allIds
|
|
33
|
+
* @param {Set<string|number>|Array<string|number>} selectedIds
|
|
34
|
+
* @returns {{ totalCount: number, selectedCount: number, isAllSelected: boolean, isIndeterminate: boolean }}
|
|
35
|
+
*/
|
|
36
|
+
export function calculateSelectionState(allIds, selectedIds) {
|
|
37
|
+
const totalCount = Array.isArray(allIds) ? allIds.length : 0;
|
|
38
|
+
const selectedSet = selectedIds instanceof Set ? selectedIds : new Set(selectedIds || []);
|
|
39
|
+
let selectedCount = 0;
|
|
40
|
+
|
|
41
|
+
if (Array.isArray(allIds)) {
|
|
42
|
+
for (let i = 0; i < allIds.length; i++) {
|
|
43
|
+
if (selectedSet.has(allIds[i])) {
|
|
44
|
+
selectedCount++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
selectedCount = selectedSet.size;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const isAllSelected = totalCount > 0 && selectedCount === totalCount;
|
|
52
|
+
const isIndeterminate = selectedCount > 0 && selectedCount < totalCount;
|
|
53
|
+
|
|
54
|
+
return { totalCount, selectedCount, isAllSelected, isIndeterminate };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Toggles a row ID within a Set of selected IDs.
|
|
59
|
+
* @param {Set<string|number>} selectedSet
|
|
60
|
+
* @param {string|number} rowId
|
|
61
|
+
* @param {boolean} [forceState]
|
|
62
|
+
* @returns {Set<string|number>}
|
|
63
|
+
*/
|
|
64
|
+
export function toggleRowSelection(selectedSet, rowId, forceState) {
|
|
65
|
+
const next = new Set(selectedSet);
|
|
66
|
+
if (rowId == null) return next;
|
|
67
|
+
|
|
68
|
+
const shouldSelect = forceState !== undefined ? forceState : !next.has(rowId);
|
|
69
|
+
if (shouldSelect) {
|
|
70
|
+
next.add(rowId);
|
|
71
|
+
} else {
|
|
72
|
+
next.delete(rowId);
|
|
73
|
+
}
|
|
74
|
+
return next;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Selects or deselects all IDs.
|
|
79
|
+
* @param {Set<string|number>} selectedSet
|
|
80
|
+
* @param {Array<string|number>} allIds
|
|
81
|
+
* @param {boolean} selectAll
|
|
82
|
+
* @returns {Set<string|number>}
|
|
83
|
+
*/
|
|
84
|
+
export function toggleSelectAll(selectedSet, allIds, selectAll) {
|
|
85
|
+
const next = new Set(selectedSet);
|
|
86
|
+
if (!Array.isArray(allIds)) return next;
|
|
87
|
+
|
|
88
|
+
if (selectAll) {
|
|
89
|
+
for (let i = 0; i < allIds.length; i++) {
|
|
90
|
+
if (allIds[i] != null) next.add(allIds[i]);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
for (let i = 0; i < allIds.length; i++) {
|
|
94
|
+
next.delete(allIds[i]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return next;
|
|
98
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# ln-table-coordinator
|
|
2
|
+
|
|
3
|
+
> Applied to a wrapper element (`<div data-ln-table-coordinator>`) enclosing a table, search controls, and filter popovers. It listens for `ln-filter:change` within its host to toggle `.ln-filter-active` classes on header `<th>` buttons, intercepts `click` on `[data-ln-table-clear]` to reset search inputs and uncheck filter checkboxes, and captures page `'/'` `keydown` to focus the search input. It acts strictly as a Layer 2 mediator, never rendering table rows or holding query caches.
|
|
4
|
+
|
|
5
|
+
A zero-dependency, per-instance, host-scoped coordinator component that wires external search hosts (`ln-search`), column filter popovers (`ln-filter`), header filter indicators, and clear button triggers to a single `ln-table`.
|
|
6
|
+
|
|
7
|
+
`ln-table` itself is a Layer 1 primitive: it manages internal data state, virtual scroll rendering, pagination/windowing cache, row template cloning, cell formatting, and selection DOM state. `ln-table-coordinator` is the Layer 2 policy and mediator that wires external UI controls to `ln-table`.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🧭 Philosophy & Architecture
|
|
12
|
+
|
|
13
|
+
1. **Layer Separation.** `ln-table` manages its own internal table state and DOM subtree only — no external DOM querying, no external input mirroring, and no visual indicator class toggles on header buttons. `ln-table-coordinator` handles all project UI wiring on top.
|
|
14
|
+
2. **Host-Scoped, Per-Instance.** The coordinator is a real component: `[data-ln-table-coordinator]` is a **required wrapper**. Its handlers are bound to its own host element (`self.dom`), not `document`. The table, its `[data-ln-filter]` popovers, and its clear button(s) must all be descendants of the same coordinator host — outside its own subtree, the coordinator does not exist and does not look.
|
|
15
|
+
3. **Event-Driven Communication.** Communication with `ln-table` occurs strictly via CustomEvents (`ln-table:set-search`, `ln-table:set-filter`, `ln-table:request-clear-filters`).
|
|
16
|
+
|
|
17
|
+
**Breaking change:** a data-driven table's search/filter/clear-all only works when wrapped in `[data-ln-table-coordinator]`. There is no page-wide fallback — the coordinator does not resolve a table by scanning the whole document.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 🛠️ Declarative API & Wire Contract
|
|
22
|
+
|
|
23
|
+
| Target / Trigger | Event / Trigger | Behavior |
|
|
24
|
+
| :--- | :--- | :--- |
|
|
25
|
+
| `[data-ln-filter="tableId"]` (delivered by `ln-filter`) | `ln-filter:change` | Resolves target table in host, toggles `.ln-filter-active` indicator on header filter button. |
|
|
26
|
+
| `[data-ln-table-clear]`, `[data-ln-table-clear-all]` | `click` | Resolves the table structurally within this host, symmetrically clears the linked search input and `data-ln-search` source attribute, checks filter reset checkboxes, removes `.ln-filter-active` visual classes, and dispatches `ln-table:request-clear-filters` to SSR tables. |
|
|
27
|
+
| Document | `keydown` (`'/'`) | Focuses the search input inside the active coordinator wrapper on the page (or the first `[data-ln-search-for]` if none). Safely ignored when focus is in an input, textarea, or `isContentEditable` surface. |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## ⚡ Consumed & Dispatched Events
|
|
32
|
+
|
|
33
|
+
### Consumed Events
|
|
34
|
+
- `ln-filter:change` (on `self.dom`)
|
|
35
|
+
- `click` (on `self.dom` — targets `[data-ln-table-clear]` and `[data-ln-table-clear-all]`)
|
|
36
|
+
- `keydown` (`document` — key `'/'`, the sole document-level exception, see below)
|
|
37
|
+
|
|
38
|
+
### Dispatched Events
|
|
39
|
+
- `ln-table:request-clear-filters` (Target: the SSR table inside this host, Payload: `{ table }`)
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🔧 Internals
|
|
44
|
+
|
|
45
|
+
Source: `components/ln-table-coordinator/src/ln-table-coordinator.js`. This is a real per-instance component: `_component(dom)` calls `_bindEvents(self)`, which attaches listeners to `self.dom` and cleans them up symmetrically on `destroy()`.
|
|
46
|
+
|
|
47
|
+
### Column-filter indicator
|
|
48
|
+
|
|
49
|
+
`ln-filter` dispatches `ln-filter:change` on both its own `<ul data-ln-filter>` root and on the target element. The coordinator catches the event within its host, finds the `th[data-ln-table-filter-col="key"]`, and toggles `.ln-filter-active` on that header's `[data-ln-table-col-filter]` button based on `values.length > 0`. This header-indicator class is Layer-2 policy — which is why `ln-table` itself never sets it.
|
|
50
|
+
|
|
51
|
+
### Clear
|
|
52
|
+
|
|
53
|
+
A `click` on `[data-ln-table-clear]` / `[data-ln-table-clear-all]` resolves the table structurally: `clearBtn.closest('[data-ln-table]')`, falling back to `dom.querySelector('[data-ln-table]')` — always scoped to this host. It then runs: (1) strip `.ln-filter-active` off every filter button in the table, (2) blank the linked search input and set `data-ln-search=""` on the source host, (3) check each linked filter popover's `[data-ln-filter-reset]` and fire a bubbling `change`, (4) dispatch `ln-table:request-clear-filters` if the table is an SSR table.
|
|
54
|
+
|
|
55
|
+
### `/` focus shortcut
|
|
56
|
+
|
|
57
|
+
The sole deliberate exception to host-scoping. It remains a single `document`-level `keydown` listener registered once at module scope (outside `_component`/`_bindEvents`) — a page-level keyboard affordance, not coordination between a host and its children. Registering it per-instance would fire it once per coordinator on the page. It focuses the search input inside the active `[data-ln-table-coordinator]` wrapper, or the first `[data-ln-search]` on the page if none. Ignored when focus is already in an `INPUT`/`TEXTAREA`, `isContentEditable` surface (e.g. `ln-editor`), or when the event was already `defaultPrevented`.
|
|
58
|
+
|
|
59
|
+
### Multi-coordinator isolation
|
|
60
|
+
|
|
61
|
+
Each `_component(dom)` instance closures its handlers over its own `dom`; `addEventListener` on a specific host node only fires for events whose bubble path passes through that node. Two coordinator hosts on the same page cannot see each other's descendants' events — no shared/global state exists to leak. The `/` shortcut is the sole intentional exception to this isolation — it is document-scoped by design.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){"use strict";function L(){return typeof window>"u"?!1:window.lnDebug===!0||window.lnCore&&window.lnCore._debugSink?!0:typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug")||document.body.querySelector("[data-ln-debug]")!==null:!1}function S(e){if(typeof window>"u")return!1;if(window.lnDebug===!0)return!0;if(window.lnCore&&window.lnCore._debugIsContained)return e?window.lnCore._debugIsContained(e):window.lnCore._debugSink!==null;if(e&&e.closest){const t=e.closest("[data-ln-debug]");return!(!t||typeof document<"u"&&t===document.documentElement)}return typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug"):!1}if(typeof window<"u"&&(window.lnCore=window.lnCore||{},!window.lnCore._warnBound)){window.lnCore._warnBound=!0;const e=console.warn;console.warn=function(...t){if(typeof t[0]=="string"&&(t[0].startsWith("[ln-")||t[0].startsWith("[lnCore"))){let r=null;for(let i=1;i<t.length;i++)if(t[i]&&t[i].nodeType===1){r=t[i];break}if(!S(r))return}e.apply(console,t)}}function I(e,t,n){const r=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,r),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:r}))}function _(e,t){if(!document.body){document.addEventListener("DOMContentLoaded",function(){_(e,t)}),console.warn("["+t+'] Script loaded before <body> — add "defer" to your <script> tag');return}e()}const $={};function V(e,t){$[e]=t}function H(e){return $[e]||{ingress:t=>t,egress:t=>t}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=V,window.lnCore.getDataMapper=H);function U(){typeof window>"u"||(window.lnCore=window.lnCore||{},!window.lnCore._localeObserverBound&&(window.lnCore._localeObserverBound=!0,_(function(){new MutationObserver(function(){I(document,"ln-core:locale-change",{})}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"],subtree:!0})},"ln-core")))}const D={};function K(e,t){if(!e||typeof t!="object")return;const n=e.toLowerCase().split("-")[0];D[n]=t}function P(e){if(!e)return null;const t=e.toLowerCase().split("-")[0];return D[t]||null}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerLocaleFallback=K,window.lnCore.getLocaleFallback=P,window.lnCore.ensureLocaleObserver=U);const k={};function Q(e,t){k[e]||(k[e]=document.querySelector('[data-ln-template="'+e+'"]'));const n=k[e];return n?n.content.cloneNode(!0):(console.warn("["+(t||"ln-core")+'] Template "'+e+'" not found'),null)}function N(e,t){if(!e||!t)return e;const n=e.querySelectorAll("[data-ln-field]");for(let s=0;s<n.length;s++){const a=n[s],o=a.getAttribute("data-ln-field");t[o]!=null&&(a.textContent=t[o])}const r=e.querySelectorAll("[data-ln-attr]");for(let s=0;s<r.length;s++){const a=r[s],o=a.getAttribute("data-ln-attr").split(",");for(let c=0;c<o.length;c++){const u=o[c].trim().split(":");if(u.length!==2)continue;const d=u[0].trim(),f=u[1].trim();t[f]!=null&&a.setAttribute(d,t[f])}}const i=e.querySelectorAll("[data-ln-show]");for(let s=0;s<i.length;s++){const a=i[s],o=a.getAttribute("data-ln-show");o in t&&a.classList.toggle("hidden",!t[o])}const l=e.querySelectorAll("[data-ln-class]");for(let s=0;s<l.length;s++){const a=l[s],o=a.getAttribute("data-ln-class").split(",");for(let c=0;c<o.length;c++){const u=o[c].trim().split(":");if(u.length!==2)continue;const d=u[0].trim(),f=u[1].trim();f in t&&a.classList.toggle(d,!!t[f])}}return e}function X(e,t){e.matches&&e.matches("[data-ln-form], [data-ln-fillable]")&&(window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",e,t??null),e.dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0})));const n=e.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let r=0;r<n.length;r++)window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",n[r],t??null),n[r].dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0}));return e}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._fillBound||(window.lnCore._fillBound=!0,document.addEventListener("ln-fill",function(e){if(!(!e.target.matches||!e.target.matches("[data-ln-fillable]")))if(e.detail)N(e.target,e.detail);else{const t=e.target.querySelectorAll("[data-ln-field]");for(let n=0;n<t.length;n++)t[n].textContent=""}})));function R(e,t){if(!e||!t)return e;const n=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(;n.nextNode();){const l=n.currentNode;l.textContent.indexOf("{{")!==-1&&(l.textContent=l.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(s,a){return t[a]!==void 0?t[a]:""}))}const r=function(l,s){return t[s]!==void 0?t[s]:""},i=Array.from(e.querySelectorAll("*"));e.nodeType===1&&i.push(e);for(let l=0;l<i.length;l++){const s=i[l],a=s.attributes;for(let o=0;o<a.length;o++){const c=a[o];c.value.indexOf("{{")!==-1&&s.setAttribute(c.name,c.value.replace(/\{\{\s*(\w+)\s*\}\}/g,r))}}return e}function G(e,t,n,r,i,l){const s={};for(let o=0;o<e.children.length;o++){const c=e.children[o],u=c.getAttribute("data-ln-render-key");u&&(s[u]=c)}const a=document.createDocumentFragment();for(let o=0;o<t.length;o++){const c=t[o],u=String(r(c));let d=s[u];if(d)i(d,c,o);else{const f=Q(n,l);if(!f||(R(f,c),d=f.firstElementChild,!d))continue;d.setAttribute("data-ln-render-key",u),i(d,c,o)}a.appendChild(d)}e.textContent="",e.appendChild(a)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.fillTemplate=R,window.lnCore.fill=N,window.lnCore.lnFill=X,window.lnCore.renderList=G);function B(e,t,n,r,i){if(!e||t===null)return!0;const l=r||"ln-component",s=e.type;if(s==="trigger"||s==="marker"||s==="string"||s==="list"||!s||t===""&&e.fallback!==void 0)return!0;const a=o=>{i?console.warn(o,i):console.warn(o)};if(s==="boolean"){const o=t.trim().toLowerCase();return o!==""&&o!=="true"&&o!=="false"&&o!=="1"&&o!=="0"?(a(`[${l}] Invalid value "${t}" for boolean attribute "${n}". Allowed: "true", "false", or presence-only.`),!1):!0}if(s==="enum"){const o=e.values||[];return o.includes(t)?!0:(a(`[${l}] Invalid value "${t}" for attribute "${n}". Allowed: ${o.join(", ")}. Fallback: "${e.fallback}".`),!1)}if(s==="integer"){if(!/^-?\d+$/.test(t))return a(`[${l}] Invalid integer "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1;const o=parseInt(t,10);return e.min!==void 0&&o<e.min?(a(`[${l}] Value ${o} for attribute "${n}" is less than min (${e.min}).`),!1):e.max!==void 0&&o>e.max?(a(`[${l}] Value ${o} for attribute "${n}" is greater than max (${e.max}).`),!1):!0}if(s==="float")return isNaN(Number(t))?(a(`[${l}] Invalid float "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1):!0;if(s==="json")try{return JSON.parse(t),!0}catch(o){return a(`[${l}] Invalid JSON for attribute "${n}": ${o.message}. Fallback: ${e.fallback}.`),!1}return!0}function J(e){const t={};for(const n in e){const r=e[n];r&&r.effect&&(t[n]=r.effect)}return Object.keys(t).length?t:null}function z(e){if(e.onAttrChange&&!e.declared)return null;const t=new Set;if(e.effects)for(const n in e.effects)t.add(n);if(e.onAttrChange&&e.declared)for(const n of e.declared)t.add(n);return t}function E(e,t,n,r){if(e.nodeType!==1)return;const l=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",s=Array.from(e.querySelectorAll(l));e.matches&&e.matches(l)&&s.push(e);for(const a of s)if(!a[n]){window.lnCore._persistSink&&a.hasAttribute("data-ln-persist")&&window.lnCore._persistSink(a,t);try{a[n]=new r(a)}catch(o){console.error("["+n+"] init failed",a,o)}}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[]);function Y(){return typeof window<"u"&&window.lnCore&&window.lnCore._bootHolds||0}function Z(e){typeof window<"u"?(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[],window.lnCore._bootHolds>0?window.lnCore._bootQueue.push(e):setTimeout(e,0)):e()}function F(){window.lnCore=window.lnCore||{};const e=window.lnCore._attrRegistry=window.lnCore._attrRegistry||{byAttr:new Map,reactive:[],persist:[]};return e.byReactive=e.byReactive||new Map,e.reactiveWildcard=e.reactiveWildcard||[],e}function ee(e){const t=F(),n=e.observed||[];for(let r=0;r<n.length;r++){const i=n[r];t.byAttr.has(i)||t.byAttr.set(i,[]),t.byAttr.get(i).push(e)}if(t.byDeclaredAttr=t.byDeclaredAttr||new Map,e.attributes)for(const r in e.attributes){t.byDeclaredAttr.has(r)||t.byDeclaredAttr.set(r,[]);const i=t.byDeclaredAttr.get(r);i.some(l=>l.componentTag===e.componentTag)||i.push({spec:e.attributes[r],componentTag:e.componentTag})}if(e.onAttrChange||e.effects){t.reactive.push(e);const r=z(e);if(r===null)t.reactiveWildcard.push(e);else for(const i of r)t.byReactive.has(i)||t.byReactive.set(i,[]),t.byReactive.get(i).push(e)}e.persist&&t.persist.push(e)}function M(e,t,n,r){for(let i=0;i<e.length;i++){const l=e[i];if(!t[l.attribute])continue;const s=l.effects&&l.effects[n];s?s(t,n,r):l.onAttrChange&&(!l.declared||l.declared.has(n))&&l.onAttrChange(t,n,r)}}function te(e){const t=e.target,n=e.attributeName;if(e.oldValue===t.getAttribute(n))return;const r=F(),i=r.byAttr.get(n);if(L()&&r.byDeclaredAttr&&r.byDeclaredAttr.has(n)&&S(t)){const l=r.byDeclaredAttr.get(n),s=t.getAttribute(n);for(let a=0;a<l.length;a++)B(l[a].spec,s,n,l[a].componentTag,t)}if(window.lnCore._debugSink&&(n.indexOf("data-ln-")===0||i)&&window.lnCore._debugSink("attr",n,t,{oldValue:e.oldValue,newValue:t.getAttribute(n)}),n.indexOf("data-ln-")===0){const l=r.byReactive.get(n);l&&M(l,t,n,e.oldValue),r.reactiveWildcard.length&&M(r.reactiveWildcard,t,n,e.oldValue)}if(i)for(let l=0;l<i.length;l++){const s=i[l];if(s.handler){s.handler(t,n,e.oldValue);continue}s.onAttributeChange&&t[s.attribute]?s.onAttributeChange(t,n):(E(t,s.selector,s.attribute,s.ComponentFn),s.onInit&&s.onInit(t))}}function ne(){window.lnCore=window.lnCore||{},!window.lnCore._attrObserverBound&&(window.lnCore._attrObserverBound=!0,_(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++)try{te(t[n])}catch(r){console.error("[ln-core] mutation handler failed",t[n].target,r)}}).observe(document.body,{attributes:!0,subtree:!0,attributeOldValue:!0})},"ln-core"))}function W(){return window.lnCore=window.lnCore||{},window.lnCore._lifecycleRegistry=window.lnCore._lifecycleRegistry||[]}function re(e){const t=W();if(t.length){if(e.target)for(let n=0;n<t.length;n++){const r=t[n];if(r.onSubtreeChange){const i=r.query,l=e.target.nodeType===1?e.target.matches(i)?e.target:e.target.closest(i):e.target.parentElement?e.target.parentElement.closest(i):null;l&&r.onSubtreeChange(l,e)}}for(let n=0;n<e.addedNodes.length;n++){const r=e.addedNodes[n];if(r.nodeType===1)for(let i=0;i<t.length;i++){const l=t[i];E(r,l.selector,l.attribute,l.ComponentFn),l.onInit&&l.onInit(r)}}for(let n=0;n<e.removedNodes.length;n++){const r=e.removedNodes[n];if(r.nodeType===1)for(let i=0;i<t.length;i++){const l=t[i],s=l.query,a=Array.from(r.querySelectorAll(s));r.matches&&r.matches(s)&&a.push(r);for(let o=0;o<a.length;o++){const c=a[o];if(!document.contains(c)){const u=c[l.attribute];if(u&&typeof u.destroy=="function")try{u.destroy()}catch(d){console.error("["+l.attribute+"] destroy failed",c,d)}delete c[l.attribute]}}}}}}function oe(){window.lnCore=window.lnCore||{},!window.lnCore._lifecycleObserverBound&&(window.lnCore._lifecycleObserverBound=!0,_(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++){const r=t[n];if(r.type==="childList")try{re(r)}catch(i){console.error("[ln-core] lifecycle handler failed",r.target,i)}}}).observe(document.body,{childList:!0,subtree:!0})},"ln-core"))}function le(e,t,n,r,i={}){const l=i.extraAttributes||[],s=i.onAttributeChange||null,a=i.onSubtreeChange||null,o=i.onInit||null,c=i.onAttrChange||null,u=i.effects||null,d=i.attributes||null,f=d?J(d):u,m=d?new Set(Object.keys(d)):null,h=i.persist||null;function w(A){const p=A||document.body;if(E(p,e,t,n),d&&L())for(const v in d){const be=d[v],T=Array.from(p.querySelectorAll("["+v+"]"));p.matches&&p.matches("["+v+"]")&&T.push(p);for(let O=0;O<T.length;O++){const x=T[O];S(x)&&B(be,x.getAttribute(v),v,r,x)}}o&&o(p)}const y=[];if(e.indexOf("[")!==-1){const A=/\[([\w-]+)/g;let p;for(;(p=A.exec(e))!==null;)y.push(p[1])}else y.push(e);ee({selector:e,attribute:t,componentTag:r,attributes:d,ComponentFn:n,onInit:o,observed:y.concat(l),onAttributeChange:s,onAttrChange:c,effects:f,declared:m,persist:h}),ne();const b=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]";W().push({selector:e,attribute:t,ComponentFn:n,onInit:o,onSubtreeChange:a,query:b}),oe(),window[t]=w;function g(){Y()>0?Z(function(){w(document.body)}):w(document.body)}return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",g):g(),w}function j(e){return(e||"").replace(/^#/,"")}function q(e){const t=e===void 0?location.hash:e,n={},r=j(t);if(!r)return n;const i=r.split("&");for(let l=0;l<i.length;l++){const s=i[l];if(!s)continue;const a=s.indexOf(":"),o=a>-1?s.slice(0,a):s,c=a>-1?s.slice(a+1):"";if(o)try{n[o]=decodeURIComponent(c)}catch{n[o]=c}}return n}function ie(e){if(!e)return null;const t=q();return e in t?t[e]:null}function se(e,t){if(!e)return;const n=q();t==null?delete n[e]:n[e]=String(t);const i=Object.keys(n).map(function(l){const s=n[l];return s===""?l:l+":"+encodeURIComponent(s)}).join("&");j(location.hash)!==i&&(location.hash=i)}function ae(e){return e.button===1||e.ctrlKey||e.metaKey||e.shiftKey?!1:(e.preventDefault(),!0)}function ce(e,t){if(!e||!e.hasAttribute("data-ln-hash"))return null;const n=e.getAttribute("data-ln-hash");if(n&&n.trim()!=="")return n.trim();const r=e.getAttribute("data-ln-sort")||e.getAttribute("data-ln-search-for")||e.getAttribute("data-ln-search")||e.getAttribute("data-ln-filter")||e.id;return r?t?r+"-"+t:r:t||null}function ue(e,t){return!t||t==="none"||e===null||e===void 0?null:String(e)+"."+t}function de(e){return!e||typeof e!="string"?null:e.endsWith(".asc")?{fieldOrColumn:e.slice(0,-4),direction:"asc"}:e.endsWith(".desc")?{fieldOrColumn:e.slice(0,-5),direction:"desc"}:null}function fe(e,t){return!e||!Array.isArray(t)||t.length===0?null:e+":"+t.map(encodeURIComponent).join(",")}function we(e){if(!e||typeof e!="string")return null;const t=e.indexOf(":");if(t===-1)return null;const n=e.slice(0,t),r=e.slice(t+1),i=r?r.split(",").map(function(l){try{return decodeURIComponent(l)}catch{return l}}).filter(Boolean):[];return{key:n,values:i}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=q,window.lnCore.hashGet=ie,window.lnCore.hashSet=se,window.lnCore.hashLinkClick=ae,window.lnCore.resolveHashNamespace=ce,window.lnCore.hashSortEncode=ue,window.lnCore.hashSortDecode=de,window.lnCore.hashFilterEncode=fe,window.lnCore.hashFilterDecode=we),(function(){const e="data-ln-table-coordinator",t="lnTableCoordinator";if(window[t]!==void 0)return;const n={"data-ln-table-coordinator":{type:"marker",description:"Mounts table coordinator mediating between table, search, filter, and pagination"}};document.addEventListener("keydown",function(a){if(a.key!=="/"||a.defaultPrevented||document.activeElement&&(document.activeElement.tagName==="INPUT"||document.activeElement.tagName==="TEXTAREA"||document.activeElement.isContentEditable))return;const o=document.querySelector("["+e+"] [data-ln-search-for]")||document.querySelector("[data-ln-search-for]");if(!o)return;const c=o.tagName==="INPUT"||o.tagName==="TEXTAREA"?o:o.querySelector('input[type="search"], input[type="text"], input');c&&(a.preventDefault(),c.focus())});function r(a){return this.dom=a,s(this),this}function i(a,o){const c=o?'[data-ln-search-for="'+o+'"]':"[data-ln-search-for]",u=a.querySelector(c)||document.querySelector(c);return u?u.tagName==="INPUT"||u.tagName==="TEXTAREA"?u:u.querySelector("input, textarea"):null}function l(a,o){if(o){const u=a.querySelectorAll('[data-ln-filter="'+o+'"]');if(u.length>0)return u;const d=document.querySelectorAll('[data-ln-filter="'+o+'"]');if(d.length>0)return d}const c=a.querySelectorAll("[data-ln-filter]");return c.length>0?c:document.querySelectorAll("[data-ln-filter]")}function s(a){const o=a.dom;function c(u){const d=u.target;if(d&&d.hasAttribute&&(d.hasAttribute("data-ln-table")||d.tagName==="TABLE"))return d;const f=u.detail&&u.detail.targetId||d&&d.id;return f?o.querySelector('[data-ln-table-source="'+f+'"]')||o.querySelector('[data-ln-table="'+f+'"]')||o.querySelector("#"+f)||(o.id===f?o:null)||document.getElementById(f):null}a._handlers={filter:function(u){if(!u.detail)return;const d=c(u);if(!d)return;const f=u.detail.key,m=u.detail.values||[],h=d.querySelectorAll("th");for(let w=0;w<h.length;w++)if((h[w].getAttribute("data-ln-table-filter-col")||h[w].getAttribute("data-ln-filter-col")||h[w].getAttribute("data-ln-filter-key")||h[w].getAttribute("data-ln-field"))===f){const C=h[w].querySelector("[data-ln-table-col-filter], .table-filter");C&&C.classList.toggle("ln-filter-active",m.length>0);break}},clear:function(u){const d=u.target.closest("[data-ln-table-clear], [data-ln-table-clear-all]");if(!d)return;const f=d.closest("[data-ln-table], table")||o.querySelector("[data-ln-table], table");if(!f)return;const m=f.lnTable&&f.lnTable.name||f.id,h=f.querySelectorAll("th");for(let b=0;b<h.length;b++){const g=h[b].querySelector("[data-ln-table-col-filter], .table-filter");g&&g.classList.remove("ln-filter-active")}const w=f.getAttribute("data-ln-table-source")||f.id,y=w?document.getElementById(w):null;if(y&&y.hasAttribute("data-ln-search"))y.setAttribute("data-ln-search","");else{const b=i(o,w);b&&b.value!==""&&(b.value="",b.dispatchEvent(new Event("input",{bubbles:!0})))}const C=l(o,w);for(let b=0;b<C.length;b++){const g=C[b].querySelector("[data-ln-filter-reset]");if(!g)continue;const A=C[b].querySelectorAll("input:not([data-ln-filter-reset]):checked").length>0;(!g.checked||A)&&(g.checked=!0,g.dispatchEvent(new Event("change",{bubbles:!0})))}f.lnTable&&!f.hasAttribute("data-ln-table-source")&&I(f,"ln-table:request-clear-filters",{table:m})}},o.addEventListener("ln-filter:change",a._handlers.filter),o.addEventListener("click",a._handlers.clear)}r.prototype.destroy=function(){this.dom[t]&&(this._handlers&&(this.dom.removeEventListener("ln-filter:change",this._handlers.filter),this.dom.removeEventListener("click",this._handlers.clear),this._handlers=null),delete this.dom[t])},le(e,t,r,"ln-table-coordinator",{attributes:n})})()})();
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "This file is partly generated. The $comment, component, generator, and each attribute's direction, sources, type, values, fallback, and description keys are owned by the generator and synchronized from JS source code. All other keys are preserved and safe to hand-author. Regenerate with `npm run sync:ln-schemas`.",
|
|
3
|
+
"component": "ln-table-coordinator",
|
|
4
|
+
"generator": "scripts/sync-ln-schemas.mjs",
|
|
5
|
+
"attributes": {
|
|
6
|
+
"data-ln-field": {
|
|
7
|
+
"direction": "author",
|
|
8
|
+
"sources": [
|
|
9
|
+
"src/ln-table-coordinator.js"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"data-ln-filter": {
|
|
13
|
+
"direction": "author",
|
|
14
|
+
"sources": [
|
|
15
|
+
"src/ln-table-coordinator.js"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"data-ln-filter-col": {
|
|
19
|
+
"direction": "author",
|
|
20
|
+
"sources": [
|
|
21
|
+
"src/ln-table-coordinator.js"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"data-ln-filter-key": {
|
|
25
|
+
"direction": "author",
|
|
26
|
+
"sources": [
|
|
27
|
+
"src/ln-table-coordinator.js"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"data-ln-filter-reset": {
|
|
31
|
+
"direction": "author",
|
|
32
|
+
"sources": [
|
|
33
|
+
"src/ln-table-coordinator.js"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"data-ln-search": {
|
|
37
|
+
"direction": "both",
|
|
38
|
+
"sources": [
|
|
39
|
+
"src/ln-table-coordinator.js"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"data-ln-search-for": {
|
|
43
|
+
"direction": "author",
|
|
44
|
+
"sources": [
|
|
45
|
+
"src/ln-table-coordinator.js"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"data-ln-table": {
|
|
49
|
+
"direction": "author",
|
|
50
|
+
"sources": [
|
|
51
|
+
"src/ln-table-coordinator.js"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"data-ln-table-clear": {
|
|
55
|
+
"direction": "author",
|
|
56
|
+
"sources": [
|
|
57
|
+
"src/ln-table-coordinator.js"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"data-ln-table-clear-all": {
|
|
61
|
+
"direction": "author",
|
|
62
|
+
"sources": [
|
|
63
|
+
"src/ln-table-coordinator.js"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
"data-ln-table-col-filter": {
|
|
67
|
+
"direction": "author",
|
|
68
|
+
"sources": [
|
|
69
|
+
"src/ln-table-coordinator.js"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"data-ln-table-coordinator": {
|
|
73
|
+
"direction": "author",
|
|
74
|
+
"sources": [
|
|
75
|
+
"src/ln-table-coordinator.js"
|
|
76
|
+
],
|
|
77
|
+
"type": "marker",
|
|
78
|
+
"description": "Mounts table coordinator mediating between table, search, filter, and pagination"
|
|
79
|
+
},
|
|
80
|
+
"data-ln-table-filter-col": {
|
|
81
|
+
"direction": "author",
|
|
82
|
+
"sources": [
|
|
83
|
+
"src/ln-table-coordinator.js"
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
"data-ln-table-source": {
|
|
87
|
+
"direction": "author",
|
|
88
|
+
"sources": [
|
|
89
|
+
"src/ln-table-coordinator.js"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { registerComponent, dispatch } from '../../ln-core';
|
|
2
|
+
|
|
3
|
+
(function () {
|
|
4
|
+
const DOM_SELECTOR = 'data-ln-table-coordinator';
|
|
5
|
+
const DOM_ATTRIBUTE = 'lnTableCoordinator';
|
|
6
|
+
|
|
7
|
+
if (window[DOM_ATTRIBUTE] !== undefined) return;
|
|
8
|
+
|
|
9
|
+
// ─── Attribute Contract (SSOT) ──────────────────────────
|
|
10
|
+
const ATTRIBUTES = {
|
|
11
|
+
'data-ln-table-coordinator': { type: 'marker', description: 'Mounts table coordinator mediating between table, search, filter, and pagination' }
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// ─── Keyboard Navigation Search Focus ('/' shortcut) ──
|
|
15
|
+
// Deliberate, user-approved exception to host-scoping: this is a
|
|
16
|
+
// page-level keyboard affordance, not coordination between a host and
|
|
17
|
+
// its children, so it stays a single module-scope document listener
|
|
18
|
+
// exactly as it behaves today (unchanged resolution order). Registering
|
|
19
|
+
// it per-instance inside _bindEvents would fire it once per coordinator
|
|
20
|
+
// on the page — this is why it lives outside _component/_bindEvents.
|
|
21
|
+
|
|
22
|
+
document.addEventListener('keydown', function (e) {
|
|
23
|
+
if (e.key !== '/') return;
|
|
24
|
+
if (e.defaultPrevented) return;
|
|
25
|
+
if (document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' || document.activeElement.isContentEditable)) return;
|
|
26
|
+
|
|
27
|
+
// Resolve search input inside active coordinator wrapper or first visible search
|
|
28
|
+
const searchHost = document.querySelector('[' + DOM_SELECTOR + '] [data-ln-search-for]')
|
|
29
|
+
|| document.querySelector('[data-ln-search-for]');
|
|
30
|
+
if (!searchHost) return;
|
|
31
|
+
|
|
32
|
+
const input = (searchHost.tagName === 'INPUT' || searchHost.tagName === 'TEXTAREA')
|
|
33
|
+
? searchHost
|
|
34
|
+
: searchHost.querySelector('input[type="search"], input[type="text"], input');
|
|
35
|
+
|
|
36
|
+
if (input) {
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
input.focus();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// ─── Component Constructor ─────────────────────────────
|
|
43
|
+
|
|
44
|
+
function _component(dom) {
|
|
45
|
+
this.dom = dom;
|
|
46
|
+
_bindEvents(this);
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function _findSearchInput(dom, sourceId) {
|
|
51
|
+
const sel = sourceId ? '[data-ln-search-for="' + sourceId + '"]' : '[data-ln-search-for]';
|
|
52
|
+
const host = dom.querySelector(sel) || document.querySelector(sel);
|
|
53
|
+
if (!host) return null;
|
|
54
|
+
return (host.tagName === 'INPUT' || host.tagName === 'TEXTAREA')
|
|
55
|
+
? host
|
|
56
|
+
: host.querySelector('input, textarea');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function _findFilterElements(dom, sourceId) {
|
|
60
|
+
if (sourceId) {
|
|
61
|
+
const scoped = dom.querySelectorAll('[data-ln-filter="' + sourceId + '"]');
|
|
62
|
+
if (scoped.length > 0) return scoped;
|
|
63
|
+
const global = document.querySelectorAll('[data-ln-filter="' + sourceId + '"]');
|
|
64
|
+
if (global.length > 0) return global;
|
|
65
|
+
}
|
|
66
|
+
const fallbackScoped = dom.querySelectorAll('[data-ln-filter]');
|
|
67
|
+
return fallbackScoped.length > 0 ? fallbackScoped : document.querySelectorAll('[data-ln-filter]');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Event Binding (host-scoped — own subtree only) ────
|
|
71
|
+
|
|
72
|
+
function _bindEvents(self) {
|
|
73
|
+
const dom = self.dom;
|
|
74
|
+
|
|
75
|
+
// Point-to-point only: the event either landed on the table itself, or it
|
|
76
|
+
// names the source that table is bound to. No "first table in the host"
|
|
77
|
+
// fallback — ln-filter fires ln-filter:change on its own <ul>
|
|
78
|
+
// root AND on the target, so a fallback makes the coordinator act twice
|
|
79
|
+
// for one user change and emit two identical fetches.
|
|
80
|
+
function _resolveTable(e) {
|
|
81
|
+
const el = e.target;
|
|
82
|
+
if (el && el.hasAttribute && (el.hasAttribute('data-ln-table') || el.tagName === 'TABLE')) return el;
|
|
83
|
+
const targetId = (e.detail && e.detail.targetId) || (el && el.id);
|
|
84
|
+
if (!targetId) return null;
|
|
85
|
+
return dom.querySelector('[data-ln-table-source="' + targetId + '"]') ||
|
|
86
|
+
dom.querySelector('[data-ln-table="' + targetId + '"]') ||
|
|
87
|
+
dom.querySelector('#' + targetId) ||
|
|
88
|
+
(dom.id === targetId ? dom : null) ||
|
|
89
|
+
document.getElementById(targetId);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
self._handlers = {
|
|
93
|
+
// Query state is not forwarded here. The source owns search/filter/sort
|
|
94
|
+
// (docs/architecture/shared-query.md) and ln-data-coordinator re-serves
|
|
95
|
+
// every view bound to it — a second forwarder would fetch twice for one
|
|
96
|
+
// user change. What is left is the header indicator, which is Layer 2
|
|
97
|
+
// policy: ln-table never sets this class itself.
|
|
98
|
+
filter: function (e) {
|
|
99
|
+
if (!e.detail) return;
|
|
100
|
+
const table = _resolveTable(e);
|
|
101
|
+
if (!table) return;
|
|
102
|
+
|
|
103
|
+
const key = e.detail.key;
|
|
104
|
+
const values = e.detail.values || [];
|
|
105
|
+
|
|
106
|
+
const ths = table.querySelectorAll('th');
|
|
107
|
+
for (let i = 0; i < ths.length; i++) {
|
|
108
|
+
const colKey = ths[i].getAttribute('data-ln-table-filter-col') ||
|
|
109
|
+
ths[i].getAttribute('data-ln-filter-col') ||
|
|
110
|
+
ths[i].getAttribute('data-ln-filter-key') ||
|
|
111
|
+
ths[i].getAttribute('data-ln-field');
|
|
112
|
+
if (colKey === key) {
|
|
113
|
+
const btn = ths[i].querySelector('[data-ln-table-col-filter], .table-filter');
|
|
114
|
+
if (btn) btn.classList.toggle('ln-filter-active', values.length > 0);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
// Clear-all has no ID binding of its own — resolve structurally,
|
|
121
|
+
// scoped to this host only (never document-wide).
|
|
122
|
+
clear: function (e) {
|
|
123
|
+
const clearBtn = e.target.closest('[data-ln-table-clear], [data-ln-table-clear-all]');
|
|
124
|
+
if (!clearBtn) return;
|
|
125
|
+
|
|
126
|
+
const table = clearBtn.closest('[data-ln-table], table') || dom.querySelector('[data-ln-table], table');
|
|
127
|
+
if (!table) return;
|
|
128
|
+
|
|
129
|
+
const name = (table.lnTable && table.lnTable.name) || table.id;
|
|
130
|
+
|
|
131
|
+
const ths = table.querySelectorAll('th');
|
|
132
|
+
for (let i = 0; i < ths.length; i++) {
|
|
133
|
+
const filterBtn = ths[i].querySelector('[data-ln-table-col-filter], .table-filter');
|
|
134
|
+
if (filterBtn) filterBtn.classList.remove('ln-filter-active');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Query controls address the source (docs/architecture/shared-query.md);
|
|
138
|
+
// only an SSR table is its own source.
|
|
139
|
+
const sourceId = table.getAttribute('data-ln-table-source') || table.id;
|
|
140
|
+
const sourceEl = sourceId ? document.getElementById(sourceId) : null;
|
|
141
|
+
if (sourceEl && sourceEl.hasAttribute('data-ln-search')) {
|
|
142
|
+
// State component syncs input controls and emits ln-search:change
|
|
143
|
+
sourceEl.setAttribute('data-ln-search', '');
|
|
144
|
+
} else {
|
|
145
|
+
const input = _findSearchInput(dom, sourceId);
|
|
146
|
+
if (input && input.value !== '') {
|
|
147
|
+
input.value = '';
|
|
148
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const filterEls = _findFilterElements(dom, sourceId);
|
|
153
|
+
for (let i = 0; i < filterEls.length; i++) {
|
|
154
|
+
const resetInput = filterEls[i].querySelector('[data-ln-filter-reset]');
|
|
155
|
+
if (!resetInput) continue;
|
|
156
|
+
const hasActiveValues = filterEls[i].querySelectorAll('input:not([data-ln-filter-reset]):checked').length > 0;
|
|
157
|
+
if (!resetInput.checked || hasActiveValues) {
|
|
158
|
+
resetInput.checked = true;
|
|
159
|
+
resetInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// A data-driven table is refreshed by its source: the control resets
|
|
164
|
+
// above already emit the query change. Only an SSR table, which holds
|
|
165
|
+
// its own rows, still needs to be told directly.
|
|
166
|
+
if (table.lnTable && !table.hasAttribute('data-ln-table-source')) {
|
|
167
|
+
dispatch(table, 'ln-table:request-clear-filters', { table: name });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
dom.addEventListener('ln-filter:change', self._handlers.filter);
|
|
173
|
+
dom.addEventListener('click', self._handlers.clear);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ─── Destroy ────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
_component.prototype.destroy = function () {
|
|
179
|
+
if (!this.dom[DOM_ATTRIBUTE]) return;
|
|
180
|
+
|
|
181
|
+
if (this._handlers) {
|
|
182
|
+
this.dom.removeEventListener('ln-filter:change', this._handlers.filter);
|
|
183
|
+
this.dom.removeEventListener('click', this._handlers.clear);
|
|
184
|
+
this._handlers = null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
delete this.dom[DOM_ATTRIBUTE];
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-table-coordinator', {
|
|
191
|
+
attributes: ATTRIBUTES
|
|
192
|
+
});
|
|
193
|
+
})();
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# ln-tabs
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Applied to a wrapper `<section data-ln-tabs>` containing trigger buttons (`data-ln-tab="key"`) and panels (`data-ln-panel="key"`).
|
|
4
|
+
> On trigger `click`, it writes `data-ln-tabs-active="key"` on the wrapper. An attribute observer synchronizes the DOM:
|
|
5
|
+
> toggling `class="hidden"` and `aria-hidden` on panels, and updating `aria-selected` on triggers. When configured with an `id`
|
|
6
|
+
> and anchor triggers (`<a href="#id:key">`), it synchronizes the active tab with `window.location.hash` for deep-linking and browser history persistence.
|
|
4
7
|
|
|
5
8
|
---
|
|
6
9
|
|
|
@@ -48,9 +51,9 @@ Triggers and panels are bound via matching keys inside a wrapper. Inactive panel
|
|
|
48
51
|
|
|
49
52
|
## 3. The Declarative API & State Contract
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
State changes are driven declaratively via the `data-ln-tabs-active` attribute. **The HTML attribute is the sole source of truth.**
|
|
52
55
|
|
|
53
|
-
Clicks, URL hash changes, localStorage restorations, and external scripts all change state by writing the active attribute on the wrapper:
|
|
56
|
+
Clicks, URL hash changes, localStorage restorations, and external scripts all change state by writing the active attribute on the wrapper, dispatching request events, or calling the instance helper method:
|
|
54
57
|
|
|
55
58
|
```js
|
|
56
59
|
const tabs = document.getElementById('user-tabs');
|
|
@@ -58,6 +61,12 @@ const tabs = document.getElementById('user-tabs');
|
|
|
58
61
|
// Canonical write — switches the active tab
|
|
59
62
|
tabs.setAttribute('data-ln-tabs-active', 'settings');
|
|
60
63
|
|
|
64
|
+
// Or via command event
|
|
65
|
+
tabs.dispatchEvent(new CustomEvent('ln-tabs:request-select', { detail: { key: 'settings' }, bubbles: true }));
|
|
66
|
+
|
|
67
|
+
// Or via instance attribute-bridge method
|
|
68
|
+
tabs.lnTabs.select('settings');
|
|
69
|
+
|
|
61
70
|
// Read-only state query
|
|
62
71
|
tabs.getAttribute('data-ln-tabs-active'); // Returns currently active key
|
|
63
72
|
```
|
|
@@ -82,19 +91,28 @@ Mixing both trigger types in one group falls back to persist mode and logs a con
|
|
|
82
91
|
|
|
83
92
|
---
|
|
84
93
|
|
|
85
|
-
## 4. Transition Events
|
|
94
|
+
## 4. Transition & Command Events
|
|
86
95
|
|
|
87
96
|
All events bubble. The dispatch target is the wrapper element.
|
|
88
97
|
|
|
89
|
-
| Event | Cancelable | `detail` | Dispatched When |
|
|
90
|
-
|
|
91
|
-
| **`ln-tabs:
|
|
92
|
-
| **`ln-tabs:
|
|
98
|
+
| Event | Direction | Cancelable | `detail` | Dispatched / Handled When |
|
|
99
|
+
|---|:---:|:---:|---|---|
|
|
100
|
+
| **`ln-tabs:request-select`** | Listens | No | `{ key }` | Command event sent by coordinators or external triggers to select a tab. |
|
|
101
|
+
| **`ln-tabs:before-change`** | Emits | **Yes** | `{ key, previousKey, tab, panel, target }` | Dispatched before active tab changes. Calling `event.preventDefault()` cancels the switch and reverts attribute/hash state. |
|
|
102
|
+
| **`ln-tabs:change`** | Emits | No | `{ key, previousKey, tab, panel, target }` | After the active panel is swapped, ARIA synced, focus moved (if enabled), and localStorage updated. |
|
|
93
103
|
|
|
94
104
|
```js
|
|
105
|
+
// Example: Prevent tab change if unsaved changes exist
|
|
106
|
+
document.addEventListener('ln-tabs:before-change', (e) => {
|
|
107
|
+
if (hasUnsavedChanges) {
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
alert('Please save changes before leaving this tab.');
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
95
113
|
// Example: Listen for tab changes
|
|
96
114
|
document.addEventListener('ln-tabs:change', (e) => {
|
|
97
|
-
console.log(`Active tab
|
|
115
|
+
console.log(`Active tab changed from ${e.detail.previousKey} to: ${e.detail.key}`);
|
|
98
116
|
});
|
|
99
117
|
```
|
|
100
118
|
|
|
@@ -146,16 +164,16 @@ Use **button triggers** and add `data-ln-persist="key"` to remember the active t
|
|
|
146
164
|
```
|
|
147
165
|
- **Standalone IIFE**: For lightweight pages, load the standalone, self-registering IIFE version:
|
|
148
166
|
```html
|
|
149
|
-
<script src="
|
|
167
|
+
<script src="components/ln-tabs/ln-tabs.js" defer></script>
|
|
150
168
|
```
|
|
151
|
-
- **Active Source (ESM)**: Development source is located at [
|
|
169
|
+
- **Active Source (ESM)**: Development source is located at [components/ln-tabs/src/ln-tabs.js](file:///c:/laragon/www/ln-ashlar/components/ln-tabs/src/ln-tabs.js).
|
|
152
170
|
|
|
153
171
|
---
|
|
154
172
|
|
|
155
173
|
## Hash codec (ln-core shared)
|
|
156
174
|
|
|
157
175
|
ln-tabs uses the shared ln-core hash codec (`hashGet` / `hashSet` /
|
|
158
|
-
`hashParse` from `
|
|
176
|
+
`hashParse` from `components/ln-core/hash.js`) instead of a private parser. The
|
|
159
177
|
grammar and behaviour are unchanged — anchor-trigger groups still produce
|
|
160
178
|
`#nsKey:activeKey` fragments, still respond to `hashchange`, and still
|
|
161
179
|
support back/forward navigation.
|
|
@@ -172,7 +190,7 @@ No markup or API change — this is an internal implementation improvement.
|
|
|
172
190
|
|
|
173
191
|
## 🔧 Internals
|
|
174
192
|
|
|
175
|
-
Source: `
|
|
193
|
+
Source: `components/ln-tabs/ln-tabs.js`. Zero cross-component imports — only `ln-core` (`registerComponent`, `dispatch`).
|
|
176
194
|
|
|
177
195
|
### State
|
|
178
196
|
|
|
@@ -202,7 +220,7 @@ A non-empty `data-ln-tab` value wins if present; otherwise the `href` is split o
|
|
|
202
220
|
|
|
203
221
|
### Destroy
|
|
204
222
|
|
|
205
|
-
Idempotent. Detaches every click handler and clears their double-attach guards, detaches `hashchange` only if hash-enabled,
|
|
223
|
+
Idempotent. Detaches every click handler and clears their double-attach guards, detaches `hashchange` only if hash-enabled, deletes `dom.lnTabs`. Does NOT reset visual state (`data-active`/`aria-selected` survive), clear `localStorage`, or remove `data-ln-tabs-active` — a future re-init resumes from whatever is left.
|
|
206
224
|
|
|
207
225
|
### Failure modes
|
|
208
226
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@use '../../theme/config/mixins' as *;
|
|
2
|
+
|
|
3
|
+
[data-ln-debug] {
|
|
4
|
+
// 1. Anchor Tabs Namespace Check: URL hash mode (<a> triggers) requires an id or data-ln-tabs-key namespace
|
|
5
|
+
[data-ln-tabs]:has(a[data-ln-tab]):not([id]):not([data-ln-tabs-key]),
|
|
6
|
+
[data-ln-tabs]:has(a[href^="#"]):not([id]):not([data-ln-tabs-key]) {
|
|
7
|
+
@include dev-dom-error("Anchor tabs (URL hash mode) require an 'id' or 'data-ln-tabs-key' namespace on [data-ln-tabs].");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// 2. Tab Trigger Empty Key: button triggers must have a non-empty key
|
|
11
|
+
button[data-ln-tab=""] {
|
|
12
|
+
@include dev-inline-error("data-ln-tab key cannot be empty");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// 3. Tab Trigger Type: button triggers inside forms must be explicitly type="button" to prevent submit events
|
|
16
|
+
form button[data-ln-tab]:not([type="button"]) {
|
|
17
|
+
@include dev-inline-error("tab trigger within form must have type='button'");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 4. Tab Trigger Nesting Check: [data-ln-tab] must be nested inside a [data-ln-tabs] parent container
|
|
21
|
+
[data-ln-tab]:not([data-ln-tabs] *) {
|
|
22
|
+
@include dev-inline-error("[data-ln-tab] is orphaned (not inside a [data-ln-tabs] container)");
|
|
23
|
+
}
|
|
24
|
+
}
|