@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
|
@@ -1,124 +1,127 @@
|
|
|
1
|
-
# ln-autosave
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
</
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
form.lnAutosave.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
| `ln-autosave:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
1
|
+
# ln-autosave
|
|
2
|
+
|
|
3
|
+
> Applied to a `<form id="..." data-ln-autosave>`. On initialization, it reads draft JSON from `localStorage.getItem(key)`
|
|
4
|
+
> and populates form fields, dispatching synthetic `input` and `change` events. On field `focusout` or `change`, it serializes
|
|
5
|
+
> form values — strictly excluding passwords (`input[type="password"]`) and `[data-ln-autosave-exclude]` — and saves them
|
|
6
|
+
> via `localStorage.setItem(key, json)`. On form `submit`, `reset`, or click on `[data-ln-autosave-clear]`, it calls `localStorage.removeItem(key)` to purge the draft.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 🧭 Philosophy & Architecture
|
|
11
|
+
|
|
12
|
+
1. **Synchronous local storage:** Unlike async network databases, `ln-autosave` uses browser `localStorage`. This ensures drafts are written instantly on field blur/leave (`focusout` / `change`), eliminating race conditions during page changes.
|
|
13
|
+
2. **Zero-Keystroke flood:** By default, writing only triggers on field blur. For continuous typing fields (like editors), an opt-in debounced input listener can be configured.
|
|
14
|
+
3. **Decoupled Event Restoration:** restored values are applied by dispatching standard synthetic `input` and `change` events. This ensures sibling primitives (such as `ln-validate` and `ln-autoresize`) re-evaluate automatically.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 📦 Minimal Blueprint
|
|
19
|
+
|
|
20
|
+
```html
|
|
21
|
+
<form id="profile-edit" data-ln-autosave>
|
|
22
|
+
<div class="form-element">
|
|
23
|
+
<label for="bio">Biography</label>
|
|
24
|
+
<textarea id="bio" name="bio" data-ln-autoresize rows="1"></textarea>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<ul class="form-actions">
|
|
28
|
+
<li><button type="button" data-ln-autosave-clear>Cancel (clear draft)</button></li>
|
|
29
|
+
<li><button type="submit">Save</button></li>
|
|
30
|
+
</ul>
|
|
31
|
+
</form>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
> [!IMPORTANT]
|
|
35
|
+
> The form **must** have a unique `id` or you must specify a custom identifier in `data-ln-autosave="identifier"`. The final storage key is scoped uniquely as `ln-autosave:{pathname}:{identifier}`.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 🛠️ Declarative API Contract
|
|
40
|
+
|
|
41
|
+
### HTML Attributes
|
|
42
|
+
|
|
43
|
+
| Attribute | Elements | Description |
|
|
44
|
+
| :--- | :--- | :--- |
|
|
45
|
+
| `data-ln-autosave` | `<form>` | Persistence marker. Value overrides the form ID lookup. |
|
|
46
|
+
| `data-ln-autosave-clear` | `<button>` | Click delegate. Instantly purges the localStorage entry. |
|
|
47
|
+
| `data-ln-autosave-debounce-input="ms"` | `<form>` | Opt-in. Saves on idle keystrokes (defaults to 1000ms if empty). |
|
|
48
|
+
|
|
49
|
+
### JS API
|
|
50
|
+
|
|
51
|
+
Access the persistence instance directly via the `lnAutosave` property on the form element:
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
const form = document.getElementById('profile-edit');
|
|
55
|
+
|
|
56
|
+
// 1. Back-reference properties
|
|
57
|
+
const storageKey = form.lnAutosave.key; // "ln-autosave:/path:profile-edit"
|
|
58
|
+
|
|
59
|
+
// 2. Clean up listeners and pending timers
|
|
60
|
+
form.lnAutosave.destroy();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## ⚡ DOM Events
|
|
66
|
+
|
|
67
|
+
### Emitted
|
|
68
|
+
|
|
69
|
+
| Event | Bubbles | Payload | Description |
|
|
70
|
+
| :--- | :--- | :--- | :--- |
|
|
71
|
+
| `ln-autosave:before-restore` | Yes (Cancelable) | `{ target, data }` | Fires before applying draft. Call `e.preventDefault()` to abort. |
|
|
72
|
+
| `ln-autosave:restored` | Yes | `{ target, data }` | Dispatched after populated values and synthetic events are sent. |
|
|
73
|
+
| `ln-autosave:saved` | Yes | `{ target, data }` | Dispatched on successful localStorage save. |
|
|
74
|
+
| `ln-autosave:cleared` | Yes | `{ target }` | Dispatched after localStorage item removal. |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## ⚠️ Common Pitfalls
|
|
79
|
+
|
|
80
|
+
- **Stale Drafts Overwriting Server Data:** On pages where the database has rendered new state, draft restores must be aborted:
|
|
81
|
+
```javascript
|
|
82
|
+
document.addEventListener('ln-autosave:before-restore', function (e) {
|
|
83
|
+
if (e.target.dataset.hasServerData === 'true') {
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
localStorage.removeItem(e.target.lnAutosave.key);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
- **ESC Modal Dismissals:** If a user closes a modal using the ESC key, no click triggers on cancel buttons. Wire draft cleanup to the modal's closed event manually:
|
|
90
|
+
```javascript
|
|
91
|
+
modal.addEventListener('ln-modal:closed', () => {
|
|
92
|
+
localStorage.removeItem(form.lnAutosave.key);
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
- **Excluded Inputs:** `ln-autosave` automatically ignores disabled inputs, button elements, `type="file"`, `type="password"`, inputs missing a `name` attribute, and any element matching `[data-ln-autosave-exclude]`.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 🔧 Internals
|
|
100
|
+
|
|
101
|
+
Source: `components/ln-autosave/ln-autosave.js` (~150 lines). No `.scss` file — the component has no visual surface, no class it sets, no CSS-driving attribute.
|
|
102
|
+
|
|
103
|
+
### State
|
|
104
|
+
|
|
105
|
+
Per-form instance (`form.lnAutosave`): `dom`, `key` (resolved once at construction — `ln-autosave:{pathname}:{identifier}`; mutating the attribute, `form.id`, or navigating via the History API afterwards does NOT re-key — destroy and re-mount to switch), and the bound handlers (`_onFocusout`, `_onChange`, `_onSubmit`, `_onReset`, `_onClearClick`, plus `_onInput` only when debounce is opted in) held so `destroy()` can pass matching references to `removeEventListener`.
|
|
106
|
+
|
|
107
|
+
### Storage key
|
|
108
|
+
|
|
109
|
+
`_getStorageKey` resolves `identifier` from the attribute value if non-empty, else `form.id`. Empty attribute (`data-ln-autosave=""`) falls through to `form.id`; missing both returns `null` and construction aborts with a console warning — `form.lnAutosave` is never set.
|
|
110
|
+
|
|
111
|
+
### Save / restore / clear
|
|
112
|
+
|
|
113
|
+
- **Save** (`focusout`, `change`, optional debounced `input`): `serializeForm` → `localStorage.setItem`, wrapped in a silent `try/catch` (`QuotaExceededError`, storage disabled). A failed write fires no event — there is no `:save-failed`.
|
|
114
|
+
- **Restore** (construction-time only): read + parse (silent try/catch) → cancelable `:before-restore` → `populateForm` → dispatch synthetic `input` **and** `change` on every restored field (covers both signal types regardless of field kind: text/textarea react to `input`, checkbox/radio/select to `change`) → `:restored`. Events fire after all fields are written, so cross-field validation sees the fully-restored form.
|
|
115
|
+
- **Clear**: `localStorage.removeItem` (idempotent) → `:cleared`. Triggered by native `submit` (not by `form.submit()` — that call bypasses the event; use `requestSubmit()`), native `reset`, or a click on `[data-ln-autosave-clear]`. Touches only the storage entry, never field values.
|
|
116
|
+
|
|
117
|
+
### Debounce is input-only, opt-in
|
|
118
|
+
|
|
119
|
+
`focusout`/`change` are naturally rate-limited by user behaviour (one event per field visit) — no debounce needed. `input` fires per keystroke, so `data-ln-autosave-debounce-input` exists as an explicit opt-in for compose-style fields the user never blurs. Default off. The interval is read once at construction (`_resolveDebounceMs`); mutating the attribute afterwards has no effect on a live instance.
|
|
120
|
+
|
|
121
|
+
### MutationObserver
|
|
122
|
+
|
|
123
|
+
`registerComponent` watches `childList` (new `[data-ln-autosave]` forms) and `attributes` (filtered to `data-ln-autosave` only — NOT `-clear` or `-debounce-input`). The clear-button delegate uses `closest()` per click, so `data-ln-autosave-clear` works even if added after construction; the debounce attribute does not, since it's read once.
|
|
124
|
+
|
|
125
|
+
### Destroy
|
|
126
|
+
|
|
127
|
+
Removes all five listeners (plus `input` and its pending timer if debounce was active), deletes the instance. Does **not** call `_clear()` — the draft persists after destroy; destroy means "stop autosaving," not "discard the draft."
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
@use '../../theme/config/mixins' as *;
|
|
2
|
+
|
|
3
|
+
[data-ln-debug] {
|
|
4
|
+
// 1. Host Element Check: data-ln-autosave belongs only on <form>
|
|
5
|
+
[data-ln-autosave]:not(form) {
|
|
6
|
+
@include dev-inline-error("data-ln-autosave belongs only on <form> elements");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// 2. Storage Key Check: form must have an id or explicit data-ln-autosave key
|
|
10
|
+
form[data-ln-autosave=""]:not([id]) {
|
|
11
|
+
@include dev-dom-error("form[data-ln-autosave] requires an 'id' or explicit storage key value.");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){"use strict";function D(){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 A(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 i=null;for(let l=1;l<t.length;l++)if(t[l]&&t[l].nodeType===1){i=t[l];break}if(!A(i))return}e.apply(console,t)}}function v(e,t,o){const i=o||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,i),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:i}))}function U(e,t,o){const i=o||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,i);const l=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:i});return e.dispatchEvent(l),l}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()}function H(e,t){const o=!!(t&&t.typed),i=t&&t.exclude,l={},s=e.elements,r={};if(o)for(let c=0;c<s.length;c++){const n=s[c];n.name&&n.type==="checkbox"&&!n.disabled&&(r[n.name]=(r[n.name]||0)+1)}for(let c=0;c<s.length;c++){const n=s[c];if(!(!n.name||n.disabled||n.type==="file"||n.type==="submit"||n.type==="button")&&!(i&&n.matches&&n.matches(i)))if(n.type==="checkbox")o&&r[n.name]===1?l[n.name]=n.checked:(l[n.name]||(l[n.name]=[]),n.checked&&l[n.name].push(n.value));else if(n.type==="radio")n.checked&&(l[n.name]=n.value);else if(n.type==="select-multiple"){l[n.name]=[];for(let a=0;a<n.options.length;a++)n.options[a].selected&&l[n.name].push(n.options[a].value)}else if(o&&n.type==="hidden")l[n.name]=n.value;else if(o&&(n.type==="number"||n.type==="range")){const a=Number(n.value);l[n.name]=n.value===""||isNaN(a)?null:a}else l[n.name]=n.value}return l}function V(e){if(typeof e!="string")return!!e;const t=e.trim().toLowerCase();return t!=="false"&&t!=="0"&&t!==""&&t!=="off"&&t!=="no"}function K(e,t){const o=e.elements,i=[],l={};for(let s=0;s<o.length;s++){const r=o[s];r.name&&r.type==="checkbox"&&(l[r.name]=(l[r.name]||0)+1)}for(let s=0;s<o.length;s++){const r=o[s];if(r.type==="file"||r.type==="submit"||r.type==="button")continue;const c=r.getAttribute("data-ln-fill-as")||r.name;if(!c||!(c in t))continue;const n=t[c];if(r.type==="checkbox"){if(Array.isArray(n))r.checked=n.indexOf(r.value)!==-1;else if(l[r.name]>1){const a=String(n).split(",").map(function(d){return d.trim()});r.checked=a.indexOf(r.value)!==-1}else r.checked=V(n);i.push(r)}else if(r.type==="radio")r.checked=r.value===String(n),i.push(r);else if(r.type==="select-multiple"){if(Array.isArray(n))for(let a=0;a<r.options.length;a++)r.options[a].selected=n.indexOf(r.options[a].value)!==-1;i.push(r)}else r.value=n,i.push(r)}return i}const R={};function Q(e,t){R[e]=t}function J(e){return R[e]||{ingress:t=>t,egress:t=>t}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=Q,window.lnCore.getDataMapper=J);function P(){typeof window>"u"||(window.lnCore=window.lnCore||{},!window.lnCore._localeObserverBound&&(window.lnCore._localeObserverBound=!0,_(function(){new MutationObserver(function(){v(document,"ln-core:locale-change",{})}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"],subtree:!0})},"ln-core")))}const $={};function X(e,t){if(!e||typeof t!="object")return;const o=e.toLowerCase().split("-")[0];$[o]=t}function G(e){if(!e)return null;const t=e.toLowerCase().split("-")[0];return $[t]||null}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerLocaleFallback=X,window.lnCore.getLocaleFallback=G,window.lnCore.ensureLocaleObserver=P);const k={};function z(e,t){k[e]||(k[e]=document.querySelector('[data-ln-template="'+e+'"]'));const o=k[e];return o?o.content.cloneNode(!0):(console.warn("["+(t||"ln-core")+'] Template "'+e+'" not found'),null)}function F(e,t){if(!e||!t)return e;const o=e.querySelectorAll("[data-ln-field]");for(let r=0;r<o.length;r++){const c=o[r],n=c.getAttribute("data-ln-field");t[n]!=null&&(c.textContent=t[n])}const i=e.querySelectorAll("[data-ln-attr]");for(let r=0;r<i.length;r++){const c=i[r],n=c.getAttribute("data-ln-attr").split(",");for(let a=0;a<n.length;a++){const d=n[a].trim().split(":");if(d.length!==2)continue;const u=d[0].trim(),h=d[1].trim();t[h]!=null&&c.setAttribute(u,t[h])}}const l=e.querySelectorAll("[data-ln-show]");for(let r=0;r<l.length;r++){const c=l[r],n=c.getAttribute("data-ln-show");n in t&&c.classList.toggle("hidden",!t[n])}const s=e.querySelectorAll("[data-ln-class]");for(let r=0;r<s.length;r++){const c=s[r],n=c.getAttribute("data-ln-class").split(",");for(let a=0;a<n.length;a++){const d=n[a].trim().split(":");if(d.length!==2)continue;const u=d[0].trim(),h=d[1].trim();h in t&&c.classList.toggle(u,!!t[h])}}return e}function Y(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 o=e.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let i=0;i<o.length;i++)window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",o[i],t??null),o[i].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)F(e.target,e.detail);else{const t=e.target.querySelectorAll("[data-ln-field]");for(let o=0;o<t.length;o++)t[o].textContent=""}})));function N(e,t){if(!e||!t)return e;const o=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(;o.nextNode();){const s=o.currentNode;s.textContent.indexOf("{{")!==-1&&(s.textContent=s.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(r,c){return t[c]!==void 0?t[c]:""}))}const i=function(s,r){return t[r]!==void 0?t[r]:""},l=Array.from(e.querySelectorAll("*"));e.nodeType===1&&l.push(e);for(let s=0;s<l.length;s++){const r=l[s],c=r.attributes;for(let n=0;n<c.length;n++){const a=c[n];a.value.indexOf("{{")!==-1&&r.setAttribute(a.name,a.value.replace(/\{\{\s*(\w+)\s*\}\}/g,i))}}return e}function Z(e,t,o,i,l,s){const r={};for(let n=0;n<e.children.length;n++){const a=e.children[n],d=a.getAttribute("data-ln-render-key");d&&(r[d]=a)}const c=document.createDocumentFragment();for(let n=0;n<t.length;n++){const a=t[n],d=String(i(a));let u=r[d];if(u)l(u,a,n);else{const h=z(o,s);if(!h||(N(h,a),u=h.firstElementChild,!u))continue;u.setAttribute("data-ln-render-key",d),l(u,a,n)}c.appendChild(u)}e.textContent="",e.appendChild(c)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.fillTemplate=N,window.lnCore.fill=F,window.lnCore.lnFill=Y,window.lnCore.renderList=Z);function q(e,t,o,i,l){if(!e||t===null)return!0;const s=i||"ln-component",r=e.type;if(r==="trigger"||r==="marker"||r==="string"||r==="list"||!r||t===""&&e.fallback!==void 0)return!0;const c=n=>{l?console.warn(n,l):console.warn(n)};if(r==="boolean"){const n=t.trim().toLowerCase();return n!==""&&n!=="true"&&n!=="false"&&n!=="1"&&n!=="0"?(c(`[${s}] Invalid value "${t}" for boolean attribute "${o}". Allowed: "true", "false", or presence-only.`),!1):!0}if(r==="enum"){const n=e.values||[];return n.includes(t)?!0:(c(`[${s}] Invalid value "${t}" for attribute "${o}". Allowed: ${n.join(", ")}. Fallback: "${e.fallback}".`),!1)}if(r==="integer"){if(!/^-?\d+$/.test(t))return c(`[${s}] Invalid integer "${t}" for attribute "${o}". Fallback: ${e.fallback}.`),!1;const n=parseInt(t,10);return e.min!==void 0&&n<e.min?(c(`[${s}] Value ${n} for attribute "${o}" is less than min (${e.min}).`),!1):e.max!==void 0&&n>e.max?(c(`[${s}] Value ${n} for attribute "${o}" is greater than max (${e.max}).`),!1):!0}if(r==="float")return isNaN(Number(t))?(c(`[${s}] Invalid float "${t}" for attribute "${o}". Fallback: ${e.fallback}.`),!1):!0;if(r==="json")try{return JSON.parse(t),!0}catch(n){return c(`[${s}] Invalid JSON for attribute "${o}": ${n.message}. Fallback: ${e.fallback}.`),!1}return!0}function ee(e){const t={};for(const o in e){const i=e[o];i&&i.effect&&(t[o]=i.effect)}return Object.keys(t).length?t:null}function te(e){if(e.onAttrChange&&!e.declared)return null;const t=new Set;if(e.effects)for(const o in e.effects)t.add(o);if(e.onAttrChange&&e.declared)for(const o of e.declared)t.add(o);return t}function E(e,t,o,i){if(e.nodeType!==1)return;const s=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",r=Array.from(e.querySelectorAll(s));e.matches&&e.matches(s)&&r.push(e);for(const c of r)if(!c[o]){window.lnCore._persistSink&&c.hasAttribute("data-ln-persist")&&window.lnCore._persistSink(c,t);try{c[o]=new i(c)}catch(n){console.error("["+o+"] init failed",c,n)}}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[]);function ne(){return typeof window<"u"&&window.lnCore&&window.lnCore._bootHolds||0}function oe(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 B(){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 re(e){const t=B(),o=e.observed||[];for(let i=0;i<o.length;i++){const l=o[i];t.byAttr.has(l)||t.byAttr.set(l,[]),t.byAttr.get(l).push(e)}if(t.byDeclaredAttr=t.byDeclaredAttr||new Map,e.attributes)for(const i in e.attributes){t.byDeclaredAttr.has(i)||t.byDeclaredAttr.set(i,[]);const l=t.byDeclaredAttr.get(i);l.some(s=>s.componentTag===e.componentTag)||l.push({spec:e.attributes[i],componentTag:e.componentTag})}if(e.onAttrChange||e.effects){t.reactive.push(e);const i=te(e);if(i===null)t.reactiveWildcard.push(e);else for(const l of i)t.byReactive.has(l)||t.byReactive.set(l,[]),t.byReactive.get(l).push(e)}e.persist&&t.persist.push(e)}function M(e,t,o,i){for(let l=0;l<e.length;l++){const s=e[l];if(!t[s.attribute])continue;const r=s.effects&&s.effects[o];r?r(t,o,i):s.onAttrChange&&(!s.declared||s.declared.has(o))&&s.onAttrChange(t,o,i)}}function ie(e){const t=e.target,o=e.attributeName;if(e.oldValue===t.getAttribute(o))return;const i=B(),l=i.byAttr.get(o);if(D()&&i.byDeclaredAttr&&i.byDeclaredAttr.has(o)&&A(t)){const s=i.byDeclaredAttr.get(o),r=t.getAttribute(o);for(let c=0;c<s.length;c++)q(s[c].spec,r,o,s[c].componentTag,t)}if(window.lnCore._debugSink&&(o.indexOf("data-ln-")===0||l)&&window.lnCore._debugSink("attr",o,t,{oldValue:e.oldValue,newValue:t.getAttribute(o)}),o.indexOf("data-ln-")===0){const s=i.byReactive.get(o);s&&M(s,t,o,e.oldValue),i.reactiveWildcard.length&&M(i.reactiveWildcard,t,o,e.oldValue)}if(l)for(let s=0;s<l.length;s++){const r=l[s];if(r.handler){r.handler(t,o,e.oldValue);continue}r.onAttributeChange&&t[r.attribute]?r.onAttributeChange(t,o):(E(t,r.selector,r.attribute,r.ComponentFn),r.onInit&&r.onInit(t))}}function le(){window.lnCore=window.lnCore||{},!window.lnCore._attrObserverBound&&(window.lnCore._attrObserverBound=!0,_(function(){new MutationObserver(function(t){for(let o=0;o<t.length;o++)try{ie(t[o])}catch(i){console.error("[ln-core] mutation handler failed",t[o].target,i)}}).observe(document.body,{attributes:!0,subtree:!0,attributeOldValue:!0})},"ln-core"))}function j(){return window.lnCore=window.lnCore||{},window.lnCore._lifecycleRegistry=window.lnCore._lifecycleRegistry||[]}function se(e){const t=j();if(t.length){if(e.target)for(let o=0;o<t.length;o++){const i=t[o];if(i.onSubtreeChange){const l=i.query,s=e.target.nodeType===1?e.target.matches(l)?e.target:e.target.closest(l):e.target.parentElement?e.target.parentElement.closest(l):null;s&&i.onSubtreeChange(s,e)}}for(let o=0;o<e.addedNodes.length;o++){const i=e.addedNodes[o];if(i.nodeType===1)for(let l=0;l<t.length;l++){const s=t[l];E(i,s.selector,s.attribute,s.ComponentFn),s.onInit&&s.onInit(i)}}for(let o=0;o<e.removedNodes.length;o++){const i=e.removedNodes[o];if(i.nodeType===1)for(let l=0;l<t.length;l++){const s=t[l],r=s.query,c=Array.from(i.querySelectorAll(r));i.matches&&i.matches(r)&&c.push(i);for(let n=0;n<c.length;n++){const a=c[n];if(!document.contains(a)){const d=a[s.attribute];if(d&&typeof d.destroy=="function")try{d.destroy()}catch(u){console.error("["+s.attribute+"] destroy failed",a,u)}delete a[s.attribute]}}}}}}function ce(){window.lnCore=window.lnCore||{},!window.lnCore._lifecycleObserverBound&&(window.lnCore._lifecycleObserverBound=!0,_(function(){new MutationObserver(function(t){for(let o=0;o<t.length;o++){const i=t[o];if(i.type==="childList")try{se(i)}catch(l){console.error("[ln-core] lifecycle handler failed",i.target,l)}}}).observe(document.body,{childList:!0,subtree:!0})},"ln-core"))}function ae(e,t,o,i,l={}){const s=l.extraAttributes||[],r=l.onAttributeChange||null,c=l.onSubtreeChange||null,n=l.onInit||null,a=l.onAttrChange||null,d=l.effects||null,u=l.attributes||null,h=u?ee(u):d,m=u?new Set(Object.keys(u)):null,O=l.persist||null;function p(g){const b=g||document.body;if(E(b,e,t,o),u&&D())for(const y in u){const _e=u[y],L=Array.from(b.querySelectorAll("["+y+"]"));b.matches&&b.matches("["+y+"]")&&L.push(b);for(let T=0;T<L.length;T++){const I=L[T];A(I)&&q(_e,I.getAttribute(y),y,i,I)}}n&&n(b)}const C=[];if(e.indexOf("[")!==-1){const g=/\[([\w-]+)/g;let b;for(;(b=g.exec(e))!==null;)C.push(b[1])}else C.push(e);re({selector:e,attribute:t,componentTag:i,attributes:u,ComponentFn:o,onInit:n,observed:C.concat(s),onAttributeChange:r,onAttrChange:a,effects:h,declared:m,persist:O}),le();const f=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]";j().push({selector:e,attribute:t,ComponentFn:o,onInit:n,onSubtreeChange:c,query:f}),ce(),window[t]=p;function x(){ne()>0?oe(function(){p(document.body)}):p(document.body)}return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",x):x(),p}function W(e){return(e||"").replace(/^#/,"")}function S(e){const t=e===void 0?location.hash:e,o={},i=W(t);if(!i)return o;const l=i.split("&");for(let s=0;s<l.length;s++){const r=l[s];if(!r)continue;const c=r.indexOf(":"),n=c>-1?r.slice(0,c):r,a=c>-1?r.slice(c+1):"";if(n)try{o[n]=decodeURIComponent(a)}catch{o[n]=a}}return o}function ue(e){if(!e)return null;const t=S();return e in t?t[e]:null}function de(e,t){if(!e)return;const o=S();t==null?delete o[e]:o[e]=String(t);const l=Object.keys(o).map(function(s){const r=o[s];return r===""?s:s+":"+encodeURIComponent(r)}).join("&");W(location.hash)!==l&&(location.hash=l)}function fe(e){return e.button===1||e.ctrlKey||e.metaKey||e.shiftKey?!1:(e.preventDefault(),!0)}function he(e,t){if(!e||!e.hasAttribute("data-ln-hash"))return null;const o=e.getAttribute("data-ln-hash");if(o&&o.trim()!=="")return o.trim();const i=e.getAttribute("data-ln-sort")||e.getAttribute("data-ln-search-for")||e.getAttribute("data-ln-search")||e.getAttribute("data-ln-filter")||e.id;return i?t?i+"-"+t:i:t||null}function we(e,t){return!t||t==="none"||e===null||e===void 0?null:String(e)+"."+t}function be(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 pe(e,t){return!e||!Array.isArray(t)||t.length===0?null:e+":"+t.map(encodeURIComponent).join(",")}function ge(e){if(!e||typeof e!="string")return null;const t=e.indexOf(":");if(t===-1)return null;const o=e.slice(0,t),i=e.slice(t+1),l=i?i.split(",").map(function(s){try{return decodeURIComponent(s)}catch{return s}}).filter(Boolean):[];return{key:o,values:l}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=S,window.lnCore.hashGet=ue,window.lnCore.hashSet=de,window.lnCore.hashLinkClick=fe,window.lnCore.resolveHashNamespace=he,window.lnCore.hashSortEncode=we,window.lnCore.hashSortDecode=be,window.lnCore.hashFilterEncode=pe,window.lnCore.hashFilterDecode=ge);const Ce="ln-autosave:",me=1e3;function ye(e,t){return t?Ce+(e||"")+":"+t:null}function ve(e,t=me){if(e==null)return 0;if(e==="")return t;const o=parseInt(String(e),10);return isNaN(o)||o<0?t:o}(function(){const e="data-ln-autosave",t="lnAutosave",o="data-ln-autosave-clear",i="data-ln-autosave-debounce-input",l='[data-ln-autosave-exclude], input[type="password"]';if(window[t]!==void 0)return;const s={"data-ln-autosave":{type:"string",description:"Form autosave storage key identifier"},"data-ln-autosave-debounce-input":{type:"integer",fallback:500,min:0,description:"Debounce delay in milliseconds before saving on input events"},"data-ln-autosave-clear":{type:"marker",description:"Designates a button that clears saved form data from localStorage"},"data-ln-autosave-exclude":{type:"marker",description:"Excludes form control from autosave serialization"}};function r(n){const a=n.tagName;return a==="INPUT"||a==="TEXTAREA"||a==="SELECT"}function c(n){const d=n.getAttribute(e)||n.id,u=ye(window.location.pathname,d);if(!u){console.warn("ln-autosave: form needs an id or data-ln-autosave value",n);return}this.dom=n,this.key=u;let h=null;function m(){const w=H(n,{exclude:l});try{localStorage.setItem(u,JSON.stringify(w))}catch{return}v(n,"ln-autosave:saved",{target:n,data:w})}function O(){let w;try{w=localStorage.getItem(u)}catch{return}if(!w)return;let f;try{f=JSON.parse(w)}catch{return}if(U(n,"ln-autosave:before-restore",{target:n,data:f}).defaultPrevented)return;const g=K(n,f);for(let b=0;b<g.length;b++)g[b].dispatchEvent(new Event("input",{bubbles:!0})),g[b].dispatchEvent(new Event("change",{bubbles:!0}));v(n,"ln-autosave:restored",{target:n,data:f})}function p(){try{localStorage.removeItem(u)}catch{return}v(n,"ln-autosave:cleared",{target:n})}this._onFocusout=function(w){const f=w.target;r(f)&&f.name&&!f.matches(l)&&m()},this._onChange=function(w){const f=w.target;r(f)&&f.name&&!f.matches(l)&&m()},this._onSubmit=function(){p()},this._onReset=function(){p()},this._onClearClick=function(w){w.target.closest("["+o+"]")&&p()},n.addEventListener("focusout",this._onFocusout),n.addEventListener("change",this._onChange),n.addEventListener("submit",this._onSubmit),n.addEventListener("reset",this._onReset),n.addEventListener("click",this._onClearClick);const C=ve(n.getAttribute(i));return C>0&&(this._onInput=function(w){const f=w.target;!r(f)||!f.name||f.matches(l)||(h!==null&&clearTimeout(h),h=setTimeout(m,C))},n.addEventListener("input",this._onInput)),this._getInputTimer=function(){return h},O(),this}c.prototype.destroy=function(){if(this.dom[t]){if(this.dom.removeEventListener("focusout",this._onFocusout),this.dom.removeEventListener("change",this._onChange),this.dom.removeEventListener("submit",this._onSubmit),this.dom.removeEventListener("reset",this._onReset),this.dom.removeEventListener("click",this._onClearClick),this._onInput){this.dom.removeEventListener("input",this._onInput);const n=this._getInputTimer();n!==null&&clearTimeout(n)}delete this.dom[t]}},ae(e,t,c,"ln-autosave",{attributes:s})})()})();
|
|
@@ -0,0 +1,48 @@
|
|
|
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-autosave",
|
|
4
|
+
"generator": "scripts/sync-ln-schemas.mjs",
|
|
5
|
+
"attributes": {
|
|
6
|
+
"data-ln-autosave": {
|
|
7
|
+
"direction": "author",
|
|
8
|
+
"sources": [
|
|
9
|
+
"ln-autosave-dev.scss",
|
|
10
|
+
"src/ln-autosave.js"
|
|
11
|
+
],
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "Form autosave storage key identifier"
|
|
14
|
+
},
|
|
15
|
+
"data-ln-autosave-clear": {
|
|
16
|
+
"direction": "author",
|
|
17
|
+
"sources": [
|
|
18
|
+
"src/ln-autosave.js"
|
|
19
|
+
],
|
|
20
|
+
"type": "marker",
|
|
21
|
+
"description": "Designates a button that clears saved form data from localStorage"
|
|
22
|
+
},
|
|
23
|
+
"data-ln-autosave-debounce-input": {
|
|
24
|
+
"direction": "author",
|
|
25
|
+
"sources": [
|
|
26
|
+
"src/ln-autosave.js"
|
|
27
|
+
],
|
|
28
|
+
"type": "integer",
|
|
29
|
+
"fallback": 500,
|
|
30
|
+
"min": 0,
|
|
31
|
+
"description": "Debounce delay in milliseconds before saving on input events"
|
|
32
|
+
},
|
|
33
|
+
"data-ln-autosave-exclude": {
|
|
34
|
+
"direction": null,
|
|
35
|
+
"sources": [
|
|
36
|
+
"src/ln-autosave.js"
|
|
37
|
+
],
|
|
38
|
+
"type": "marker",
|
|
39
|
+
"description": "Excludes form control from autosave serialization"
|
|
40
|
+
},
|
|
41
|
+
"data-ln-debug": {
|
|
42
|
+
"direction": null,
|
|
43
|
+
"sources": [
|
|
44
|
+
"ln-autosave-dev.scss"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const STORAGE_PREFIX = 'ln-autosave:';
|
|
2
|
+
export const DEFAULT_DEBOUNCE_MS = 1000;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds a deterministic storage key for autosave persistence.
|
|
6
|
+
* @param {string} pathname
|
|
7
|
+
* @param {string} identifier
|
|
8
|
+
* @returns {string|null}
|
|
9
|
+
*/
|
|
10
|
+
export function buildAutosaveKey(pathname, identifier) {
|
|
11
|
+
if (!identifier) return null;
|
|
12
|
+
const path = pathname || '';
|
|
13
|
+
return STORAGE_PREFIX + path + ':' + identifier;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parses and sanitizes the debounce milliseconds attribute value for ln-autosave.
|
|
18
|
+
*
|
|
19
|
+
* Semantics:
|
|
20
|
+
* - Returns `0` if the attribute is absent (null/undefined), signaling that input debouncing is disabled (no listener).
|
|
21
|
+
* - Returns `fallbackMs` (default 1000) if the attribute is present as boolean/empty string `""` or contains an invalid value.
|
|
22
|
+
* - Returns the parsed positive integer if a valid numeric string is provided.
|
|
23
|
+
*
|
|
24
|
+
* @param {unknown} rawValue Raw DOM attribute value (e.g. data-ln-autosave-debounce-input)
|
|
25
|
+
* @param {number} [fallbackMs=1000] Fallback debounce duration in ms
|
|
26
|
+
* @returns {number} Milliseconds to debounce, or 0 if disabled
|
|
27
|
+
*/
|
|
28
|
+
export function parseAutosaveDebounce(rawValue, fallbackMs = DEFAULT_DEBOUNCE_MS) {
|
|
29
|
+
if (rawValue === null || rawValue === undefined) return 0;
|
|
30
|
+
if (rawValue === '') return fallbackMs;
|
|
31
|
+
const parsed = parseInt(String(rawValue), 10);
|
|
32
|
+
if (isNaN(parsed) || parsed < 0) return fallbackMs;
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
@@ -1,19 +1,35 @@
|
|
|
1
|
-
import { dispatch, dispatchCancelable,
|
|
1
|
+
import { dispatch, dispatchCancelable, populateForm, registerComponent, serializeForm } from '../../ln-core';
|
|
2
|
+
import { buildAutosaveKey, parseAutosaveDebounce } from './autosave-model.js';
|
|
2
3
|
|
|
3
4
|
(function () {
|
|
4
5
|
const DOM_SELECTOR = 'data-ln-autosave';
|
|
5
6
|
const DOM_ATTRIBUTE = 'lnAutosave';
|
|
6
7
|
const CLEAR_SELECTOR = 'data-ln-autosave-clear';
|
|
7
8
|
const DEBOUNCE_SELECTOR = 'data-ln-autosave-debounce-input';
|
|
8
|
-
const
|
|
9
|
-
const DEFAULT_DEBOUNCE_MS = 1000;
|
|
9
|
+
const EXCLUDE_SELECTOR = '[data-ln-autosave-exclude], input[type="password"]';
|
|
10
10
|
|
|
11
11
|
if (window[DOM_ATTRIBUTE] !== undefined) return;
|
|
12
12
|
|
|
13
|
+
// ─── Attribute Contract (SSOT) ──────────────────────────
|
|
14
|
+
const ATTRIBUTES = {
|
|
15
|
+
'data-ln-autosave': { type: 'string', description: 'Form autosave storage key identifier' },
|
|
16
|
+
'data-ln-autosave-debounce-input': { type: 'integer', fallback: 500, min: 0, description: 'Debounce delay in milliseconds before saving on input events' },
|
|
17
|
+
'data-ln-autosave-clear': { type: 'marker', description: 'Designates a button that clears saved form data from localStorage' },
|
|
18
|
+
'data-ln-autosave-exclude': { type: 'marker', description: 'Excludes form control from autosave serialization' }
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function _isFormField(el) {
|
|
22
|
+
const tag = el.tagName;
|
|
23
|
+
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
|
|
24
|
+
}
|
|
25
|
+
|
|
13
26
|
// ─── Component ─────────────────────────────────────────────
|
|
14
27
|
|
|
15
28
|
function _component(form) {
|
|
16
|
-
const
|
|
29
|
+
const value = form.getAttribute(DOM_SELECTOR);
|
|
30
|
+
const identifier = value || form.id;
|
|
31
|
+
const key = buildAutosaveKey(window.location.pathname, identifier);
|
|
32
|
+
|
|
17
33
|
if (!key) {
|
|
18
34
|
console.warn('ln-autosave: form needs an id or data-ln-autosave value', form);
|
|
19
35
|
return;
|
|
@@ -24,20 +40,30 @@ import { dispatch, dispatchCancelable, serializeForm, populateForm, registerComp
|
|
|
24
40
|
|
|
25
41
|
let inputTimer = null;
|
|
26
42
|
|
|
27
|
-
// Closure-scoped helpers (NOT on prototype — internal only)
|
|
28
43
|
function _save() {
|
|
29
|
-
const data = serializeForm(form, { exclude:
|
|
30
|
-
try {
|
|
31
|
-
|
|
44
|
+
const data = serializeForm(form, { exclude: EXCLUDE_SELECTOR });
|
|
45
|
+
try {
|
|
46
|
+
localStorage.setItem(key, JSON.stringify(data));
|
|
47
|
+
} catch (_) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
32
50
|
dispatch(form, 'ln-autosave:saved', { target: form, data: data });
|
|
33
51
|
}
|
|
34
52
|
|
|
35
53
|
function _restore() {
|
|
36
54
|
let raw;
|
|
37
|
-
try {
|
|
55
|
+
try {
|
|
56
|
+
raw = localStorage.getItem(key);
|
|
57
|
+
} catch (_) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
38
60
|
if (!raw) return;
|
|
39
61
|
let data;
|
|
40
|
-
try {
|
|
62
|
+
try {
|
|
63
|
+
data = JSON.parse(raw);
|
|
64
|
+
} catch (_) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
41
67
|
const before = dispatchCancelable(form, 'ln-autosave:before-restore', { target: form, data: data });
|
|
42
68
|
if (before.defaultPrevented) return;
|
|
43
69
|
const restored = populateForm(form, data);
|
|
@@ -49,24 +75,31 @@ import { dispatch, dispatchCancelable, serializeForm, populateForm, registerComp
|
|
|
49
75
|
}
|
|
50
76
|
|
|
51
77
|
function _clear() {
|
|
52
|
-
try {
|
|
78
|
+
try {
|
|
79
|
+
localStorage.removeItem(key);
|
|
80
|
+
} catch (_) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
53
83
|
dispatch(form, 'ln-autosave:cleared', { target: form });
|
|
54
84
|
}
|
|
55
85
|
|
|
56
|
-
// Listener handlers (held on `this` for symmetric removal in destroy)
|
|
57
86
|
this._onFocusout = function (e) {
|
|
58
87
|
const el = e.target;
|
|
59
|
-
if (_isFormField(el) && el.name && !el.
|
|
88
|
+
if (_isFormField(el) && el.name && !el.matches(EXCLUDE_SELECTOR)) _save();
|
|
60
89
|
};
|
|
61
90
|
|
|
62
91
|
this._onChange = function (e) {
|
|
63
92
|
const el = e.target;
|
|
64
|
-
if (_isFormField(el) && el.name && !el.
|
|
93
|
+
if (_isFormField(el) && el.name && !el.matches(EXCLUDE_SELECTOR)) _save();
|
|
65
94
|
};
|
|
66
95
|
|
|
67
|
-
this._onSubmit = function () {
|
|
96
|
+
this._onSubmit = function () {
|
|
97
|
+
_clear();
|
|
98
|
+
};
|
|
68
99
|
|
|
69
|
-
this._onReset = function () {
|
|
100
|
+
this._onReset = function () {
|
|
101
|
+
_clear();
|
|
102
|
+
};
|
|
70
103
|
|
|
71
104
|
this._onClearClick = function (e) {
|
|
72
105
|
const btn = e.target.closest('[' + CLEAR_SELECTOR + ']');
|
|
@@ -74,74 +107,48 @@ import { dispatch, dispatchCancelable, serializeForm, populateForm, registerComp
|
|
|
74
107
|
};
|
|
75
108
|
|
|
76
109
|
form.addEventListener('focusout', this._onFocusout);
|
|
77
|
-
form.addEventListener('change',
|
|
78
|
-
form.addEventListener('submit',
|
|
79
|
-
form.addEventListener('reset',
|
|
80
|
-
form.addEventListener('click',
|
|
110
|
+
form.addEventListener('change', this._onChange);
|
|
111
|
+
form.addEventListener('submit', this._onSubmit);
|
|
112
|
+
form.addEventListener('reset', this._onReset);
|
|
113
|
+
form.addEventListener('click', this._onClearClick);
|
|
81
114
|
|
|
82
|
-
|
|
83
|
-
const debounceMs = _resolveDebounceMs(form);
|
|
115
|
+
const debounceMs = parseAutosaveDebounce(form.getAttribute(DEBOUNCE_SELECTOR));
|
|
84
116
|
if (debounceMs > 0) {
|
|
85
117
|
this._onInput = function (e) {
|
|
86
118
|
const el = e.target;
|
|
87
|
-
if (!_isFormField(el) || !el.name || el.
|
|
119
|
+
if (!_isFormField(el) || !el.name || el.matches(EXCLUDE_SELECTOR)) return;
|
|
88
120
|
if (inputTimer !== null) clearTimeout(inputTimer);
|
|
89
121
|
inputTimer = setTimeout(_save, debounceMs);
|
|
90
122
|
};
|
|
91
123
|
form.addEventListener('input', this._onInput);
|
|
92
124
|
}
|
|
93
125
|
|
|
94
|
-
|
|
95
|
-
|
|
126
|
+
this._getInputTimer = function () {
|
|
127
|
+
return inputTimer;
|
|
128
|
+
};
|
|
96
129
|
|
|
97
130
|
_restore();
|
|
98
|
-
|
|
99
131
|
return this;
|
|
100
132
|
}
|
|
101
133
|
|
|
102
134
|
_component.prototype.destroy = function () {
|
|
103
135
|
if (!this.dom[DOM_ATTRIBUTE]) return;
|
|
104
136
|
this.dom.removeEventListener('focusout', this._onFocusout);
|
|
105
|
-
this.dom.removeEventListener('change',
|
|
106
|
-
this.dom.removeEventListener('submit',
|
|
107
|
-
this.dom.removeEventListener('reset',
|
|
108
|
-
this.dom.removeEventListener('click',
|
|
137
|
+
this.dom.removeEventListener('change', this._onChange);
|
|
138
|
+
this.dom.removeEventListener('submit', this._onSubmit);
|
|
139
|
+
this.dom.removeEventListener('reset', this._onReset);
|
|
140
|
+
this.dom.removeEventListener('click', this._onClearClick);
|
|
109
141
|
if (this._onInput) {
|
|
110
142
|
this.dom.removeEventListener('input', this._onInput);
|
|
111
143
|
const t = this._getInputTimer();
|
|
112
144
|
if (t !== null) clearTimeout(t);
|
|
113
145
|
}
|
|
114
|
-
dispatch(this.dom, 'ln-autosave:destroyed', { target: this.dom });
|
|
115
146
|
delete this.dom[DOM_ATTRIBUTE];
|
|
116
147
|
};
|
|
117
148
|
|
|
118
|
-
// ─── Helpers ───────────────────────────────────────────────
|
|
119
|
-
|
|
120
|
-
function _getStorageKey(form) {
|
|
121
|
-
const value = form.getAttribute(DOM_SELECTOR);
|
|
122
|
-
const identifier = value || form.id;
|
|
123
|
-
if (!identifier) return null;
|
|
124
|
-
return STORAGE_PREFIX + window.location.pathname + ':' + identifier;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function _isFormField(el) {
|
|
128
|
-
const tag = el.tagName;
|
|
129
|
-
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function _resolveDebounceMs(form) {
|
|
133
|
-
if (!form.hasAttribute(DEBOUNCE_SELECTOR)) return 0;
|
|
134
|
-
const raw = form.getAttribute(DEBOUNCE_SELECTOR);
|
|
135
|
-
if (raw === '' || raw === null) return DEFAULT_DEBOUNCE_MS;
|
|
136
|
-
const ms = parseInt(raw, 10);
|
|
137
|
-
if (isNaN(ms) || ms < 0) {
|
|
138
|
-
console.warn('ln-autosave: invalid debounce value, using default', form);
|
|
139
|
-
return DEFAULT_DEBOUNCE_MS;
|
|
140
|
-
}
|
|
141
|
-
return ms;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
149
|
// ─── Init ──────────────────────────────────────────────────
|
|
145
150
|
|
|
146
|
-
registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-autosave'
|
|
151
|
+
registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-autosave', {
|
|
152
|
+
attributes: ATTRIBUTES
|
|
153
|
+
});
|
|
147
154
|
})();
|