@livenetworks/ashlar 1.6.1 → 2.0.1
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 +190 -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,160 +1,171 @@
|
|
|
1
|
-
# ln-ajax
|
|
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
|
-
| `data-ln-ajax
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
>
|
|
50
|
-
>
|
|
51
|
-
> * **
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
* **`
|
|
74
|
-
* **`
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
| `ln-ajax:start` |
|
|
86
|
-
| `ln-ajax:
|
|
87
|
-
| `ln-ajax:
|
|
88
|
-
| `ln-ajax:
|
|
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
|
-
Response branches
|
|
122
|
-
-
|
|
123
|
-
|
|
124
|
-
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
- **
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
###
|
|
154
|
-
|
|
155
|
-
`ln-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
1
|
+
# ln-ajax
|
|
2
|
+
|
|
3
|
+
> Applied to a container, link, or form via `data-ln-ajax`. It intercepts native `<a>` clicks and `<form>` submits,
|
|
4
|
+
> calling `e.preventDefault()` and issuing an AJAX request with `X-Requested-With: XMLHttpRequest`. On receiving a JSON
|
|
5
|
+
> response containing HTML fragments (`{ content: { "#id": "<html>" } }`), it replaces the target elements in the DOM in-place
|
|
6
|
+
> and updates browser history via `history.pushState()`.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 🧭 Philosophy & Architecture
|
|
11
|
+
|
|
12
|
+
1. **HTML-First Swapping:** The server remains the single source of truth for both data and markup. Instead of client routers rendering JSON arrays, the server compiles standard HTML fragments and returns them inside a structured JSON payload.
|
|
13
|
+
2. **Selective DOM Merges:** The response maps selector IDs (e.g. `main-content`) directly to their new HTML chunks, replacing only the specified regions in-place.
|
|
14
|
+
3. **Transparent Enhancements:** Intercepts only native interactions (links to the same origin, form submissions). Safely falls back to native browser redirects on errors or external hosts.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 📦 Minimal Blueprint
|
|
19
|
+
|
|
20
|
+
Wrap interactive elements or entire layouts with the `data-ln-ajax` selector.
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<div data-ln-ajax>
|
|
24
|
+
<!-- Clicking this fetches /dashboard and swaps only the returned targets -->
|
|
25
|
+
<a href="/dashboard">Dashboard</a>
|
|
26
|
+
|
|
27
|
+
<!-- Submitting this posts data and swaps target parts on success -->
|
|
28
|
+
<form method="POST" action="/users/create">
|
|
29
|
+
<input name="username" type="text" required>
|
|
30
|
+
<button type="submit">Create User</button>
|
|
31
|
+
</form>
|
|
32
|
+
|
|
33
|
+
<!-- Exclude specific elements from AJAX handling -->
|
|
34
|
+
<a href="/logout" data-ln-ajax="false">Logout</a>
|
|
35
|
+
</div>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 🛠️ Declarative API Contract
|
|
41
|
+
|
|
42
|
+
### HTML Attributes
|
|
43
|
+
|
|
44
|
+
| Attribute | Elements | Values | Description |
|
|
45
|
+
| :--- | :--- | :--- | :--- |
|
|
46
|
+
| `data-ln-ajax` | Container, `<a>`, `<form>` | *none* (empty attribute) | Activates AJAX capture on the element and its descendants. Acts as a boolean flag. |
|
|
47
|
+
| `data-ln-ajax="false"` | `<a>`, `<form>` | `"false"` | **The only supported value.** Excludes the specific link or form from AJAX interception. |
|
|
48
|
+
|
|
49
|
+
> [!IMPORTANT]
|
|
50
|
+
> **Attribute Value Restriction:** The `data-ln-ajax` attribute operates as a presence-based trigger (like a boolean flag).
|
|
51
|
+
> * **The only valid and supported value is `"false"`**, which is used to exclude/opt-out specific elements inside an AJAX-enabled container.
|
|
52
|
+
> * **Do not use custom values** (e.g., `data-ln-ajax="loginform"` or `data-ln-ajax="my-widget"`). The component does **not** accept, process, or assign behavior based on other string values. Using custom values will simply activate normal AJAX routing (acting the same as an empty attribute) but is a major anti-pattern that confuses both developers and AI agents.
|
|
53
|
+
|
|
54
|
+
### Server Response Protocol
|
|
55
|
+
|
|
56
|
+
The server must return JSON with the `application/json` Content-Type:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"title": "New Dashboard Page",
|
|
61
|
+
"content": {
|
|
62
|
+
"main-content": "<h1>Dashboard</h1><p>Welcome back!</p>",
|
|
63
|
+
"sidebar-nav": "<ul><li>Active Nav Item</li></ul>"
|
|
64
|
+
},
|
|
65
|
+
"message": {
|
|
66
|
+
"type": "success",
|
|
67
|
+
"title": "User Created",
|
|
68
|
+
"body": "The user was registered successfully."
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
* **`title`**: Updates `document.title` on page swap.
|
|
74
|
+
* **`content`**: Key-value pairs matching container `id` selectors to their new `innerHTML` content.
|
|
75
|
+
* **`message`**: Optional. When present, handled and dispatched as UI toasts by Layer 2 coordinators (such as `ln-ui-coordinator`). The `message.body` property is required to display the notification text; `message.title` and `message.type` (`"success"` | `"error"` | `"warning"` | `"info"`) are optional.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## ⚡ DOM Events
|
|
80
|
+
|
|
81
|
+
All events are dispatched on the initiating element (`<a>` or `<form>`) and bubble.
|
|
82
|
+
|
|
83
|
+
| Event | Cancelable | Description | Payload (`detail`) |
|
|
84
|
+
| :--- | :--- | :--- | :--- |
|
|
85
|
+
| `ln-ajax:before-start` | **Yes** | Fires before any network activity. Call `e.preventDefault()` to cancel. | `{ method, url }` |
|
|
86
|
+
| `ln-ajax:start` | No | Fires as the loader class is added and fetch begins. | `{ method, url }` |
|
|
87
|
+
| `ln-ajax:success` | No | Fires after successful DOM swaps. | `{ method, url, data }` |
|
|
88
|
+
| `ln-ajax:error` | No | Fires on HTTP status failure or network rejects. | `{ method, url, status, data, error }` |
|
|
89
|
+
| `ln-ajax:complete` | No | Fires at the very end of the lifecycle (success or error). | `{ method, url }` |
|
|
90
|
+
| `ln-ajax:aborted` | No | Fires when a newer GET supersedes this in-flight GET. Ends the lifecycle in place of `success`/`error`/`complete`. | `{ method, url }` |
|
|
91
|
+
|
|
92
|
+
Every `ln-ajax:start` is followed by exactly one of `ln-ajax:complete` or `ln-ajax:aborted`.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## ⚠️ Common Pitfalls
|
|
97
|
+
|
|
98
|
+
- **Missing DOM IDs on Swap Targets:** If the server returns a key in `content` that does not match a mounting ID in the active document (e.g. `id="main-content"`), that segment swap fails silently.
|
|
99
|
+
- **Forgetting CSRF Meta:** `ln-ajax` automatically reads `<meta name="csrf-token" content="...">` to inject the `X-CSRF-TOKEN` header on non-GET calls. If this meta tag is missing, POST/PUT requests may fail authentication.
|
|
100
|
+
- **Breaking External Links:** Links with different hostnames are ignored automatically, but absolute paths on the same host are captured. Ensure assets/downloads use `data-ln-ajax="false"`.
|
|
101
|
+
- **Forms carrying `data-ln-data-coordinator-scope` are skipped entirely** (one-time `console.warn`) — the `ln-data-coordinator` write pipeline takes precedence over ajax progressive enhancement.
|
|
102
|
+
- **Respects `e.defaultPrevented`.** If a prior `submit` listener on the same form (e.g. a validation gate) already called `preventDefault()`, `ln-ajax` does nothing — no fetch, no loader class.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 🔧 Internals
|
|
107
|
+
|
|
108
|
+
Source: `components/ln-ajax/ln-ajax.js`.
|
|
109
|
+
|
|
110
|
+
### Request lifecycle
|
|
111
|
+
|
|
112
|
+
In order, per intercepted click/submit:
|
|
113
|
+
|
|
114
|
+
1. **Intercept** — click on `<a>` or submit on `<form>`. Modifier-key clicks (Ctrl/Cmd/middle) and `#`-href links pass through untouched.
|
|
115
|
+
2. **External-hostname skip** — if the link's hostname differs from `location.hostname`, ln-ajax aborts and the browser follows the link normally (no opt-out needed).
|
|
116
|
+
3. **`ln-ajax:before-start`** — cancelable; `preventDefault()` stops here (no spinner, no fetch).
|
|
117
|
+
- **Supersede (GET only)** — if not prevented, a GET aborts the in-flight GET, wherever it started: its response writes document-wide (`document.title`, ids, history). The aborted request cleans up and emits `ln-ajax:aborted` before the new one starts. Other methods are never aborted and never abort.
|
|
118
|
+
4. **Spinner mount** — `.ln-ajax--loading` on the trigger, `<span class="ln-ajax-spinner">` appended, form buttons disabled.
|
|
119
|
+
5. **`ln-ajax:start`** — non-cancelable; fetch is about to begin.
|
|
120
|
+
6. **`fetch()`** — built from `href`/`action`, `method`, `FormData`. Headers always include `X-Requested-With`, `Accept: application/json`, and `X-CSRF-TOKEN` from the page meta. A `<input name="_method">` (auto-added by `ln-form`'s RESTful mode) rides in `FormData` transparently — the HTTP verb stays POST while `_method` travels in the body.
|
|
121
|
+
7. **Response** — see the branches below.
|
|
122
|
+
8. **`ln-ajax:success` / `ln-ajax:error`** — dispatched on the trigger, bubbling.
|
|
123
|
+
9. **Cleanup** — loading class + spinner removed, buttons re-enabled.
|
|
124
|
+
10. **`ln-ajax:complete`** — fires last, success or error. A superseded GET ends with `ln-ajax:aborted` instead.
|
|
125
|
+
|
|
126
|
+
Response branches:
|
|
127
|
+
- **HTTP error or non-JSON body** (`!response.ok`, or a body `JSON.parse` rejects): dispatch `ln-ajax:error` `{ method, url, status, data, error }`.
|
|
128
|
+
- **Fetch rejection** (network / DNS / offline): dispatch `ln-ajax:error` `{ method, url, status: 0, data: null, error }`.
|
|
129
|
+
- **Success**: update `document.title`, swap `target.innerHTML` for each `data.content[id]`, `history.pushState` for `<a>` and GET `<form>`.
|
|
130
|
+
|
|
131
|
+
`data.message` on `success`/`error` is toasted by `ln-ui-coordinator`, not by ln-ajax.
|
|
132
|
+
- **Superseded** (GET only): clean up, dispatch `ln-ajax:aborted` `{ method, url }`. No `success`/`error`/`complete`, no DOM swap, no `pushState`.
|
|
133
|
+
|
|
134
|
+
### MutationObserver flow
|
|
135
|
+
|
|
136
|
+
A single observer on `document.body` handles three cases:
|
|
137
|
+
- **New `data-ln-ajax` root injected** → `new lnAjax(node)` attaches listeners (covers AJAX-swapped frames and `innerHTML` writes).
|
|
138
|
+
- **Elements injected inside a live root** → `findElements` re-runs on the subtree and attaches to any new `<a>`/`<form>` — no full re-init of the root.
|
|
139
|
+
- **`data-ln-ajax` set on an existing element** → the attributes branch runs `new lnAjax(node)`, so programmatic opt-in works.
|
|
140
|
+
|
|
141
|
+
### DOM mutations performed
|
|
142
|
+
|
|
143
|
+
| Phase | Mutation |
|
|
144
|
+
|-------|----------|
|
|
145
|
+
| Request start | `.ln-ajax--loading` on trigger |
|
|
146
|
+
| Request start | `<span class="ln-ajax-spinner">` appended to trigger |
|
|
147
|
+
| Request start | `disabled` on all `<button>` descendants of a `<form>` trigger |
|
|
148
|
+
| Success | `target.innerHTML` replaced per `id` in `data.content` |
|
|
149
|
+
| Success | `document.title` updated from `data.title` |
|
|
150
|
+
| Success (`<a>` / GET `<form>`) | `history.pushState` |
|
|
151
|
+
| Completion / abort | loading class + spinner removed, form buttons re-enabled |
|
|
152
|
+
|
|
153
|
+
### `findElements` local divergence
|
|
154
|
+
|
|
155
|
+
ln-ajax uses a local `findElements` rather than the ln-core helper of the same name: it needs a `{ links, forms }` partition (to bind `click` on links and `submit` on forms in one pass), where ln-core returns a flat list. The two must not be merged without updating all call sites.
|
|
156
|
+
|
|
157
|
+
### Trust boundary
|
|
158
|
+
|
|
159
|
+
ln-ajax operates under a trusted, same-origin model: it expects every HTML fragment in the JSON response to be safe. It performs **no** client-side sanitization or regex script/attribute filtering — that would be fragile and would corrupt valid markup (e.g. stripping `data-ln-confirm` values that contain event-like substrings). Sanitizing user-submitted HTML is the server's job, done before the fragment is rendered. See [Security §5 — AJAX Fragment Trust Boundary](../../docs/architecture/security.md#5-ajax-fragment-trust-boundary).
|
|
160
|
+
|
|
161
|
+
### Error detail shape
|
|
162
|
+
|
|
163
|
+
`ln-ajax:error` always carries all five keys `{ method, url, status, data, error }`:
|
|
164
|
+
|
|
165
|
+
| Failure | `status` | `data` | `error` |
|
|
166
|
+
|---|---|---|---|
|
|
167
|
+
| HTTP error (`!ok`) | response status | parsed body (may contain `message`), or `null` | `null` |
|
|
168
|
+
| Body is not JSON | response status | `null` | the `SyntaxError` |
|
|
169
|
+
| Network / DNS / offline | `0` | `null` | the caught `Error` |
|
|
170
|
+
|
|
171
|
+
`status === 0` identifies a network failure.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
@use '../../theme/config/mixins' as *;
|
|
2
|
+
|
|
3
|
+
[data-ln-debug] {
|
|
4
|
+
// 1. AJAX Form Action Check: forms with data-ln-ajax must declare an action URL
|
|
5
|
+
form[data-ln-ajax]:not([action]) {
|
|
6
|
+
@include dev-inline-error("form[data-ln-ajax] is missing a required 'action' attribute");
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){"use strict";function U(){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 F(e){if(typeof window>"u")return!1;if(window.lnDebug===!0)return!0;if(window.lnCore&&window.lnCore._debugIsContained)return e?window.lnCore._debugIsContained(e):window.lnCore._debugSink!==null;if(e&&e.closest){const t=e.closest("[data-ln-debug]");return!(!t||typeof document<"u"&&t===document.documentElement)}return typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug"):!1}if(typeof window<"u"&&(window.lnCore=window.lnCore||{},!window.lnCore._warnBound)){window.lnCore._warnBound=!0;const e=console.warn;console.warn=function(...t){if(typeof t[0]=="string"&&(t[0].startsWith("[ln-")||t[0].startsWith("[lnCore"))){let r=null;for(let l=1;l<t.length;l++)if(t[l]&&t[l].nodeType===1){r=t[l];break}if(!F(r))return}e.apply(console,t)}}function y(e,t,n){const r=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,r),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:r}))}function G(e,t,n){const r=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,r);const l=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:r});return e.dispatchEvent(l),l}function E(e,t){if(!document.body){document.addEventListener("DOMContentLoaded",function(){E(e,t)}),console.warn("["+t+'] Script loaded before <body> — add "defer" to your <script> tag');return}e()}function K(e,t){if(e.ctrlKey||e.metaKey||e.shiftKey||e.altKey||e.button!==0||!t)return!1;const n=t.getAttribute("href");return!(!n||t.getAttribute("target")==="_blank"||t.hasAttribute("download")||n.startsWith("mailto:")||n.startsWith("tel:")||n==="#"||n.startsWith("#")||t.hostname&&t.hostname!==window.location.hostname)}const q={};function H(e,t){q[e]=t}function P(e){return q[e]||{ingress:t=>t,egress:t=>t}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=H,window.lnCore.getDataMapper=P);function X(){typeof window>"u"||(window.lnCore=window.lnCore||{},!window.lnCore._localeObserverBound&&(window.lnCore._localeObserverBound=!0,E(function(){new MutationObserver(function(){y(document,"ln-core:locale-change",{})}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"],subtree:!0})},"ln-core")))}const M={};function J(e,t){if(!e||typeof t!="object")return;const n=e.toLowerCase().split("-")[0];M[n]=t}function Q(e){if(!e)return null;const t=e.toLowerCase().split("-")[0];return M[t]||null}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerLocaleFallback=J,window.lnCore.getLocaleFallback=Q,window.lnCore.ensureLocaleObserver=X);const O={};function z(e,t){O[e]||(O[e]=document.querySelector('[data-ln-template="'+e+'"]'));const n=O[e];return n?n.content.cloneNode(!0):(console.warn("["+(t||"ln-core")+'] Template "'+e+'" not found'),null)}function W(e,t){if(!e||!t)return e;const n=e.querySelectorAll("[data-ln-field]");for(let o=0;o<n.length;o++){const u=n[o],s=u.getAttribute("data-ln-field");t[s]!=null&&(u.textContent=t[s])}const r=e.querySelectorAll("[data-ln-attr]");for(let o=0;o<r.length;o++){const u=r[o],s=u.getAttribute("data-ln-attr").split(",");for(let d=0;d<s.length;d++){const h=s[d].trim().split(":");if(h.length!==2)continue;const p=h[0].trim(),a=h[1].trim();t[a]!=null&&u.setAttribute(p,t[a])}}const l=e.querySelectorAll("[data-ln-show]");for(let o=0;o<l.length;o++){const u=l[o],s=u.getAttribute("data-ln-show");s in t&&u.classList.toggle("hidden",!t[s])}const i=e.querySelectorAll("[data-ln-class]");for(let o=0;o<i.length;o++){const u=i[o],s=u.getAttribute("data-ln-class").split(",");for(let d=0;d<s.length;d++){const h=s[d].trim().split(":");if(h.length!==2)continue;const p=h[0].trim(),a=h[1].trim();a in t&&u.classList.toggle(p,!!t[a])}}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 n=e.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let r=0;r<n.length;r++)window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",n[r],t??null),n[r].dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0}));return e}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._fillBound||(window.lnCore._fillBound=!0,document.addEventListener("ln-fill",function(e){if(!(!e.target.matches||!e.target.matches("[data-ln-fillable]")))if(e.detail)W(e.target,e.detail);else{const t=e.target.querySelectorAll("[data-ln-field]");for(let n=0;n<t.length;n++)t[n].textContent=""}})));function I(e,t){if(!e||!t)return e;const n=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(;n.nextNode();){const i=n.currentNode;i.textContent.indexOf("{{")!==-1&&(i.textContent=i.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(o,u){return t[u]!==void 0?t[u]:""}))}const r=function(i,o){return t[o]!==void 0?t[o]:""},l=Array.from(e.querySelectorAll("*"));e.nodeType===1&&l.push(e);for(let i=0;i<l.length;i++){const o=l[i],u=o.attributes;for(let s=0;s<u.length;s++){const d=u[s];d.value.indexOf("{{")!==-1&&o.setAttribute(d.name,d.value.replace(/\{\{\s*(\w+)\s*\}\}/g,r))}}return e}function Z(e,t,n,r,l,i){const o={};for(let s=0;s<e.children.length;s++){const d=e.children[s],h=d.getAttribute("data-ln-render-key");h&&(o[h]=d)}const u=document.createDocumentFragment();for(let s=0;s<t.length;s++){const d=t[s],h=String(r(d));let p=o[h];if(p)l(p,d,s);else{const a=z(n,i);if(!a||(I(a,d),p=a.firstElementChild,!p))continue;p.setAttribute("data-ln-render-key",h),l(p,d,s)}u.appendChild(p)}e.textContent="",e.appendChild(u)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.fillTemplate=I,window.lnCore.fill=W,window.lnCore.lnFill=Y,window.lnCore.renderList=Z);function ee(e,t,n,r,l){if(!e||t===null)return!0;const i=r||"ln-component",o=e.type;if(o==="trigger"||o==="marker"||o==="string"||o==="list"||!o||t===""&&e.fallback!==void 0)return!0;const u=s=>{l?console.warn(s,l):console.warn(s)};if(o==="boolean"){const s=t.trim().toLowerCase();return s!==""&&s!=="true"&&s!=="false"&&s!=="1"&&s!=="0"?(u(`[${i}] Invalid value "${t}" for boolean attribute "${n}". Allowed: "true", "false", or presence-only.`),!1):!0}if(o==="enum"){const s=e.values||[];return s.includes(t)?!0:(u(`[${i}] Invalid value "${t}" for attribute "${n}". Allowed: ${s.join(", ")}. Fallback: "${e.fallback}".`),!1)}if(o==="integer"){if(!/^-?\d+$/.test(t))return u(`[${i}] Invalid integer "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1;const s=parseInt(t,10);return e.min!==void 0&&s<e.min?(u(`[${i}] Value ${s} for attribute "${n}" is less than min (${e.min}).`),!1):e.max!==void 0&&s>e.max?(u(`[${i}] Value ${s} for attribute "${n}" is greater than max (${e.max}).`),!1):!0}if(o==="float")return isNaN(Number(t))?(u(`[${i}] Invalid float "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1):!0;if(o==="json")try{return JSON.parse(t),!0}catch(s){return u(`[${i}] Invalid JSON for attribute "${n}": ${s.message}. Fallback: ${e.fallback}.`),!1}return!0}function te(e){if(e.onAttrChange&&!e.declared)return null;const t=new Set;if(e.effects)for(const n in e.effects)t.add(n);if(e.onAttrChange&&e.declared)for(const n of e.declared)t.add(n);return t}function ne(e,t,n,r){if(e.nodeType!==1)return;const i=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",o=Array.from(e.querySelectorAll(i));e.matches&&e.matches(i)&&o.push(e);for(const u of o)if(!u[n]){window.lnCore._persistSink&&u.hasAttribute("data-ln-persist")&&window.lnCore._persistSink(u,t);try{u[n]=new r(u)}catch(s){console.error("["+n+"] init failed",u,s)}}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[]);function N(){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=N(),n=e.observed||[];for(let r=0;r<n.length;r++){const l=n[r];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 r in e.attributes){t.byDeclaredAttr.has(r)||t.byDeclaredAttr.set(r,[]);const l=t.byDeclaredAttr.get(r);l.some(i=>i.componentTag===e.componentTag)||l.push({spec:e.attributes[r],componentTag:e.componentTag})}if(e.onAttrChange||e.effects){t.reactive.push(e);const r=te(e);if(r===null)t.reactiveWildcard.push(e);else for(const l of r)t.byReactive.has(l)||t.byReactive.set(l,[]),t.byReactive.get(l).push(e)}e.persist&&t.persist.push(e)}function B(e,t,n,r){for(let l=0;l<e.length;l++){const i=e[l];if(!t[i.attribute])continue;const o=i.effects&&i.effects[n];o?o(t,n,r):i.onAttrChange&&(!i.declared||i.declared.has(n))&&i.onAttrChange(t,n,r)}}function oe(e){const t=e.target,n=e.attributeName;if(e.oldValue===t.getAttribute(n))return;const r=N(),l=r.byAttr.get(n);if(U()&&r.byDeclaredAttr&&r.byDeclaredAttr.has(n)&&F(t)){const i=r.byDeclaredAttr.get(n),o=t.getAttribute(n);for(let u=0;u<i.length;u++)ee(i[u].spec,o,n,i[u].componentTag,t)}if(window.lnCore._debugSink&&(n.indexOf("data-ln-")===0||l)&&window.lnCore._debugSink("attr",n,t,{oldValue:e.oldValue,newValue:t.getAttribute(n)}),n.indexOf("data-ln-")===0){const i=r.byReactive.get(n);i&&B(i,t,n,e.oldValue),r.reactiveWildcard.length&&B(r.reactiveWildcard,t,n,e.oldValue)}if(l)for(let i=0;i<l.length;i++){const o=l[i];if(o.handler){o.handler(t,n,e.oldValue);continue}o.onAttributeChange&&t[o.attribute]?o.onAttributeChange(t,n):(ne(t,o.selector,o.attribute,o.ComponentFn),o.onInit&&o.onInit(t))}}function ie(){window.lnCore=window.lnCore||{},!window.lnCore._attrObserverBound&&(window.lnCore._attrObserverBound=!0,E(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++)try{oe(t[n])}catch(r){console.error("[ln-core] mutation handler failed",t[n].target,r)}}).observe(document.body,{attributes:!0,subtree:!0,attributeOldValue:!0})},"ln-core"))}function le(e,t){re({observed:e,handler:t}),ie()}function R(e){return(e||"").replace(/^#/,"")}function D(e){const t=e===void 0?location.hash:e,n={},r=R(t);if(!r)return n;const l=r.split("&");for(let i=0;i<l.length;i++){const o=l[i];if(!o)continue;const u=o.indexOf(":"),s=u>-1?o.slice(0,u):o,d=u>-1?o.slice(u+1):"";if(s)try{n[s]=decodeURIComponent(d)}catch{n[s]=d}}return n}function se(e){if(!e)return null;const t=D();return e in t?t[e]:null}function ae(e,t){if(!e)return;const n=D();t==null?delete n[e]:n[e]=String(t);const l=Object.keys(n).map(function(i){const o=n[i];return o===""?i:i+":"+encodeURIComponent(o)}).join("&");R(location.hash)!==l&&(location.hash=l)}function ue(e){return e.button===1||e.ctrlKey||e.metaKey||e.shiftKey?!1:(e.preventDefault(),!0)}function ce(e,t){if(!e||!e.hasAttribute("data-ln-hash"))return null;const n=e.getAttribute("data-ln-hash");if(n&&n.trim()!=="")return n.trim();const r=e.getAttribute("data-ln-sort")||e.getAttribute("data-ln-search-for")||e.getAttribute("data-ln-search")||e.getAttribute("data-ln-filter")||e.id;return r?t?r+"-"+t:r:t||null}function fe(e,t){return!t||t==="none"||e===null||e===void 0?null:String(e)+"."+t}function de(e){return!e||typeof e!="string"?null:e.endsWith(".asc")?{fieldOrColumn:e.slice(0,-4),direction:"asc"}:e.endsWith(".desc")?{fieldOrColumn:e.slice(0,-5),direction:"desc"}:null}function we(e,t){return!e||!Array.isArray(t)||t.length===0?null:e+":"+t.map(encodeURIComponent).join(",")}function be(e){if(!e||typeof e!="string")return null;const t=e.indexOf(":");if(t===-1)return null;const n=e.slice(0,t),r=e.slice(t+1),l=r?r.split(",").map(function(i){try{return decodeURIComponent(i)}catch{return i}}).filter(Boolean):[];return{key:n,values:l}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=D,window.lnCore.hashGet=se,window.lnCore.hashSet=ae,window.lnCore.hashLinkClick=ue,window.lnCore.resolveHashNamespace=ce,window.lnCore.hashSortEncode=fe,window.lnCore.hashSortDecode=de,window.lnCore.hashFilterEncode=we,window.lnCore.hashFilterDecode=be),(function(){const e="data-ln-ajax",t="lnAjax",n="data-ln-data-coordinator-scope";if(window[t]!==void 0)return;let r=null;function l(a){if(!a.hasAttribute(e)||a[t])return;a[t]=!0;const c=d(a);i(c.links),o(c.forms)}function i(a){for(const c of a){if(c[t+"Trigger"]||c.hostname&&c.hostname!==window.location.hostname)continue;const w=c.getAttribute("href");if(w&&w.includes("#"))continue;const f=function(m){if(!K(m,c))return;m.preventDefault();const C=c.getAttribute("href");C&&s("GET",C,null,c)};c.addEventListener("click",f),c[t+"Trigger"]=f}}function o(a){for(const c of a){if(c[t+"Trigger"])continue;if(c.hasAttribute(n)){c[t+"ScopeWarned"]||(c[t+"ScopeWarned"]=!0,console.warn("[ln-ajax] Form has data-ln-data-coordinator-scope — the ln-data-coordinator write pipeline takes precedence; skipping ajax interception for this form."));continue}const w=function(f){if(f.defaultPrevented)return;f.preventDefault();const m=c.method.toUpperCase(),C=c.action,T=new FormData(c);for(const v of c.querySelectorAll('button, input[type="submit"]'))v.disabled=!0;s(m,C,T,c,function(){for(const v of c.querySelectorAll('button, input[type="submit"]'))v.disabled=!1})};c.addEventListener("submit",w),c[t+"Trigger"]=w}}function u(a){if(!a[t])return;const c=d(a);for(const w of c.links)w[t+"Trigger"]&&(w.removeEventListener("click",w[t+"Trigger"]),delete w[t+"Trigger"]);for(const w of c.forms)w[t+"Trigger"]&&(w.removeEventListener("submit",w[t+"Trigger"]),delete w[t+"Trigger"]);delete a[t]}function s(a,c,w,f,m){if(G(f,"ln-ajax:before-start",{method:a,url:c}).defaultPrevented)return;a==="GET"&&r&&r(),y(f,"ln-ajax:start",{method:a,url:c}),f.classList.add("ln-ajax--loading");const T=document.createElement("span");T.className="ln-ajax-spinner",f.appendChild(T);function v(){f.classList.remove("ln-ajax--loading");const b=f.querySelector(".ln-ajax-spinner");b&&b.remove(),m&&m()}let A=c;const V=document.querySelector('meta[name="csrf-token"]'),L=V?V.getAttribute("content"):null;w instanceof FormData&&L&&w.append("_token",L);const j={method:a,headers:{"X-Requested-With":"XMLHttpRequest",Accept:"application/json"}};if(L&&(j.headers["X-CSRF-TOKEN"]=L),a==="GET"&&w){const b=new URLSearchParams(w);A=c+(c.includes("?")?"&":"?")+b.toString()}else a!=="GET"&&w&&(j.body=w);let _=null;if(a==="GET"){const b=new AbortController;j.signal=b.signal,_=function(){r=null,b.abort(),v(),y(f,"ln-ajax:aborted",{method:a,url:A})},r=_}fetch(A,j).then(function(b){const $=b.ok,g=b.status;return b.text().then(function(S){let k=null,x=null;if(S&&S.trim())try{k=JSON.parse(S)}catch(he){x=he}return{ok:$,status:g,data:k,parseError:x}})}).then(function(b){_&&r===_&&(r=null);const $=b.status,g=b.data,S=b.parseError;if(b.ok&&!S){if(g&&g.title&&(document.title=g.title),g&&g.content)for(const k in g.content){const x=document.getElementById(k);x&&(x.innerHTML=g.content[k])}if(f.tagName==="A"){const k=f.getAttribute("href");k&&window.history.pushState({ajax:!0},"",k)}else f.tagName==="FORM"&&f.method.toUpperCase()==="GET"&&window.history.pushState({ajax:!0},"",A);y(f,"ln-ajax:success",{method:a,url:A,data:g})}else y(f,"ln-ajax:error",{method:a,url:A,status:$,data:g,error:S||null});v(),y(f,"ln-ajax:complete",{method:a,url:A})}).catch(function(b){b&&b.name==="AbortError"||(_&&r===_&&(r=null),y(f,"ln-ajax:error",{method:a,url:A,status:0,data:null,error:b}),v(),y(f,"ln-ajax:complete",{method:a,url:A}))})}function d(a){const c={links:[],forms:[]};return a.tagName==="A"&&a.getAttribute(e)!=="false"?c.links.push(a):a.tagName==="FORM"&&a.getAttribute(e)!=="false"?c.forms.push(a):(c.links=Array.from(a.querySelectorAll('a:not([data-ln-ajax="false"])')),c.forms=Array.from(a.querySelectorAll('form:not([data-ln-ajax="false"])'))),c}function h(){E(function(){new MutationObserver(function(c){for(const w of c)if(w.type==="childList"){for(const f of w.addedNodes)if(f.nodeType===1&&(l(f),!f.hasAttribute(e))){for(const C of f.querySelectorAll("["+e+"]"))l(C);const m=f.closest&&f.closest("["+e+"]");if(m&&m.getAttribute(e)!=="false"){const C=d(f);i(C.links),o(C.forms)}}}}).observe(document.body,{childList:!0,subtree:!0}),le([e],function(c){l(c)})},"ln-ajax")}function p(){for(const a of document.querySelectorAll("["+e+"]"))l(a)}window[t]=l,window[t].destroy=u,h(),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",p):p()})()})();
|
|
@@ -0,0 +1,30 @@
|
|
|
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-ajax",
|
|
4
|
+
"generator": "scripts/sync-ln-schemas.mjs",
|
|
5
|
+
"attributes": {
|
|
6
|
+
"data-ln-ajax": {
|
|
7
|
+
"direction": "author",
|
|
8
|
+
"sources": [
|
|
9
|
+
"ln-ajax-dev.scss",
|
|
10
|
+
"src/ln-ajax.js"
|
|
11
|
+
],
|
|
12
|
+
"type": "marker",
|
|
13
|
+
"description": "Enables AJAX interception of form submissions and anchor clicks within container"
|
|
14
|
+
},
|
|
15
|
+
"data-ln-data-coordinator-scope": {
|
|
16
|
+
"direction": "author",
|
|
17
|
+
"sources": [
|
|
18
|
+
"src/ln-ajax.js"
|
|
19
|
+
],
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Excludes form from ln-ajax interception to let data coordinator handle submission"
|
|
22
|
+
},
|
|
23
|
+
"data-ln-debug": {
|
|
24
|
+
"direction": null,
|
|
25
|
+
"sources": [
|
|
26
|
+
"ln-ajax-dev.scss"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import { guardBody, dispatch, dispatchCancelable, shouldInterceptLink } from '../../ln-core';
|
|
1
|
+
import { guardBody, dispatch, dispatchCancelable, shouldInterceptLink, observeAttributes } from '../../ln-core';
|
|
2
2
|
|
|
3
3
|
(function () {
|
|
4
4
|
const DOM_SELECTOR = 'data-ln-ajax';
|
|
5
5
|
const DOM_ATTRIBUTE = 'lnAjax';
|
|
6
|
-
const SCOPE_ATTR = 'data-ln-
|
|
6
|
+
const SCOPE_ATTR = 'data-ln-data-coordinator-scope';
|
|
7
7
|
|
|
8
8
|
if (window[DOM_ATTRIBUTE] !== undefined) return;
|
|
9
9
|
|
|
10
|
+
// A GET response writes document-wide (title, ids, history), so a newer GET
|
|
11
|
+
// supersedes the in-flight one wherever it started. Other methods are never aborted.
|
|
12
|
+
let abortInflightGet = null;
|
|
13
|
+
|
|
14
|
+
// ─── Attribute Contract (SSOT) ──────────────────────────
|
|
15
|
+
const ATTRIBUTES = {
|
|
16
|
+
'data-ln-ajax': { type: 'marker', description: 'Enables AJAX interception of form submissions and anchor clicks within container' },
|
|
17
|
+
'data-ln-data-coordinator-scope': { type: 'string', description: 'Excludes form from ln-ajax interception to let data coordinator handle submission' }
|
|
18
|
+
};
|
|
19
|
+
|
|
10
20
|
function _constructor(domRoot) {
|
|
11
21
|
if (!domRoot.hasAttribute(DOM_SELECTOR)) return;
|
|
12
22
|
if (domRoot[DOM_ATTRIBUTE]) return;
|
|
@@ -49,7 +59,7 @@ import { guardBody, dispatch, dispatchCancelable, shouldInterceptLink } from '..
|
|
|
49
59
|
if (form.hasAttribute(SCOPE_ATTR)) {
|
|
50
60
|
if (!form[DOM_ATTRIBUTE + 'ScopeWarned']) {
|
|
51
61
|
form[DOM_ATTRIBUTE + 'ScopeWarned'] = true;
|
|
52
|
-
console.warn('[ln-ajax] Form has data-ln-
|
|
62
|
+
console.warn('[ln-ajax] Form has data-ln-data-coordinator-scope — the ln-data-coordinator write pipeline takes precedence; skipping ajax interception for this form.');
|
|
53
63
|
}
|
|
54
64
|
continue;
|
|
55
65
|
}
|
|
@@ -101,6 +111,8 @@ import { guardBody, dispatch, dispatchCancelable, shouldInterceptLink } from '..
|
|
|
101
111
|
const before = dispatchCancelable(element, 'ln-ajax:before-start', { method: method, url: url });
|
|
102
112
|
if (before.defaultPrevented) return;
|
|
103
113
|
|
|
114
|
+
if (method === 'GET' && abortInflightGet) abortInflightGet();
|
|
115
|
+
|
|
104
116
|
dispatch(element, 'ln-ajax:start', { method: method, url: url });
|
|
105
117
|
|
|
106
118
|
element.classList.add('ln-ajax--loading');
|
|
@@ -143,22 +155,48 @@ import { guardBody, dispatch, dispatchCancelable, shouldInterceptLink } from '..
|
|
|
143
155
|
options.body = data;
|
|
144
156
|
}
|
|
145
157
|
|
|
158
|
+
let abort = null;
|
|
159
|
+
if (method === 'GET') {
|
|
160
|
+
const controller = new AbortController();
|
|
161
|
+
options.signal = controller.signal;
|
|
162
|
+
abort = function () {
|
|
163
|
+
abortInflightGet = null;
|
|
164
|
+
controller.abort();
|
|
165
|
+
_cleanup();
|
|
166
|
+
dispatch(element, 'ln-ajax:aborted', { method: method, url: finalUrl });
|
|
167
|
+
};
|
|
168
|
+
abortInflightGet = abort;
|
|
169
|
+
}
|
|
170
|
+
|
|
146
171
|
fetch(finalUrl, options)
|
|
147
172
|
.then(function (response) {
|
|
148
173
|
const ok = response.ok;
|
|
149
|
-
|
|
150
|
-
|
|
174
|
+
const status = response.status;
|
|
175
|
+
return response.text().then(function (text) {
|
|
176
|
+
let data = null;
|
|
177
|
+
let parseError = null;
|
|
178
|
+
if (text && text.trim()) {
|
|
179
|
+
try {
|
|
180
|
+
data = JSON.parse(text);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
parseError = err;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { ok: ok, status: status, data: data, parseError: parseError };
|
|
151
186
|
});
|
|
152
187
|
})
|
|
153
188
|
.then(function (result) {
|
|
189
|
+
if (abort && abortInflightGet === abort) abortInflightGet = null;
|
|
190
|
+
const status = result.status;
|
|
154
191
|
const data = result.data;
|
|
192
|
+
const parseError = result.parseError;
|
|
155
193
|
|
|
156
|
-
if (result.ok) {
|
|
157
|
-
if (data.title) {
|
|
194
|
+
if (result.ok && !parseError) {
|
|
195
|
+
if (data && data.title) {
|
|
158
196
|
document.title = data.title;
|
|
159
197
|
}
|
|
160
198
|
|
|
161
|
-
if (data.content) {
|
|
199
|
+
if (data && data.content) {
|
|
162
200
|
for (const targetId in data.content) {
|
|
163
201
|
const targetElement = document.getElementById(targetId);
|
|
164
202
|
if (targetElement) {
|
|
@@ -178,29 +216,26 @@ import { guardBody, dispatch, dispatchCancelable, shouldInterceptLink } from '..
|
|
|
178
216
|
|
|
179
217
|
dispatch(element, 'ln-ajax:success', { method: method, url: finalUrl, data: data });
|
|
180
218
|
} else {
|
|
181
|
-
dispatch(element, 'ln-ajax:error', {
|
|
219
|
+
dispatch(element, 'ln-ajax:error', {
|
|
220
|
+
method: method,
|
|
221
|
+
url: finalUrl,
|
|
222
|
+
status: status,
|
|
223
|
+
data: data,
|
|
224
|
+
error: parseError || null
|
|
225
|
+
});
|
|
182
226
|
}
|
|
183
227
|
|
|
184
|
-
// Auto-dispatch response message as a toast event.
|
|
185
|
-
// Any listener (ln-toast by default) can pick it up.
|
|
186
|
-
if (data.message) {
|
|
187
|
-
const msg = data.message;
|
|
188
|
-
window.dispatchEvent(new CustomEvent('ln-toast:enqueue', {
|
|
189
|
-
detail: {
|
|
190
|
-
type: msg.type || (result.ok ? 'success' : 'error'),
|
|
191
|
-
title: msg.title || '',
|
|
192
|
-
message: msg.body || ''
|
|
193
|
-
}
|
|
194
|
-
}));
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
dispatch(element, 'ln-ajax:complete', { method: method, url: finalUrl });
|
|
198
228
|
_cleanup();
|
|
229
|
+
dispatch(element, 'ln-ajax:complete', { method: method, url: finalUrl });
|
|
199
230
|
})
|
|
200
231
|
.catch(function (error) {
|
|
201
|
-
|
|
202
|
-
|
|
232
|
+
// Superseded: abort() already cleaned up and emitted ln-ajax:aborted
|
|
233
|
+
if (error && error.name === 'AbortError') return;
|
|
234
|
+
if (abort && abortInflightGet === abort) abortInflightGet = null;
|
|
235
|
+
// True network failure / DNS / offline (fetch itself threw)
|
|
236
|
+
dispatch(element, 'ln-ajax:error', { method: method, url: finalUrl, status: 0, data: null, error: error });
|
|
203
237
|
_cleanup();
|
|
238
|
+
dispatch(element, 'ln-ajax:complete', { method: method, url: finalUrl });
|
|
204
239
|
});
|
|
205
240
|
}
|
|
206
241
|
|
|
@@ -243,17 +278,17 @@ import { guardBody, dispatch, dispatchCancelable, shouldInterceptLink } from '..
|
|
|
243
278
|
}
|
|
244
279
|
}
|
|
245
280
|
}
|
|
246
|
-
} else if (mutation.type === 'attributes') {
|
|
247
|
-
_constructor(mutation.target);
|
|
248
281
|
}
|
|
249
282
|
}
|
|
250
283
|
});
|
|
251
284
|
|
|
252
285
|
observer.observe(document.body, {
|
|
253
286
|
childList: true,
|
|
254
|
-
subtree: true
|
|
255
|
-
|
|
256
|
-
|
|
287
|
+
subtree: true
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
observeAttributes([DOM_SELECTOR], function (el) {
|
|
291
|
+
_constructor(el);
|
|
257
292
|
});
|
|
258
293
|
}, 'ln-ajax');
|
|
259
294
|
}
|