@livenetworks/ashlar 1.6.0 → 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/components/ln-confirm/README.md +216 -0
- 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 -167
- 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/README.md +0 -162
- 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
|
@@ -1,432 +1,443 @@
|
|
|
1
|
-
# ln-router
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 1. Philosophy & The Primitive Mindset
|
|
8
|
-
|
|
9
|
-
In `ln-ashlar`, the core design principle is **orthogonality**. Rather than creating a heavy router that handles data fetching, state management, and HTML sanitization, `ln-router` separates these concerns:
|
|
10
|
-
|
|
11
|
-
1. **The Route Mounter (JavaScript)**: The `ln-router` component only manages path matching, history states (`pushState`/`popstate`), accessibility focus shifting, and DOM swapping. It does not fetch data, sanitise markup, or touch database stores.
|
|
12
|
-
2. **The Declarative Layout (HTML)**: Route configurations and view templates live in HTML as `<template data-ln-route="...">` tags. The router never constructs HTML inside JS. It simply clones the templates into the designated outlet.
|
|
13
|
-
3. **Decoupled Data Binding (Coordinator)**: The coordinator listens to the `ln-router:navigated` event, retrieves params/query, fetches from a store or HTTP endpoint, and updates the mounted view using core helpers:
|
|
14
|
-
- **`fillTemplate(clone, data)`**: Replaces inline `{{ placeholder }}` text-node variables in the cloned view. Since it operates strictly on text nodes (via `TreeWalker`), it is 100% safe from reflected XSS.
|
|
15
|
-
- **`fill(el, data)`**: Performs attribute-driven binding (`data-ln-field` → `textContent`, `data-ln-attr` → `setAttribute`), which is also inherently secure.
|
|
16
|
-
|
|
17
|
-
> [!WARNING]
|
|
18
|
-
> **Reflected XSS Security**: The route coordinator is responsible for populating the view. When binding URL parameters (e.g., username in `/profile/:username`) or query parameters to the DOM, **never** use unsafe methods like `innerHTML` or `outerHTML`. Always use `fillTemplate`, `fill`, or set `textContent` directly to avoid reflected XSS injections. For more, see [Security Architecture](file:///c:/laragon/www/ln-ashlar/docs/architecture/security.md#safe-dom-interpolation).
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## 2. Minimal Blueprint
|
|
23
|
-
|
|
24
|
-
Route templates and outlets are declared in HTML. Triggers are plain same-origin `<a>` tags.
|
|
25
|
-
|
|
26
|
-
```html
|
|
27
|
-
<!-- Default Outlet (where views render by default) -->
|
|
28
|
-
<main data-ln-outlet></main>
|
|
29
|
-
|
|
30
|
-
<!-- Route Templates (lives anywhere, commonly grouped near outlet) -->
|
|
31
|
-
<template data-ln-route="/">
|
|
32
|
-
<article>
|
|
33
|
-
<h1>Home</h1>
|
|
34
|
-
<p>Welcome to Ashlar SPA.</p>
|
|
35
|
-
</article>
|
|
36
|
-
</template>
|
|
37
|
-
|
|
38
|
-
<template data-ln-route="/users">
|
|
39
|
-
<article>
|
|
40
|
-
<h1>Users List</h1>
|
|
41
|
-
<ul id="user-list"></ul>
|
|
42
|
-
</article>
|
|
43
|
-
</template>
|
|
44
|
-
|
|
45
|
-
<!-- Parameter route -->
|
|
46
|
-
<template data-ln-route="/users/:id" data-ln-route-title="User Details">
|
|
47
|
-
<article>
|
|
48
|
-
<h1 id="user-name-title">Loading User...</h1>
|
|
49
|
-
</article>
|
|
50
|
-
</template>
|
|
51
|
-
|
|
52
|
-
<!-- Catch-all fallback route -->
|
|
53
|
-
<template data-ln-route="*">
|
|
54
|
-
<article>
|
|
55
|
-
<h1>404 - Not Found</h1>
|
|
56
|
-
</article>
|
|
57
|
-
</template>
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Multi-Region Blueprint
|
|
61
|
-
|
|
62
|
-
```html
|
|
63
|
-
<!-- Primary outlet -->
|
|
64
|
-
<main data-ln-outlet></main>
|
|
65
|
-
|
|
66
|
-
<!-- Persistent sidebar — keeps state on param-only changes -->
|
|
67
|
-
<aside id="side" data-ln-route-keep></aside>
|
|
68
|
-
|
|
69
|
-
<!-- Primary region templates -->
|
|
70
|
-
<template data-ln-route="/users/:id">
|
|
71
|
-
<article><h1>User {{id}}</h1></article>
|
|
72
|
-
</template>
|
|
73
|
-
|
|
74
|
-
<!-- Sidebar templates -->
|
|
75
|
-
<template data-ln-route="/users/:id" data-ln-route-target="side">
|
|
76
|
-
<section>
|
|
77
|
-
<h2>Sidebar: User {{id}}</h2>
|
|
78
|
-
<input type="text" placeholder="Type something — survives param changes">
|
|
79
|
-
</section>
|
|
80
|
-
</template>
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
## 3. The Declarative API & State Contract
|
|
86
|
-
|
|
87
|
-
There is no need to manually instantiate or initialize the router. Discovered `<template data-ln-route>` elements register themselves automatically.
|
|
88
|
-
|
|
89
|
-
```js
|
|
90
|
-
import { router } from '../ln-router';
|
|
91
|
-
|
|
92
|
-
// Programmatic navigation
|
|
93
|
-
router.navigate('/users/42?tab=profile');
|
|
94
|
-
|
|
95
|
-
// Programmatic replace state
|
|
96
|
-
router.replace('/dashboard');
|
|
97
|
-
|
|
98
|
-
// Read-only state query
|
|
99
|
-
const current = router.current();
|
|
100
|
-
// Returns:
|
|
101
|
-
// {
|
|
102
|
-
// path: '/users/42?tab=profile',
|
|
103
|
-
// params: { id: '42' }, // primary region params
|
|
104
|
-
// query: { tab: 'profile' },
|
|
105
|
-
// route: {
|
|
106
|
-
// pattern: '/users/:id',
|
|
107
|
-
// target: null, // The string ID of the custom target outlet, or null if using default
|
|
108
|
-
// title: 'User Details',
|
|
109
|
-
// templateNode: HTMLTemplateElement
|
|
110
|
-
// },
|
|
111
|
-
// regions: Map { //
|
|
112
|
-
// '__primary__' => { route: {...}, params: { id: '42' } },
|
|
113
|
-
// 'side' => { route: {...}, params: { id: '42' } } | null
|
|
114
|
-
// }
|
|
115
|
-
// }
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
- `data-ln-
|
|
131
|
-
- `data-ln-
|
|
132
|
-
- `data-ln-route-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
`
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
> [!NOTE]
|
|
241
|
-
> **
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
<!--
|
|
303
|
-
<
|
|
304
|
-
|
|
305
|
-
<!--
|
|
306
|
-
<
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
-
|
|
327
|
-
- `
|
|
328
|
-
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
>
|
|
335
|
-
>
|
|
336
|
-
>
|
|
337
|
-
>
|
|
338
|
-
>
|
|
339
|
-
>
|
|
340
|
-
>
|
|
341
|
-
>
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
-
|
|
353
|
-
- **
|
|
354
|
-
- **
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
1
|
+
# ln-router
|
|
2
|
+
|
|
3
|
+
> Declared globally with route definitions in `<template data-ln-route="...">` elements rendering into `<main data-ln-outlet>` or auxiliary targets (`data-ln-route-target`). It intercepts click events on same-origin `<a>` tags and listens for `popstate`, matches the URL against specificity-ranked route patterns, and clones templates into designated outlets with `document.startViewTransition()` support. It unmounts old views by calling `.destroy()` on child components, leaving data fetching and view filling to route coordinators.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Philosophy & The Primitive Mindset
|
|
8
|
+
|
|
9
|
+
In `ln-ashlar`, the core design principle is **orthogonality**. Rather than creating a heavy router that handles data fetching, state management, and HTML sanitization, `ln-router` separates these concerns:
|
|
10
|
+
|
|
11
|
+
1. **The Route Mounter (JavaScript)**: The `ln-router` component only manages path matching, history states (`pushState`/`popstate`), accessibility focus shifting, and DOM swapping. It does not fetch data, sanitise markup, or touch database stores.
|
|
12
|
+
2. **The Declarative Layout (HTML)**: Route configurations and view templates live in HTML as `<template data-ln-route="...">` tags. The router never constructs HTML inside JS. It simply clones the templates into the designated outlet.
|
|
13
|
+
3. **Decoupled Data Binding (Coordinator)**: The coordinator listens to the `ln-router:navigated` event, retrieves params/query, fetches from a store or HTTP endpoint, and updates the mounted view using core helpers:
|
|
14
|
+
- **`fillTemplate(clone, data)`**: Replaces inline `{{ placeholder }}` text-node variables in the cloned view. Since it operates strictly on text nodes (via `TreeWalker`), it is 100% safe from reflected XSS.
|
|
15
|
+
- **`fill(el, data)`**: Performs attribute-driven binding (`data-ln-field` → `textContent`, `data-ln-attr` → `setAttribute`), which is also inherently secure.
|
|
16
|
+
|
|
17
|
+
> [!WARNING]
|
|
18
|
+
> **Reflected XSS Security**: The route coordinator is responsible for populating the view. When binding URL parameters (e.g., username in `/profile/:username`) or query parameters to the DOM, **never** use unsafe methods like `innerHTML` or `outerHTML`. Always use `fillTemplate`, `fill`, or set `textContent` directly to avoid reflected XSS injections. For more, see [Security Architecture](file:///c:/laragon/www/ln-ashlar/docs/architecture/security.md#safe-dom-interpolation).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 2. Minimal Blueprint
|
|
23
|
+
|
|
24
|
+
Route templates and outlets are declared in HTML. Triggers are plain same-origin `<a>` tags.
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<!-- Default Outlet (where views render by default) -->
|
|
28
|
+
<main data-ln-outlet></main>
|
|
29
|
+
|
|
30
|
+
<!-- Route Templates (lives anywhere, commonly grouped near outlet) -->
|
|
31
|
+
<template data-ln-route="/">
|
|
32
|
+
<article>
|
|
33
|
+
<h1>Home</h1>
|
|
34
|
+
<p>Welcome to Ashlar SPA.</p>
|
|
35
|
+
</article>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<template data-ln-route="/users">
|
|
39
|
+
<article>
|
|
40
|
+
<h1>Users List</h1>
|
|
41
|
+
<ul id="user-list"></ul>
|
|
42
|
+
</article>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<!-- Parameter route -->
|
|
46
|
+
<template data-ln-route="/users/:id" data-ln-route-title="User Details">
|
|
47
|
+
<article>
|
|
48
|
+
<h1 id="user-name-title">Loading User...</h1>
|
|
49
|
+
</article>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<!-- Catch-all fallback route -->
|
|
53
|
+
<template data-ln-route="*">
|
|
54
|
+
<article>
|
|
55
|
+
<h1>404 - Not Found</h1>
|
|
56
|
+
</article>
|
|
57
|
+
</template>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Multi-Region Blueprint
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<!-- Primary outlet -->
|
|
64
|
+
<main data-ln-outlet></main>
|
|
65
|
+
|
|
66
|
+
<!-- Persistent sidebar — keeps state on param-only changes -->
|
|
67
|
+
<aside id="side" data-ln-route-keep></aside>
|
|
68
|
+
|
|
69
|
+
<!-- Primary region templates -->
|
|
70
|
+
<template data-ln-route="/users/:id">
|
|
71
|
+
<article><h1>User {{id}}</h1></article>
|
|
72
|
+
</template>
|
|
73
|
+
|
|
74
|
+
<!-- Sidebar templates -->
|
|
75
|
+
<template data-ln-route="/users/:id" data-ln-route-target="side">
|
|
76
|
+
<section>
|
|
77
|
+
<h2>Sidebar: User {{id}}</h2>
|
|
78
|
+
<input type="text" placeholder="Type something — survives param changes">
|
|
79
|
+
</section>
|
|
80
|
+
</template>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 3. The Declarative API & State Contract
|
|
86
|
+
|
|
87
|
+
There is no need to manually instantiate or initialize the router. Discovered `<template data-ln-route>` elements register themselves automatically.
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
import { router } from '../ln-router';
|
|
91
|
+
|
|
92
|
+
// Programmatic navigation
|
|
93
|
+
router.navigate('/users/42?tab=profile');
|
|
94
|
+
|
|
95
|
+
// Programmatic replace state
|
|
96
|
+
router.replace('/dashboard');
|
|
97
|
+
|
|
98
|
+
// Read-only state query
|
|
99
|
+
const current = router.current();
|
|
100
|
+
// Returns:
|
|
101
|
+
// {
|
|
102
|
+
// path: '/users/42?tab=profile',
|
|
103
|
+
// params: { id: '42' }, // primary region params
|
|
104
|
+
// query: { tab: 'profile' },
|
|
105
|
+
// route: { // null on an auxiliary-only navigation
|
|
106
|
+
// pattern: '/users/:id',
|
|
107
|
+
// target: null, // The string ID of the custom target outlet, or null if using default
|
|
108
|
+
// title: 'User Details',
|
|
109
|
+
// templateNode: HTMLTemplateElement
|
|
110
|
+
// },
|
|
111
|
+
// regions: Map { // per-region snapshot
|
|
112
|
+
// '__primary__' => { route: {...}, params: { id: '42' } },
|
|
113
|
+
// 'side' => { route: {...}, params: { id: '42' } } | null
|
|
114
|
+
// }
|
|
115
|
+
// }
|
|
116
|
+
// current() returns null only when the router has never navigated. Once it
|
|
117
|
+
// has, route is null exactly when the primary region has no match for the
|
|
118
|
+
// current URL — an auxiliary-only navigation.
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Target Outlet Resolution Fallback
|
|
122
|
+
When a route matches, the router resolves the target DOM element to render into by checking the following hierarchy:
|
|
123
|
+
1. An explicit element ID specified by `data-ln-route-target="..."` on the `<template>`.
|
|
124
|
+
2. A default outlet element marked with the `data-ln-outlet` attribute.
|
|
125
|
+
3. The first `<main>` element found in the document.
|
|
126
|
+
|
|
127
|
+
If the **primary** outlet cannot be resolved, the router logs a `console.warn` and aborts the navigation, leaving the current page state intact. If an **auxiliary** region's target element cannot be resolved, the router warns and skips only that region — the remaining regions still render.
|
|
128
|
+
|
|
129
|
+
### Attributes
|
|
130
|
+
- `data-ln-route="pattern"`: Declares a route on a `<template>`. Supports static segments (`/users`), parameters (`/users/:id`), and wildcards (`*`).
|
|
131
|
+
- `data-ln-route-target="id"`: Sets an explicit element ID to render this route into. Templates with no `data-ln-route-target` belong to the **primary region** (default outlet).
|
|
132
|
+
- `data-ln-route-title="text"`: Sets `document.title` on successful navigation (primary region only).
|
|
133
|
+
- `data-ln-outlet`: Marks the default outlet element (falls back to the first `<main>` element if omitted).
|
|
134
|
+
- `data-ln-router-hydrate`: Placed on the outlet element to signal that initial SSR (Server-Side Rendered) content is already present, letting the router skip the initial clone and register events cleanly.
|
|
135
|
+
- `data-ln-route-keep`: Placed on a **target element** (not a template). When present, the router skips teardown and DOM swap for that region if the matched template is the same as the one currently mounted — surviving param-only URL changes while preserving DOM state (inputs, scroll, open panels). When the matched template changes, teardown and swap proceed normally. `data-ln-route-keep` also opts an auxiliary region out of auto-clear: without it, a region left unmatched by the current URL is emptied automatically; with it, unmatched content is left in place. Ignored on the primary outlet (the primary always re-renders).
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 4. Transition Events
|
|
140
|
+
|
|
141
|
+
All events bubble from the target outlet element.
|
|
142
|
+
|
|
143
|
+
| Event | Cancelable | Detail | Dispatched When |
|
|
144
|
+
|---|---|---|---|
|
|
145
|
+
| **`ln-router:before-navigate`** | **Yes** | `{ from, to, params, query }` | Fired **once per navigation with at least one region match** on the primary outlet, before any region swap or clear. `preventDefault()` aborts the entire navigation (no history update, no region swaps). |
|
|
146
|
+
| **`ln-router:navigated`** | No | `{ path, params, query, route, target, region }` | Fired **per region that actually mounted a template**. `region` is `'__primary__'` for the default outlet, or the target element's id string for auxiliary regions. All existing keys (`path, params, query, route, target`) remain present for backward compatibility. |
|
|
147
|
+
| **`ln-router:not-found`** | No | `{ path }` | When **no region matched** the current path (and no catch-all `*` exists anywhere). DOM is left untouched. |
|
|
148
|
+
|
|
149
|
+
### Timing guarantee
|
|
150
|
+
|
|
151
|
+
The initial render runs after the full route scan completes — all `<template data-ln-route>` elements in the document are registered before the first navigation fires.
|
|
152
|
+
The initial `ln-router:navigated` (on a matched route) and boot-path `ln-router:not-found` (on no match) dispatches are **deferred one microtask** via `queueMicrotask`. This ensures that listeners registered during the same `DOMContentLoaded` burst — i.e. in a `<script defer>` tag that appears after the router bundle — always receive the boot event. When the View Transitions API is available, the deferral is handled via the VT callback rather than literally `queueMicrotask`, but the guarantee is identical — listeners in a later `defer` script still receive the boot event.
|
|
153
|
+
|
|
154
|
+
All subsequent navigations (link clicks, `popstate`, programmatic `router.navigate()`/`router.replace()`) dispatch **synchronously**; no deferral applies once the boot phase is complete.
|
|
155
|
+
|
|
156
|
+
**Consumers no longer need a `current()` boot-replay block.** The pattern:
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
// OBSOLETE — no longer needed
|
|
160
|
+
const booted = window.lnRouter && window.lnRouter.current();
|
|
161
|
+
if (booted && !navigatedFired) {
|
|
162
|
+
mountRoute(booted);
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
can be replaced by simply attaching a `ln-router:navigated` listener — it will fire even if the router booted before the listener was added.
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
// Example: Block navigation out of unsaved forms
|
|
170
|
+
document.addEventListener('ln-router:before-navigate', (e) => {
|
|
171
|
+
if (hasUnsavedChanges && !confirm('Discard changes?')) {
|
|
172
|
+
e.preventDefault(); // Cancels transition and keeps current view
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## 5. Fragment-only popstate guard
|
|
178
|
+
|
|
179
|
+
A `popstate` event whose new URL differs from the current SPA state **only in
|
|
180
|
+
the fragment** (path and query are identical) is **skipped** — no navigation,
|
|
181
|
+
no outlet teardown, no `ln-router:navigated` event fires.
|
|
182
|
+
|
|
183
|
+
This is what makes hash-bound overlays (like `ln-modal`, which uses its
|
|
184
|
+
element `id` as the hash namespace) work cleanly with Back/Forward:
|
|
185
|
+
|
|
186
|
+
1. User opens a hash-bound modal → URL gets `#id:value`.
|
|
187
|
+
2. User presses **Back** → browser fires `popstate` to the URL without the
|
|
188
|
+
fragment.
|
|
189
|
+
3. ln-router detects path + query are identical → **skips navigation** → the
|
|
190
|
+
under-modal view (table, scroll position, search/filter state) is
|
|
191
|
+
completely preserved.
|
|
192
|
+
4. `hashchange` fires → ln-modal's `_onHashChange` sees the segment is gone
|
|
193
|
+
→ `data-ln-modal="close"` → modal closes cleanly.
|
|
194
|
+
|
|
195
|
+
Real path/query Back/Forward navigations and link clicks are unaffected. The
|
|
196
|
+
`router.current()` return value is left coherent (unchanged) on a skipped
|
|
197
|
+
fragment popstate — no stale state is introduced.
|
|
198
|
+
|
|
199
|
+
This guard is implemented via `_queryEqual` in the router source.
|
|
200
|
+
|
|
201
|
+
## 6. Accessibility & Screen Reader Continuity
|
|
202
|
+
|
|
203
|
+
`ln-router` shifts focus automatically on each user-initiated navigation (click or popstate back/forward) to ensure compatibility with assistive technologies (screen readers). Focus is **not** shifted on the initial page load (boot render) to avoid an unexpected visible focus ring without user action.
|
|
204
|
+
1. **Focus Shifting** (user navigation only): The router looks for the first heading element (`h1` through `h6`) in the newly mounted template. If found, it dynamically assigns `tabindex="-1"` and calls `.focus()` on it. If no heading element is present, focus is shifted to the outlet container itself.
|
|
205
|
+
2. **Title Updates**: When `data-ln-route-title` is defined, the router updates `document.title`, which is announced by screen readers upon focus shifting.
|
|
206
|
+
3. **Scroll Management** (user navigation only): The router automatically scrolls the target element into view via `.scrollIntoView({ block: 'start', behavior: 'instant' })` to reset the viewport for the new content.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## 7. Server Integration & Deep Links
|
|
211
|
+
|
|
212
|
+
Since `ln-router` operates client-side using history `pushState`, direct access or page refresh on a deep path (e.g., direct opening of `/users/42`) will bypass the client-side router and hit the web server directly. This results in a server 404 error if not configured correctly.
|
|
213
|
+
|
|
214
|
+
### 1. Server Catch-All Fallback
|
|
215
|
+
Your web server must serve the main shell entrypoint (e.g., `index.html` or main layout view) for all deep URLs, allowing the client-side router to boot and handle the path.
|
|
216
|
+
|
|
217
|
+
In **Laravel**, define a fallback route at the end of `routes/web.php`:
|
|
218
|
+
```php
|
|
219
|
+
Route::fallback(function () {
|
|
220
|
+
return view('app'); // returns your main blade layout shell
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
In **Nginx**, configure the fallback in your site configuration:
|
|
225
|
+
```nginx
|
|
226
|
+
location / {
|
|
227
|
+
try_files $uri $uri/ /index.html;
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 2. Relative Assets & `<base>` Tag
|
|
232
|
+
If your app references assets using relative paths, deep paths will attempt to load assets relative to the current route path (e.g., loading `/users/assets/app.js` instead of `/assets/app.js`). To prevent this:
|
|
233
|
+
- **Configure the base tag**: Add `<base href="/">` in the `<head>` of your main HTML shell.
|
|
234
|
+
- **Or use absolute asset paths**: Ensure script and link tags point to absolute URLs (e.g., `/dist/main.js` instead of `dist/main.js`).
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## 8. Routing Model — Parallel Regions, Not Nested Routes
|
|
239
|
+
|
|
240
|
+
> [!NOTE]
|
|
241
|
+
> **No Nested Routing**: `ln-router` does not support nested outlets or child routes (rendering a sub-view inside an already-routed parent). All routes are flat.
|
|
242
|
+
|
|
243
|
+
> [!NOTE]
|
|
244
|
+
> **Multi-region routing is PARALLEL (sibling), not nested.** When a single URL simultaneously paints a sidebar region and a main region, both regions are siblings at the page level — neither is a child of the other's outlet. The "No Nested Routing" doctrine applies: there is no concept of a parent route whose outlet contains another router outlet.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 9. Integration Patterns
|
|
249
|
+
|
|
250
|
+
### Coordinator Data Binding (Secure Interpolation)
|
|
251
|
+
Coordinators listen to `ln-router:navigated`, fetch data, and populate the view. Use `fillTemplate` for safe `{{ placeholder }}` text-node substitutions:
|
|
252
|
+
|
|
253
|
+
```html
|
|
254
|
+
<template data-ln-route="/profile/:username">
|
|
255
|
+
<section>
|
|
256
|
+
<h1>Profile: {{username}}</h1>
|
|
257
|
+
</section>
|
|
258
|
+
</template>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
```js
|
|
262
|
+
import { fillTemplate } from '../ln-core';
|
|
263
|
+
|
|
264
|
+
document.addEventListener('ln-router:navigated', function (e) {
|
|
265
|
+
if (e.detail.route.pattern !== '/profile/:username') return;
|
|
266
|
+
|
|
267
|
+
const username = e.detail.params.username;
|
|
268
|
+
// Populate read-only placeholders like {{username}} inside the outlet
|
|
269
|
+
// fillTemplate walks text nodes and is completely safe from XSS.
|
|
270
|
+
fillTemplate(e.detail.target, { username: username });
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## 10. Integration & Source Files
|
|
277
|
+
|
|
278
|
+
`ln-ashlar` is a source-only package. Products should bundle it from source using their asset compilation pipelines (Vite, Laravel Mix, Webpack, etc.):
|
|
279
|
+
|
|
280
|
+
- **Vite / ES Modules**:
|
|
281
|
+
```js
|
|
282
|
+
import '@livenetworks/ashlar/components/ln-router';
|
|
283
|
+
```
|
|
284
|
+
- **Git Submodule ESM import**:
|
|
285
|
+
```js
|
|
286
|
+
import 'resources/ln-ashlar/components/ln-router/src/ln-router.js';
|
|
287
|
+
```
|
|
288
|
+
- **Development Demo**:
|
|
289
|
+
The compiled build in `demo/dist/ln-ashlar.iife.js` exists solely for running the local repository demo environment and should not be referenced in production consumer applications.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 11. Multi-Region Routing
|
|
294
|
+
|
|
295
|
+
One URL can simultaneously paint multiple sibling regions. Each region independently maintains its own route table and matches the current URL.
|
|
296
|
+
|
|
297
|
+
### Declaring regions
|
|
298
|
+
|
|
299
|
+
Regions are ordinary DOM elements. Each is identified by an `id`. Route templates target a region via `data-ln-route-target="<id>"`. Templates with no `data-ln-route-target` belong to the **primary region** (the default outlet).
|
|
300
|
+
|
|
301
|
+
```html
|
|
302
|
+
<!-- Primary outlet (default region) -->
|
|
303
|
+
<main data-ln-outlet></main>
|
|
304
|
+
|
|
305
|
+
<!-- Auxiliary region: sidebar -->
|
|
306
|
+
<aside id="side" data-ln-route-keep></aside>
|
|
307
|
+
|
|
308
|
+
<!-- Primary templates -->
|
|
309
|
+
<template data-ln-route="/users/:id" data-ln-route-title="User Details">
|
|
310
|
+
<article><h1>User {{id}}</h1></article>
|
|
311
|
+
</template>
|
|
312
|
+
|
|
313
|
+
<!-- Sidebar templates — same or different patterns -->
|
|
314
|
+
<template data-ln-route="/users/:id" data-ln-route-target="side">
|
|
315
|
+
<section><h2>Sidebar for User {{id}}</h2></section>
|
|
316
|
+
</template>
|
|
317
|
+
|
|
318
|
+
<!-- Sidebar catch-all — shown on all other routes -->
|
|
319
|
+
<template data-ln-route="*" data-ln-route-target="side">
|
|
320
|
+
<section><p>Select a user to see details.</p></section>
|
|
321
|
+
</template>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### The primary region
|
|
325
|
+
|
|
326
|
+
The primary region is the default outlet (`[data-ln-outlet]` or first `<main>`). Every region matches the URL and swaps independently; the primary is authoritative only for:
|
|
327
|
+
- `router.current()`'s `route`/`params` fields
|
|
328
|
+
- The before-navigate host element (it is a stable container, always resolvable, so it stays the dispatch target even on an auxiliary-only navigation)
|
|
329
|
+
|
|
330
|
+
Title updates and focus/scroll management belong to the navigation's **owner** region: the primary if it swapped, otherwise the first region (in registration order) that swapped. `ln-router:before-navigate` fires once per navigation as long as at least one region matched; `ln-router:not-found` fires only when no region matched at all.
|
|
331
|
+
|
|
332
|
+
`data-ln-route-keep` is ignored on the primary region: the primary always tears down and re-renders on every matched navigation.
|
|
333
|
+
|
|
334
|
+
> [!IMPORTANT]
|
|
335
|
+
> **Each region matches the URL independently.** A route targeting an
|
|
336
|
+
> auxiliary region (`data-ln-route-target="…"`) does not compete with primary
|
|
337
|
+
> routes for a single "best match" — every region runs its own match. If you
|
|
338
|
+
> have a route like `/users/:id/logs` that targets only an auxiliary region,
|
|
339
|
+
> the primary region will independently try to match `/users/42/logs` against
|
|
340
|
+
> its own templates. If it has none, it simply auto-clears (or is left alone
|
|
341
|
+
> if it carries `data-ln-route-keep`) — the auxiliary region still renders.
|
|
342
|
+
> This is a deliberate consequence of the parallel-regions model.
|
|
343
|
+
|
|
344
|
+
### `data-ln-route-keep` — persistent regions
|
|
345
|
+
|
|
346
|
+
Add `data-ln-route-keep` to any target element to make it a **keep region**.
|
|
347
|
+
|
|
348
|
+
```html
|
|
349
|
+
<aside id="side" data-ln-route-keep></aside>
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Keep-region logic:
|
|
353
|
+
- **Same template matches again** (e.g. param-only change `/users/42` → `/users/43`): the region is left completely untouched. No teardown, no DOM swap, no `ln-router:navigated` event. Input values, scroll position, and open panels survive.
|
|
354
|
+
- **Different template matches** (e.g. navigating to a route that has a different sidebar template): normal teardown + DOM swap + `ln-router:navigated` event.
|
|
355
|
+
- **No match for this URL**: the region is left as-is (untouched content persists). This is the entire purpose of `data-ln-route-keep` — without it, an unmatched region auto-clears (see below).
|
|
356
|
+
|
|
357
|
+
Keep is a property of the **region target element**, not of the template.
|
|
358
|
+
|
|
359
|
+
### Auto-clear — unmatched regions empty themselves
|
|
360
|
+
|
|
361
|
+
A region without `data-ln-route-keep` that has no match for the current URL is
|
|
362
|
+
automatically emptied: existing components inside it are torn down and its
|
|
363
|
+
content is replaced with nothing. This applies to regions with content
|
|
364
|
+
already present (nothing happens to an already-empty region), and it does not
|
|
365
|
+
fire `ln-router:navigated` — that event means "mounted a template", and a
|
|
366
|
+
clear mounts nothing. `data-ln-route-keep` is the opt-out: an unmatched keep
|
|
367
|
+
region is left exactly as it was.
|
|
368
|
+
|
|
369
|
+
### `navigated` event — per swapped region
|
|
370
|
+
|
|
371
|
+
`ln-router:navigated` fires once per region that actually swapped. The `region` key identifies which region fired:
|
|
372
|
+
|
|
373
|
+
```js
|
|
374
|
+
document.addEventListener('ln-router:navigated', function (e) {
|
|
375
|
+
const { path, params, query, route, target, region } = e.detail;
|
|
376
|
+
|
|
377
|
+
if (region === '__primary__') {
|
|
378
|
+
// Primary outlet navigated
|
|
379
|
+
} else if (region === 'side') {
|
|
380
|
+
// Sidebar region swapped
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
The `region` value is `'__primary__'` for the default outlet, or the target element's `id` string for auxiliary regions.
|
|
386
|
+
|
|
387
|
+
**Backward compatibility:** all existing keys (`path, params, query, route, target`) remain in the event detail unchanged. Single-outlet pages continue to receive one `navigated` event with the same payload they always have.
|
|
388
|
+
|
|
389
|
+
### `router.current()` — multi-region state
|
|
390
|
+
|
|
391
|
+
```js
|
|
392
|
+
const state = router.current();
|
|
393
|
+
// {
|
|
394
|
+
// path: '/users/42',
|
|
395
|
+
// params: { id: '42' }, // primary region params
|
|
396
|
+
// query: {},
|
|
397
|
+
// route: { ... }, // primary region matched route
|
|
398
|
+
// regions: Map {
|
|
399
|
+
// '__primary__' => { route: {...}, params: { id: '42' } },
|
|
400
|
+
// 'side' => { route: {...}, params: { id: '42' } },
|
|
401
|
+
// }
|
|
402
|
+
// }
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Atomicity
|
|
406
|
+
|
|
407
|
+
All region swaps for a single navigation are wrapped in a single `document.startViewTransition` call. History push/replace happens once. `before-navigate` fires once.
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## 🔧 Internals
|
|
412
|
+
|
|
413
|
+
Source: `components/ln-router/src/ln-router.js`. The region clear/swap/owner decision is a pure function, `planRegions`, in `components/ln-router/src/router-model.js` — it does no DOM access and is covered directly by `tests/ln-router.test.js`.
|
|
414
|
+
|
|
415
|
+
### Navigation pipeline (order matters)
|
|
416
|
+
|
|
417
|
+
1. Strip hash/query, collapse trailing slashes.
|
|
418
|
+
2. Every region in `regionRegistry` matches independently against its own sorted route list. DOM-reading descriptors are built for every region (matched or not), then handed to the pure `planRegions` (`router-model.js`) for the decision. If no region matched, the router dispatches `ln-router:not-found` and stops — this is checked before anything is cleared, so a 404 never wipes the page.
|
|
419
|
+
3. `ln-router:before-navigate` fires once, on the primary outlet, as long as at least one region matched; `preventDefault()` aborts the whole navigation before any region swap, clear, or history update.
|
|
420
|
+
4. `planRegions` classifies every region with a resolvable target into `clears` (unmatched, no `data-ln-route-keep`, has content), `swaps` (matched, and not a keep-region already showing the matched template), or neither (keep-region already correct, or unmatched-and-empty).
|
|
421
|
+
5. History push/replace happens once.
|
|
422
|
+
6. All planned clears and swaps run sequentially inside a single `document.startViewTransition` call — clears teardown and `replaceChildren()` with nothing; swaps teardown, clone, `replaceChildren`, record in `mountedTemplates`. Title/focus/scroll apply only to the plan's owner region (the primary if it swapped, otherwise the first region — in registration order — that swapped).
|
|
423
|
+
7. `ln-router:navigated` fires per region that mounted a template (`region` key = `'__primary__'` or the target id). Clears never fire it.
|
|
424
|
+
|
|
425
|
+
### Registry & specificity
|
|
426
|
+
|
|
427
|
+
`regionRegistry: Map<regionKey, { routes: Map<pattern, meta>, sorted: meta[] }>` — one route table per region, each independently sorted static > param > wildcard, first-declared wins ties. A duplicate `(pattern, regionKey)` registration is dropped with `console.warn`; the same pattern targeting two different regions is legal.
|
|
428
|
+
|
|
429
|
+
### Teardown & teleport cleanup
|
|
430
|
+
|
|
431
|
+
Only regions actually in `plan.clears` or `plan.swaps` are torn down — a keep-region that matches neither list is never touched. Before `replaceChildren()` empties an outlet (swap or clear), the router walks its descendants for any element carrying an `ln*` property with a `destroy()` method and calls it — this is what prevents leaked window/document listeners and teleported overlay trees (e.g. an open popover in `<body>` whose trigger lived in the outlet) from surviving.
|
|
432
|
+
|
|
433
|
+
### Per-region persistence (keep regions)
|
|
434
|
+
|
|
435
|
+
A `WeakMap<Element, HTMLTemplateElement>` (`mountedTemplates`) tracks the currently-mounted template per target. A keep-region skips its swap only when the newly matched template node is the *same* node already mounted there — never for the primary region, which always re-renders.
|
|
436
|
+
|
|
437
|
+
### SSR hydration
|
|
438
|
+
|
|
439
|
+
On boot, a region whose target carries `data-ln-router-hydrate` and already has children skips its initial clone swap — the SSR markup is left in place, listeners are wired, and `ln-router:navigated` still fires so a coordinator can bind/fill it.
|
|
440
|
+
|
|
441
|
+
### Boot timing
|
|
442
|
+
|
|
443
|
+
A module-level `_booting` flag is true only during the router's own initial `_navigate` call inside `_boot()`. While true, `ln-router:navigated`/`not-found` dispatches are queued via `queueMicrotask` instead of firing synchronously — guaranteeing that listeners registered later in the same `defer`-script burst still receive the boot event. Every subsequent navigation dispatches synchronously.
|