@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,15 +1,26 @@
|
|
|
1
|
-
import { dispatch, computePlacement, measureHidden, registerComponent } from '../../ln-core';
|
|
1
|
+
import { dispatch, computePlacement, measureHidden, registerComponent, defineAttrs, attrSpec, attrStr } from '../../ln-core';
|
|
2
2
|
|
|
3
3
|
(function () {
|
|
4
4
|
const DOM_SELECTOR = 'data-ln-dropdown';
|
|
5
5
|
const DOM_ATTRIBUTE = 'lnDropdown';
|
|
6
|
+
const DEFAULT_POSITION = 'bottom-end';
|
|
6
7
|
|
|
7
8
|
if (window[DOM_ATTRIBUTE] !== undefined) return;
|
|
8
9
|
|
|
10
|
+
// ─── Attribute Contract (SSOT) ──────────────────────────
|
|
11
|
+
const ATTRIBUTES = {
|
|
12
|
+
'data-ln-dropdown': { type: 'marker', description: 'Initializes the dropdown container' },
|
|
13
|
+
'data-ln-dropdown-position': { prop: 'position', type: 'enum', values: ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end', 'right', 'right-start', 'right-end'], fallback: DEFAULT_POSITION, description: 'Preferred positioning anchor' },
|
|
14
|
+
'data-ln-dropdown-placement': { type: 'enum', values: ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end', 'right', 'right-start', 'right-end'], description: 'Active calculated placement applied at runtime' },
|
|
15
|
+
'data-ln-dropdown-menu': { type: 'marker', description: 'Dropdown menu element containing items' }
|
|
16
|
+
};
|
|
17
|
+
const ATTR_SPEC = attrSpec(ATTRIBUTES);
|
|
18
|
+
|
|
9
19
|
// ─── Component ─────────────────────────────────────────────
|
|
10
20
|
|
|
11
21
|
function _component(dom) {
|
|
12
22
|
this.dom = dom;
|
|
23
|
+
defineAttrs(this, dom, ATTR_SPEC);
|
|
13
24
|
this.toggleEl = dom.querySelector('[data-ln-toggle]');
|
|
14
25
|
this._boundDocClick = null;
|
|
15
26
|
this._docClickTimeout = null;
|
|
@@ -20,6 +31,7 @@ import { dispatch, computePlacement, measureHidden, registerComponent } from '..
|
|
|
20
31
|
this.toggleEl.setAttribute('data-ln-dropdown-menu', '');
|
|
21
32
|
this.toggleEl.setAttribute('role', 'menu');
|
|
22
33
|
this.toggleEl.setAttribute('popover', 'manual');
|
|
34
|
+
this._initMenuAria();
|
|
23
35
|
}
|
|
24
36
|
|
|
25
37
|
// ARIA on trigger button
|
|
@@ -29,13 +41,6 @@ import { dispatch, computePlacement, measureHidden, registerComponent } from '..
|
|
|
29
41
|
this.triggerBtn.setAttribute('aria-expanded', 'false');
|
|
30
42
|
}
|
|
31
43
|
|
|
32
|
-
// role="menuitem" on direct children of menu
|
|
33
|
-
if (this.toggleEl) {
|
|
34
|
-
for (const item of this.toggleEl.children) {
|
|
35
|
-
item.setAttribute('role', 'menuitem');
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
44
|
const self = this;
|
|
40
45
|
|
|
41
46
|
this._onRequestOpen = function () {
|
|
@@ -51,14 +56,77 @@ import { dispatch, computePlacement, measureHidden, registerComponent } from '..
|
|
|
51
56
|
}
|
|
52
57
|
};
|
|
53
58
|
|
|
59
|
+
// ─── Keyboard Navigation (ARIA APG menu pattern) ───────────
|
|
60
|
+
this._onKeydown = function (e) {
|
|
61
|
+
const isOpen = self.toggleEl && self.toggleEl.getAttribute('data-ln-toggle') === 'open';
|
|
62
|
+
|
|
63
|
+
if (e.key === 'Escape') {
|
|
64
|
+
if (isOpen) {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
e.stopPropagation();
|
|
67
|
+
self.toggleEl.setAttribute('data-ln-toggle', 'close');
|
|
68
|
+
if (self.triggerBtn) self.triggerBtn.focus();
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (e.key === 'Tab') {
|
|
74
|
+
if (isOpen) {
|
|
75
|
+
// Return focus to trigger button before closing so native Tab advance starts from trigger
|
|
76
|
+
if (self.triggerBtn) self.triggerBtn.focus();
|
|
77
|
+
self.toggleEl.setAttribute('data-ln-toggle', 'close');
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const items = self._getMenuItems();
|
|
83
|
+
if (items.length === 0) return;
|
|
84
|
+
|
|
85
|
+
// Open from trigger on ArrowDown or ArrowUp
|
|
86
|
+
if (!isOpen && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
self.toggleEl.setAttribute('data-ln-toggle', 'open');
|
|
89
|
+
// Macrotask deferral: ensures ln-toggle's MutationObserver microtask has finished opening and showing the popover before moving focus
|
|
90
|
+
setTimeout(function () {
|
|
91
|
+
const freshItems = self._getMenuItems();
|
|
92
|
+
if (freshItems.length > 0) {
|
|
93
|
+
self._focusItem(freshItems, e.key === 'ArrowDown' ? 0 : freshItems.length - 1);
|
|
94
|
+
}
|
|
95
|
+
}, 0);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!isOpen) return;
|
|
100
|
+
|
|
101
|
+
const currentIndex = items.indexOf(document.activeElement);
|
|
102
|
+
|
|
103
|
+
if (e.key === 'ArrowDown') {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
106
|
+
self._focusItem(items, nextIndex);
|
|
107
|
+
} else if (e.key === 'ArrowUp') {
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
110
|
+
self._focusItem(items, prevIndex);
|
|
111
|
+
} else if (e.key === 'Home') {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
self._focusItem(items, 0);
|
|
114
|
+
} else if (e.key === 'End') {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
self._focusItem(items, items.length - 1);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
54
120
|
this.dom.addEventListener('ln-dropdown:request-open', this._onRequestOpen);
|
|
55
121
|
this.dom.addEventListener('ln-dropdown:request-close', this._onRequestClose);
|
|
56
122
|
this.dom.addEventListener('ln-dropdown:request-toggle', this._onRequestToggle);
|
|
123
|
+
this.dom.addEventListener('keydown', this._onKeydown);
|
|
57
124
|
|
|
58
125
|
this._onToggleOpen = function (e) {
|
|
59
126
|
if (!e.detail || e.detail.target !== self.toggleEl) return;
|
|
60
127
|
if (self.triggerBtn) self.triggerBtn.setAttribute('aria-expanded', 'true');
|
|
61
128
|
if (typeof self.toggleEl.showPopover === 'function') self.toggleEl.showPopover();
|
|
129
|
+
self._initMenuAria();
|
|
62
130
|
self._reposition();
|
|
63
131
|
self._addOutsideClickListener();
|
|
64
132
|
self._addScrollRepositionListener();
|
|
@@ -74,6 +142,7 @@ import { dispatch, computePlacement, measureHidden, registerComponent } from '..
|
|
|
74
142
|
self._removeResizeCloseListener();
|
|
75
143
|
self.toggleEl.style.top = '';
|
|
76
144
|
self.toggleEl.style.left = '';
|
|
145
|
+
self.toggleEl.removeAttribute('data-ln-dropdown-placement');
|
|
77
146
|
// :popover-open guard — a boot-opened menu (persist/static "open") was never shown via showPopover()
|
|
78
147
|
if (typeof self.toggleEl.hidePopover === 'function' && self.toggleEl.matches(':popover-open')) self.toggleEl.hidePopover();
|
|
79
148
|
dispatch(dom, 'ln-dropdown:close', { target: e.detail.target });
|
|
@@ -87,6 +156,35 @@ import { dispatch, computePlacement, measureHidden, registerComponent } from '..
|
|
|
87
156
|
return this;
|
|
88
157
|
}
|
|
89
158
|
|
|
159
|
+
// ─── Menu Items & ARIA Helpers ─────────────────────────────
|
|
160
|
+
|
|
161
|
+
_component.prototype._initMenuAria = function () {
|
|
162
|
+
if (!this.toggleEl) return;
|
|
163
|
+
const listItems = this.toggleEl.querySelectorAll('li');
|
|
164
|
+
for (const li of listItems) {
|
|
165
|
+
li.setAttribute('role', 'none');
|
|
166
|
+
}
|
|
167
|
+
const items = this._getMenuItems();
|
|
168
|
+
for (let i = 0; i < items.length; i++) {
|
|
169
|
+
items[i].setAttribute('role', 'menuitem');
|
|
170
|
+
items[i].setAttribute('tabindex', i === 0 ? '0' : '-1');
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
_component.prototype._getMenuItems = function () {
|
|
175
|
+
if (!this.toggleEl) return [];
|
|
176
|
+
return Array.from(this.toggleEl.querySelectorAll('a[href], button:not([disabled]), [role="menuitem"]:not([disabled])'));
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
_component.prototype._focusItem = function (items, index) {
|
|
180
|
+
for (let i = 0; i < items.length; i++) {
|
|
181
|
+
items[i].setAttribute('tabindex', i === index ? '0' : '-1');
|
|
182
|
+
}
|
|
183
|
+
if (items[index]) {
|
|
184
|
+
items[index].focus();
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
90
188
|
// ─── Positioning ───────────────────────────────────────────
|
|
91
189
|
|
|
92
190
|
_component.prototype._reposition = function () {
|
|
@@ -94,9 +192,11 @@ import { dispatch, computePlacement, measureHidden, registerComponent } from '..
|
|
|
94
192
|
const rect = this.triggerBtn.getBoundingClientRect();
|
|
95
193
|
const size = measureHidden(this.toggleEl);
|
|
96
194
|
const gap = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--size-xs')) * 16 || 4;
|
|
97
|
-
const
|
|
195
|
+
const position = this.position || DEFAULT_POSITION;
|
|
196
|
+
const p = computePlacement(rect, size, position, gap);
|
|
98
197
|
this.toggleEl.style.top = p.top + 'px';
|
|
99
198
|
this.toggleEl.style.left = p.left + 'px';
|
|
199
|
+
this.toggleEl.setAttribute('data-ln-dropdown-placement', p.placement);
|
|
100
200
|
};
|
|
101
201
|
|
|
102
202
|
// ─── Outside click ─────────────────────────────────────────
|
|
@@ -171,6 +271,7 @@ import { dispatch, computePlacement, measureHidden, registerComponent } from '..
|
|
|
171
271
|
this.dom.removeEventListener('ln-dropdown:request-open', this._onRequestOpen);
|
|
172
272
|
this.dom.removeEventListener('ln-dropdown:request-close', this._onRequestClose);
|
|
173
273
|
this.dom.removeEventListener('ln-dropdown:request-toggle', this._onRequestToggle);
|
|
274
|
+
this.dom.removeEventListener('keydown', this._onKeydown);
|
|
174
275
|
this._removeOutsideClickListener();
|
|
175
276
|
this._removeScrollRepositionListener();
|
|
176
277
|
this._removeResizeCloseListener();
|
|
@@ -178,14 +279,16 @@ import { dispatch, computePlacement, measureHidden, registerComponent } from '..
|
|
|
178
279
|
this.toggleEl.hidePopover();
|
|
179
280
|
}
|
|
180
281
|
if (this.toggleEl) {
|
|
282
|
+
this.toggleEl.removeAttribute('data-ln-dropdown-placement');
|
|
181
283
|
this.toggleEl.removeEventListener('ln-toggle:open', this._onToggleOpen);
|
|
182
284
|
this.toggleEl.removeEventListener('ln-toggle:close', this._onToggleClose);
|
|
183
285
|
}
|
|
184
|
-
dispatch(this.dom, 'ln-dropdown:destroyed', { target: this.dom });
|
|
185
286
|
delete this.dom[DOM_ATTRIBUTE];
|
|
186
287
|
};
|
|
187
288
|
|
|
188
289
|
// ─── Init ──────────────────────────────────────────────────
|
|
189
290
|
|
|
190
|
-
registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-dropdown'
|
|
291
|
+
registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-dropdown', {
|
|
292
|
+
attributes: ATTRIBUTES
|
|
293
|
+
});
|
|
191
294
|
})();
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# ln-editor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Applied to a `<div data-ln-editor>` wrapper enclosing an authored `<div role="toolbar">` and a `<textarea name="...">`.
|
|
4
|
+
> On initialization, it hides the textarea and injects a `<div contenteditable="true" role="textbox">` populated with the initial content.
|
|
5
|
+
> Toolbar clicks execute text commands on the selection; on every `input` event, it sanitizes markup and writes
|
|
6
|
+
> `textarea.value = editable.innerHTML` so native form submissions send current HTML.
|
|
4
7
|
|
|
5
8
|
## Quick Start
|
|
6
9
|
|
|
@@ -133,7 +136,6 @@ This authored-markup design keeps the popover structure fully customizable and a
|
|
|
133
136
|
| `ln-editor:focus` | Notification | `{ target }` | Editing surface focused |
|
|
134
137
|
| `ln-editor:blur` | Notification | `{ target }` | Editing surface blurred |
|
|
135
138
|
| `ln-editor:set-content` | Request | `{ html }` | Set content programmatically — also emits `ln-editor:changed` |
|
|
136
|
-
| `ln-editor:destroyed` | Notification | `{ target }` | Instance destroyed |
|
|
137
139
|
|
|
138
140
|
## API
|
|
139
141
|
|
|
@@ -216,9 +218,9 @@ Include only the buttons you need:
|
|
|
216
218
|
|
|
217
219
|
Visual styling uses the two-layer architecture:
|
|
218
220
|
|
|
219
|
-
- **Mixin:** `
|
|
220
|
-
- **Component:** `
|
|
221
|
-
- **Co-located:** `
|
|
221
|
+
- **Mixin:** `theme/config/mixins/_editor.scss` — `@mixin editor`
|
|
222
|
+
- **Component:** `theme/components/_editor.scss` — applies to `[data-ln-editor]`
|
|
223
|
+
- **Co-located:** `components/ln-editor/ln-editor.scss` — link popover (runtime-cloned from the authored `<template data-ln-template="ln-editor-link-popover">`)
|
|
222
224
|
|
|
223
225
|
The editing surface uses `@include prose` — content looks identical to how it will render in a `.prose` container.
|
|
224
226
|
|
|
@@ -239,7 +241,7 @@ Override with your own selector:
|
|
|
239
241
|
|
|
240
242
|
## 🔧 Internals
|
|
241
243
|
|
|
242
|
-
Source: `
|
|
244
|
+
Source: `components/ln-editor/ln-editor.js`. No cached formatting state — every toolbar sync re-queries `document.queryCommandState` and DOM ancestry on `selectionchange`.
|
|
243
245
|
|
|
244
246
|
### Construction
|
|
245
247
|
|
|
@@ -247,7 +249,7 @@ Finds the `<textarea>`, reads its value as the initial HTML, builds a `contentEd
|
|
|
247
249
|
|
|
248
250
|
### Content sync
|
|
249
251
|
|
|
250
|
-
|
|
252
|
+
Continuous and bidirectional: every surface `input` event copies `innerHTML` into the textarea's `value` and dispatches `ln-editor:changed`. Conversely, synthetic `input` events on the `<textarea>` (e.g. from form population via `ln-form` or coordinators) update `surface.innerHTML` and notify listeners without losing caret position during normal editing.
|
|
251
253
|
|
|
252
254
|
### Formatting flow
|
|
253
255
|
|
|
@@ -263,11 +265,11 @@ On every document `selectionchange`: bail if the selection isn't inside the surf
|
|
|
263
265
|
|
|
264
266
|
### Link insertion
|
|
265
267
|
|
|
266
|
-
Requires a page-authored `<template data-ln-template="ln-editor-link-popover">` — the editor never generates this markup. Flow: save the current selection range, detect if already inside an `<a>` (pre-fill), clone the template via `cloneTemplateScoped`, insert it after the toolbar; on confirm, restore the saved range and either update the existing link's `href` or run `execCommand('createLink')` (adding `rel="noopener noreferrer"` for new links) — either path dispatches `ln-editor:changed` exactly once.
|
|
268
|
+
Requires a page-authored `<template data-ln-template="ln-editor-link-popover">` — the editor never generates this markup. Flow: save the current selection range, detect if already inside an `<a>` (pre-fill), clone the template via `cloneTemplateScoped`, insert it after the toolbar; on confirm, restore the saved range and either update the existing link's `href` or run `execCommand('createLink')` (adding `rel="noopener noreferrer"` for new links) — either path dispatches `ln-editor:changed` exactly once. Dismissing via outside-click closes the popover without yanking focus; explicit Cancel/Escape restores selection to the editing surface.
|
|
267
269
|
|
|
268
270
|
### Destroy
|
|
269
271
|
|
|
270
|
-
Removes all surface
|
|
272
|
+
Removes all surface, toolbar, textarea, document, and form listeners, removes the surface node, restores textarea visibility (removes `data-ln-editor-source`), and dismisses any active link popover.
|
|
271
273
|
|
|
272
274
|
### Permanent constraints
|
|
273
275
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@use '../../theme/config/mixins' as *;
|
|
2
|
+
|
|
3
|
+
[data-ln-debug] {
|
|
4
|
+
// 1. Editor Target Check: [data-ln-editor] must contain a target <textarea>
|
|
5
|
+
[data-ln-editor]:not(:has(textarea)) {
|
|
6
|
+
@include dev-dom-error("[data-ln-editor] is missing a target <textarea> element.");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// 2. Editor Action Check: action string cannot be empty
|
|
10
|
+
[data-ln-editor-action=""] {
|
|
11
|
+
@include dev-inline-error("data-ln-editor-action cannot be empty");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 3. Editor Action Nesting Check: actions must reside inside an editor
|
|
15
|
+
[data-ln-editor-action]:not([data-ln-editor] *) {
|
|
16
|
+
@include dev-inline-error("[data-ln-editor-action] is orphaned (not inside a [data-ln-editor] container)");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){"use strict";function B(){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 D(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 o=1;o<t.length;o++)if(t[o]&&t[o].nodeType===1){r=t[o];break}if(!D(r))return}e.apply(console,t)}}function k(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 Y(e,t,n){const r=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,r);const o=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:r});return e.dispatchEvent(o),o}function T(e,t){if(!document.body){document.addEventListener("DOMContentLoaded",function(){T(e,t)}),console.warn("["+t+'] Script loaded before <body> — add "defer" to your <script> tag');return}e()}const R={};function Z(e,t){R[e]=t}function ee(e){return R[e]||{ingress:t=>t,egress:t=>t}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=Z,window.lnCore.getDataMapper=ee);function te(){typeof window>"u"||(window.lnCore=window.lnCore||{},!window.lnCore._localeObserverBound&&(window.lnCore._localeObserverBound=!0,T(function(){new MutationObserver(function(){k(document,"ln-core:locale-change",{})}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"],subtree:!0})},"ln-core")))}const H={};function ne(e,t){if(!e||typeof t!="object")return;const n=e.toLowerCase().split("-")[0];H[n]=t}function re(e){if(!e)return null;const t=e.toLowerCase().split("-")[0];return H[t]||null}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerLocaleFallback=ne,window.lnCore.getLocaleFallback=re,window.lnCore.ensureLocaleObserver=te);const M={};function $(e,t){M[e]||(M[e]=document.querySelector('[data-ln-template="'+e+'"]'));const n=M[e];return n?n.content.cloneNode(!0):(console.warn("["+(t||"ln-core")+'] Template "'+e+'" not found'),null)}function oe(e,t,n){if(e){const r=e.querySelector('[data-ln-template="'+t+'"]');if(r)return r.content.cloneNode(!0)}return $(t,n)}function F(e,t){if(!e||!t)return e;const n=e.querySelectorAll("[data-ln-field]");for(let l=0;l<n.length;l++){const u=n[l],c=u.getAttribute("data-ln-field");t[c]!=null&&(u.textContent=t[c])}const r=e.querySelectorAll("[data-ln-attr]");for(let l=0;l<r.length;l++){const u=r[l],c=u.getAttribute("data-ln-attr").split(",");for(let h=0;h<c.length;h++){const m=c[h].trim().split(":");if(m.length!==2)continue;const g=m[0].trim(),v=m[1].trim();t[v]!=null&&u.setAttribute(g,t[v])}}const o=e.querySelectorAll("[data-ln-show]");for(let l=0;l<o.length;l++){const u=o[l],c=u.getAttribute("data-ln-show");c in t&&u.classList.toggle("hidden",!t[c])}const i=e.querySelectorAll("[data-ln-class]");for(let l=0;l<i.length;l++){const u=i[l],c=u.getAttribute("data-ln-class").split(",");for(let h=0;h<c.length;h++){const m=c[h].trim().split(":");if(m.length!==2)continue;const g=m[0].trim(),v=m[1].trim();v in t&&u.classList.toggle(g,!!t[v])}}return e}function ie(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)F(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 P(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(l,u){return t[u]!==void 0?t[u]:""}))}const r=function(i,l){return t[l]!==void 0?t[l]:""},o=Array.from(e.querySelectorAll("*"));e.nodeType===1&&o.push(e);for(let i=0;i<o.length;i++){const l=o[i],u=l.attributes;for(let c=0;c<u.length;c++){const h=u[c];h.value.indexOf("{{")!==-1&&l.setAttribute(h.name,h.value.replace(/\{\{\s*(\w+)\s*\}\}/g,r))}}return e}function le(e,t,n,r,o,i){const l={};for(let c=0;c<e.children.length;c++){const h=e.children[c],m=h.getAttribute("data-ln-render-key");m&&(l[m]=h)}const u=document.createDocumentFragment();for(let c=0;c<t.length;c++){const h=t[c],m=String(r(h));let g=l[m];if(g)o(g,h,c);else{const v=$(n,i);if(!v||(P(v,h),g=v.firstElementChild,!g))continue;g.setAttribute("data-ln-render-key",m),o(g,h,c)}u.appendChild(g)}e.textContent="",e.appendChild(u)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.fillTemplate=P,window.lnCore.fill=F,window.lnCore.lnFill=ie,window.lnCore.renderList=le);function W(e,t,n,r,o){if(!e||t===null)return!0;const i=r||"ln-component",l=e.type;if(l==="trigger"||l==="marker"||l==="string"||l==="list"||!l||t===""&&e.fallback!==void 0)return!0;const u=c=>{o?console.warn(c,o):console.warn(c)};if(l==="boolean"){const c=t.trim().toLowerCase();return c!==""&&c!=="true"&&c!=="false"&&c!=="1"&&c!=="0"?(u(`[${i}] Invalid value "${t}" for boolean attribute "${n}". Allowed: "true", "false", or presence-only.`),!1):!0}if(l==="enum"){const c=e.values||[];return c.includes(t)?!0:(u(`[${i}] Invalid value "${t}" for attribute "${n}". Allowed: ${c.join(", ")}. Fallback: "${e.fallback}".`),!1)}if(l==="integer"){if(!/^-?\d+$/.test(t))return u(`[${i}] Invalid integer "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1;const c=parseInt(t,10);return e.min!==void 0&&c<e.min?(u(`[${i}] Value ${c} for attribute "${n}" is less than min (${e.min}).`),!1):e.max!==void 0&&c>e.max?(u(`[${i}] Value ${c} for attribute "${n}" is greater than max (${e.max}).`),!1):!0}if(l==="float")return isNaN(Number(t))?(u(`[${i}] Invalid float "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1):!0;if(l==="json")try{return JSON.parse(t),!0}catch(c){return u(`[${i}] Invalid JSON for attribute "${n}": ${c.message}. Fallback: ${e.fallback}.`),!1}return!0}function se(e){const t={};for(const n in e){const r=e[n];r&&r.effect&&(t[n]=r.effect)}return Object.keys(t).length?t:null}function ae(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 q(e,t,n,r){if(e.nodeType!==1)return;const i=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",l=Array.from(e.querySelectorAll(i));e.matches&&e.matches(i)&&l.push(e);for(const u of l)if(!u[n]){window.lnCore._persistSink&&u.hasAttribute("data-ln-persist")&&window.lnCore._persistSink(u,t);try{u[n]=new r(u)}catch(c){console.error("["+n+"] init failed",u,c)}}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[]);function ce(){return typeof window<"u"&&window.lnCore&&window.lnCore._bootHolds||0}function ue(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 j(){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 de(e){const t=j(),n=e.observed||[];for(let r=0;r<n.length;r++){const o=n[r];t.byAttr.has(o)||t.byAttr.set(o,[]),t.byAttr.get(o).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 o=t.byDeclaredAttr.get(r);o.some(i=>i.componentTag===e.componentTag)||o.push({spec:e.attributes[r],componentTag:e.componentTag})}if(e.onAttrChange||e.effects){t.reactive.push(e);const r=ae(e);if(r===null)t.reactiveWildcard.push(e);else for(const o of r)t.byReactive.has(o)||t.byReactive.set(o,[]),t.byReactive.get(o).push(e)}e.persist&&t.persist.push(e)}function K(e,t,n,r){for(let o=0;o<e.length;o++){const i=e[o];if(!t[i.attribute])continue;const l=i.effects&&i.effects[n];l?l(t,n,r):i.onAttrChange&&(!i.declared||i.declared.has(n))&&i.onAttrChange(t,n,r)}}function fe(e){const t=e.target,n=e.attributeName;if(e.oldValue===t.getAttribute(n))return;const r=j(),o=r.byAttr.get(n);if(B()&&r.byDeclaredAttr&&r.byDeclaredAttr.has(n)&&D(t)){const i=r.byDeclaredAttr.get(n),l=t.getAttribute(n);for(let u=0;u<i.length;u++)W(i[u].spec,l,n,i[u].componentTag,t)}if(window.lnCore._debugSink&&(n.indexOf("data-ln-")===0||o)&&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&&K(i,t,n,e.oldValue),r.reactiveWildcard.length&&K(r.reactiveWildcard,t,n,e.oldValue)}if(o)for(let i=0;i<o.length;i++){const l=o[i];if(l.handler){l.handler(t,n,e.oldValue);continue}l.onAttributeChange&&t[l.attribute]?l.onAttributeChange(t,n):(q(t,l.selector,l.attribute,l.ComponentFn),l.onInit&&l.onInit(t))}}function he(){window.lnCore=window.lnCore||{},!window.lnCore._attrObserverBound&&(window.lnCore._attrObserverBound=!0,T(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++)try{fe(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 V(){return window.lnCore=window.lnCore||{},window.lnCore._lifecycleRegistry=window.lnCore._lifecycleRegistry||[]}function we(e){const t=V();if(t.length){if(e.target)for(let n=0;n<t.length;n++){const r=t[n];if(r.onSubtreeChange){const o=r.query,i=e.target.nodeType===1?e.target.matches(o)?e.target:e.target.closest(o):e.target.parentElement?e.target.parentElement.closest(o):null;i&&r.onSubtreeChange(i,e)}}for(let n=0;n<e.addedNodes.length;n++){const r=e.addedNodes[n];if(r.nodeType===1)for(let o=0;o<t.length;o++){const i=t[o];q(r,i.selector,i.attribute,i.ComponentFn),i.onInit&&i.onInit(r)}}for(let n=0;n<e.removedNodes.length;n++){const r=e.removedNodes[n];if(r.nodeType===1)for(let o=0;o<t.length;o++){const i=t[o],l=i.query,u=Array.from(r.querySelectorAll(l));r.matches&&r.matches(l)&&u.push(r);for(let c=0;c<u.length;c++){const h=u[c];if(!document.contains(h)){const m=h[i.attribute];if(m&&typeof m.destroy=="function")try{m.destroy()}catch(g){console.error("["+i.attribute+"] destroy failed",h,g)}delete h[i.attribute]}}}}}}function be(){window.lnCore=window.lnCore||{},!window.lnCore._lifecycleObserverBound&&(window.lnCore._lifecycleObserverBound=!0,T(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++){const r=t[n];if(r.type==="childList")try{we(r)}catch(o){console.error("[ln-core] lifecycle handler failed",r.target,o)}}}).observe(document.body,{childList:!0,subtree:!0})},"ln-core"))}function ge(e,t,n,r,o={}){const i=o.extraAttributes||[],l=o.onAttributeChange||null,u=o.onSubtreeChange||null,c=o.onInit||null,h=o.onAttrChange||null,m=o.effects||null,g=o.attributes||null,v=g?se(g):m,N=g?new Set(Object.keys(g)):null,E=o.persist||null;function x(f){const p=f||document.body;if(q(p,e,t,n),g&&B())for(const b in g){const C=g[b],w=Array.from(p.querySelectorAll("["+b+"]"));p.matches&&p.matches("["+b+"]")&&w.push(p);for(let _=0;_<w.length;_++){const A=w[_];D(A)&&W(C,A.getAttribute(b),b,r,A)}}c&&c(p)}const L=[];if(e.indexOf("[")!==-1){const f=/\[([\w-]+)/g;let p;for(;(p=f.exec(e))!==null;)L.push(p[1])}else L.push(e);de({selector:e,attribute:t,componentTag:r,attributes:g,ComponentFn:n,onInit:c,observed:L.concat(i),onAttributeChange:l,onAttrChange:h,effects:v,declared:N,persist:E}),he();const a=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]";V().push({selector:e,attribute:t,ComponentFn:n,onInit:c,onSubtreeChange:u,query:a}),be(),window[t]=x;function d(){ce()>0?ue(function(){x(document.body)}):x(document.body)}return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",d):d(),x}function U(e){return(e||"").replace(/^#/,"")}function I(e){const t=e===void 0?location.hash:e,n={},r=U(t);if(!r)return n;const o=r.split("&");for(let i=0;i<o.length;i++){const l=o[i];if(!l)continue;const u=l.indexOf(":"),c=u>-1?l.slice(0,u):l,h=u>-1?l.slice(u+1):"";if(c)try{n[c]=decodeURIComponent(h)}catch{n[c]=h}}return n}function pe(e){if(!e)return null;const t=I();return e in t?t[e]:null}function me(e,t){if(!e)return;const n=I();t==null?delete n[e]:n[e]=String(t);const o=Object.keys(n).map(function(i){const l=n[i];return l===""?i:i+":"+encodeURIComponent(l)}).join("&");U(location.hash)!==o&&(location.hash=o)}function _e(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 ye(e,t){return!t||t==="none"||e===null||e===void 0?null:String(e)+"."+t}function ve(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 Ae(e,t){return!e||!Array.isArray(t)||t.length===0?null:e+":"+t.map(encodeURIComponent).join(",")}function ke(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),o=r?r.split(",").map(function(i){try{return decodeURIComponent(i)}catch{return i}}).filter(Boolean):[];return{key:n,values:o}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=I,window.lnCore.hashGet=pe,window.lnCore.hashSet=me,window.lnCore.hashLinkClick=_e,window.lnCore.resolveHashNamespace=Ce,window.lnCore.hashSortEncode=ye,window.lnCore.hashSortDecode=ve,window.lnCore.hashFilterEncode=Ae,window.lnCore.hashFilterDecode=ke),(function(){const e="data-ln-editor",t="lnEditor";if(window[t]!==void 0)return;const n={"data-ln-editor":{type:"marker",description:"Mounts rich text editor instance on container"},"data-ln-editor-action":{type:"enum",values:["bold","italic","underline","strikethrough","heading-2","heading-3","heading-4","blockquote","code","paragraph","ordered-list","unordered-list","link","unlink","clear","confirm-link","cancel-link"],description:"Rich text toolbar formatting action"},"data-ln-editor-source":{type:"string",description:"Selector or ID of backing textarea synchronized with editor content"}},r={P:!0,BR:!0,STRONG:!0,B:!0,EM:!0,I:!0,U:!0,S:!0,A:!0,UL:!0,OL:!0,LI:!0,H2:!0,H3:!0,H4:!0,BLOCKQUOTE:!0,PRE:!0,CODE:!0,DIV:!0},o={bold:"bold",italic:"italic",underline:"underline",strikethrough:"strikeThrough"},i={"heading-2":"h2","heading-3":"h3","heading-4":"h4",blockquote:"blockquote",code:"pre",paragraph:"p"},l={"ordered-list":"insertOrderedList","unordered-list":"insertUnorderedList"};let u=0;function c(s){return!!(o[s]||i[s]||l[s]||s==="link")}function h(s){this.dom=s;const a=this;if(this._textarea=s.querySelector("textarea"),!this._textarea)return console.warn("[ln-editor] No <textarea> found inside",s),this;const d=this._textarea.getAttribute("placeholder")||"";this._textarea.setAttribute("data-ln-editor-source",""),this._surface=document.createElement("div"),this._surface.className="ln-editor__surface",this._surface.setAttribute("contenteditable","true"),this._surface.setAttribute("role","textbox"),this._surface.setAttribute("aria-multiline","true"),d&&this._surface.setAttribute("data-placeholder",d);const f=this._textarea.id;if(f){const w=s.querySelector('label[for="'+f+'"]');w&&(w.id||(w.id=f+"-label"),this._surface.setAttribute("aria-labelledby",w.id))}this._surface.id=f?f+"-surface":"ln-editor-surface-"+ ++u;const p=this._textarea.value.trim();p&&(this._surface.innerHTML=p);const b=s.querySelector('[role="toolbar"]');if(b&&b.nextSibling?s.insertBefore(this._surface,b.nextSibling):s.appendChild(this._surface),b){b.setAttribute("aria-controls",this._surface.id);const w=b.querySelectorAll("[data-ln-editor-action]");for(let _=0;_<w.length;_++){const A=w[_].getAttribute("data-ln-editor-action");c(A)&&w[_].setAttribute("aria-pressed","false")}}this._onInput=function(){a._syncToTextarea(),k(a.dom,"ln-editor:changed",{html:a._textarea.value,target:a.dom})},this._onMousedownToolbar=function(w){w.target.closest("[data-ln-editor-action]")&&w.preventDefault()},this._onClickToolbar=function(w){const _=w.target.closest("[data-ln-editor-action]");if(!_)return;const A=_.getAttribute("data-ln-editor-action");a._execAction(A)},this._onPaste=function(w){v(a,w)},this._onKeydown=function(w){x(a,w)},this._onSelectionChange=function(){document.contains(a._surface)&&a._updateActiveStates()},this._onFocus=function(){k(a.dom,"ln-editor:focus",{target:a.dom})},this._onBlur=function(){a._syncToTextarea(),k(a.dom,"ln-editor:blur",{target:a.dom})},this._onTextareaInput=function(){a._surface.innerHTML!==a._textarea.value&&(a._surface.innerHTML=a._textarea.value,k(a.dom,"ln-editor:changed",{html:a._textarea.value,target:a.dom}))},this._surface.addEventListener("input",this._onInput),this._surface.addEventListener("paste",this._onPaste),this._surface.addEventListener("keydown",this._onKeydown),this._surface.addEventListener("focus",this._onFocus),this._surface.addEventListener("blur",this._onBlur),this._textarea.addEventListener("input",this._onTextareaInput),b&&(b.addEventListener("mousedown",this._onMousedownToolbar),b.addEventListener("click",this._onClickToolbar)),document.addEventListener("selectionchange",this._onSelectionChange),this._onSetContent=function(w){const _=w.detail&&w.detail.html;_!==void 0&&(a._surface.innerHTML=_,a._syncToTextarea(),k(a.dom,"ln-editor:changed",{html:a._textarea.value,target:a.dom}))},s.addEventListener("ln-editor:set-content",this._onSetContent);const C=this._textarea.form;return C&&(this._onFormReset=function(){setTimeout(function(){a._surface.innerHTML=a._textarea.value,k(s,"ln-editor:changed",{html:a._textarea.value,target:s})},0)},C.addEventListener("reset",this._onFormReset)),this}h.prototype._syncToTextarea=function(){this._textarea&&(this._textarea.value=this._surface.innerHTML)},h.prototype._execAction=function(s){if(!(!s||Y(this.dom,"ln-editor:before-change",{action:s,target:this.dom}).defaultPrevented)){if(this._surface.focus(),o[s])document.execCommand(o[s],!1,null);else if(i[s]){const d=i[s],f=m(this._surface);f&&f.toLowerCase()===d?document.execCommand("formatBlock",!1,"<p>"):document.execCommand("formatBlock",!1,"<"+d+">")}else l[s]?document.execCommand(l[s],!1,null):s==="link"?L(this):s==="unlink"?document.execCommand("unlink",!1,null):s==="clear"&&(document.execCommand("removeFormat",!1,null),document.execCommand("formatBlock",!1,"<p>"));this._syncToTextarea(),this._updateActiveStates()}},h.prototype._updateActiveStates=function(){const s=this.dom.querySelector('[role="toolbar"]');if(!s)return;const a=window.getSelection();if(!a||a.rangeCount===0)return;const d=a.anchorNode;if(!d||!this._surface.contains(d))return;const f=s.querySelectorAll("[data-ln-editor-action]");for(let p=0;p<f.length;p++){const b=f[p],C=b.getAttribute("data-ln-editor-action");let w=!1;if(o[C])try{w=document.queryCommandState(o[C])}catch{}else if(i[C]){const _=m(this._surface);w=_&&_.toLowerCase()===i[C]}else if(l[C])try{w=document.queryCommandState(l[C])}catch{}else C==="link"&&(w=!!g(a.anchorNode,"A",this._surface));c(C)&&b.setAttribute("aria-pressed",String(w)),w?b.classList.add("ln-editor-active"):b.classList.remove("ln-editor-active")}},h.prototype.getHTML=function(){return this._surface?this._surface.innerHTML:""},h.prototype.setHTML=function(s){this._surface&&(this._surface.innerHTML=s,this._syncToTextarea(),k(this.dom,"ln-editor:changed",{html:this._textarea.value,target:this.dom}))},h.prototype.destroy=function(){if(!this.dom[t])return;this._surface&&(this._surface.removeEventListener("input",this._onInput),this._surface.removeEventListener("paste",this._onPaste),this._surface.removeEventListener("keydown",this._onKeydown),this._surface.removeEventListener("focus",this._onFocus),this._surface.removeEventListener("blur",this._onBlur),this._surface.remove());const s=this.dom.querySelector('[role="toolbar"]');s&&(s.removeEventListener("mousedown",this._onMousedownToolbar),s.removeEventListener("click",this._onClickToolbar)),document.removeEventListener("selectionchange",this._onSelectionChange),this.dom.removeEventListener("ln-editor:set-content",this._onSetContent);const a=this._textarea?this._textarea.form:null;if(a&&this._onFormReset&&a.removeEventListener("reset",this._onFormReset),this._textarea&&(this._onTextareaInput&&this._textarea.removeEventListener("input",this._onTextareaInput),this._textarea.removeAttribute("data-ln-editor-source")),this._closeLinkPopover)this._closeLinkPopover();else{const d=this.dom.querySelector(".ln-editor__link-popover");d&&d.remove()}delete this.dom[t]};function m(s){const a=window.getSelection();if(!a||a.rangeCount===0)return null;let d=a.anchorNode;if(!d)return null;for(;d&&d!==s;){if(d.nodeType===1){const f=d.tagName;if(f==="H2"||f==="H3"||f==="H4"||f==="BLOCKQUOTE"||f==="PRE"||f==="P")return f}d=d.parentNode}return null}function g(s,a,d){for(;s&&s!==d;){if(s.nodeType===1&&s.tagName===a)return s;s=s.parentNode}return null}function v(s,a){a.preventDefault();let d="";if(a.clipboardData&&(d=a.clipboardData.getData("text/html"),!d)){const p=a.clipboardData.getData("text/plain");p&&(d=p.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/\n\n/g,"</p><p>").replace(/\n/g,"<br>"),d="<p>"+d+"</p>")}if(!d)return;const f=N(d);f&&document.execCommand("insertHTML",!1,f)}function N(s){const a=document.createElement("div");return a.innerHTML=s,E(a),a.innerHTML}function E(s){const a=Array.from(s.childNodes);for(let d=0;d<a.length;d++){const f=a[d];if(f.nodeType!==3){if(f.nodeType!==1){s.removeChild(f);continue}if(r[f.tagName]){const p=Array.from(f.attributes);for(let b=0;b<p.length;b++){const C=p[b].name;if(f.tagName==="A"&&C==="href"){const w=f.getAttribute("href")||"";/^(https?:|mailto:|\/|#)/.test(w)||f.removeAttribute("href")}else f.removeAttribute(C)}f.tagName==="A"&&f.setAttribute("rel","noopener noreferrer"),E(f)}else{for(;f.firstChild;)s.insertBefore(f.firstChild,f);s.removeChild(f)}}}}function x(s,a){if(!(a.ctrlKey||a.metaKey))return;let d=null;switch(a.key.toLowerCase()){case"b":d="bold";break;case"i":d="italic";break;case"u":d="underline";break;case"k":d="link";break}d&&(a.preventDefault(),s._execAction(d))}function L(s){const a=window.getSelection();if(!a||a.rangeCount===0)return;const d=g(a.anchorNode,"A",s._surface),f=a.getRangeAt(0).cloneRange();s._closeLinkPopover&&s._closeLinkPopover();const p=oe(s.dom,"ln-editor-link-popover","ln-editor");if(!p)return;const b=p.firstElementChild;if(!b)return;const C=b.querySelector('input[type="url"]'),w=b.querySelector('[data-ln-editor-action="confirm-link"]'),_=b.querySelector('[data-ln-editor-action="cancel-link"]');d&&(C.value=d.getAttribute("href")||"");const A=s.dom.querySelector('[role="toolbar"]');A?A.after(b):s.dom.insertBefore(b,s._surface),C.focus();function Q(){const y=window.getSelection();y.removeAllRanges(),y.addRange(f)}function O(){document.removeEventListener("mousedown",J),s._closeLinkPopover=null,b.remove()}function z(){const y=C.value.trim();if(O(),Q(),s._surface.focus(),y)if(d)d.setAttribute("href",y),d.setAttribute("rel","noopener noreferrer"),s._syncToTextarea(),k(s.dom,"ln-editor:changed",{html:s._textarea.value,target:s.dom});else{document.execCommand("createLink",!1,y);const S=window.getSelection();if(S&&S.anchorNode){const X=g(S.anchorNode,"A",s._surface);X&&(X.setAttribute("rel","noopener noreferrer"),s._syncToTextarea())}}else d&&document.execCommand("unlink",!1,null)}function G(){O(),Q(),s._surface.focus()}function xe(){O()}function J(y){const S=s.dom.contains(y.target)&&y.target.closest('[data-ln-editor-action="link"]');!b.contains(y.target)&&!S&&xe()}s._closeLinkPopover=O,w.addEventListener("click",z),_.addEventListener("click",G),C.addEventListener("keydown",function(y){y.key==="Enter"?(y.preventDefault(),z()):y.key==="Escape"&&(y.preventDefault(),G())}),document.addEventListener("mousedown",J)}ge(e,t,h,"ln-editor",{attributes:n})})()})();
|
|
@@ -0,0 +1,58 @@
|
|
|
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-editor",
|
|
4
|
+
"generator": "scripts/sync-ln-schemas.mjs",
|
|
5
|
+
"attributes": {
|
|
6
|
+
"data-ln-debug": {
|
|
7
|
+
"direction": null,
|
|
8
|
+
"sources": [
|
|
9
|
+
"ln-editor-dev.scss"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"data-ln-editor": {
|
|
13
|
+
"direction": null,
|
|
14
|
+
"sources": [
|
|
15
|
+
"ln-editor-dev.scss",
|
|
16
|
+
"src/ln-editor.js"
|
|
17
|
+
],
|
|
18
|
+
"type": "marker",
|
|
19
|
+
"description": "Mounts rich text editor instance on container"
|
|
20
|
+
},
|
|
21
|
+
"data-ln-editor-action": {
|
|
22
|
+
"direction": "author",
|
|
23
|
+
"sources": [
|
|
24
|
+
"ln-editor-dev.scss",
|
|
25
|
+
"src/ln-editor.js"
|
|
26
|
+
],
|
|
27
|
+
"type": "enum",
|
|
28
|
+
"values": [
|
|
29
|
+
"bold",
|
|
30
|
+
"italic",
|
|
31
|
+
"underline",
|
|
32
|
+
"strikethrough",
|
|
33
|
+
"heading-2",
|
|
34
|
+
"heading-3",
|
|
35
|
+
"heading-4",
|
|
36
|
+
"blockquote",
|
|
37
|
+
"code",
|
|
38
|
+
"paragraph",
|
|
39
|
+
"ordered-list",
|
|
40
|
+
"unordered-list",
|
|
41
|
+
"link",
|
|
42
|
+
"unlink",
|
|
43
|
+
"clear",
|
|
44
|
+
"confirm-link",
|
|
45
|
+
"cancel-link"
|
|
46
|
+
],
|
|
47
|
+
"description": "Rich text toolbar formatting action"
|
|
48
|
+
},
|
|
49
|
+
"data-ln-editor-source": {
|
|
50
|
+
"direction": "runtime",
|
|
51
|
+
"sources": [
|
|
52
|
+
"src/ln-editor.js"
|
|
53
|
+
],
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "Selector or ID of backing textarea synchronized with editor content"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// ==========================================================================
|
|
2
|
+
// ln-editor — Functional Link Popover Mechanics
|
|
3
|
+
// ==========================================================================
|
|
4
|
+
|
|
5
|
+
.ln-editor__link-popover {
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
|
|
9
|
+
> input {
|
|
10
|
+
flex: 1;
|
|
11
|
+
min-width: 0;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -6,6 +6,13 @@ import { dispatch, dispatchCancelable, registerComponent, cloneTemplateScoped }
|
|
|
6
6
|
|
|
7
7
|
if (window[DOM_ATTRIBUTE] !== undefined) return;
|
|
8
8
|
|
|
9
|
+
// ─── Attribute Contract (SSOT) ──────────────────────────
|
|
10
|
+
const ATTRIBUTES = {
|
|
11
|
+
'data-ln-editor': { type: 'marker', description: 'Mounts rich text editor instance on container' },
|
|
12
|
+
'data-ln-editor-action': { type: 'enum', values: ['bold', 'italic', 'underline', 'strikethrough', 'heading-2', 'heading-3', 'heading-4', 'blockquote', 'code', 'paragraph', 'ordered-list', 'unordered-list', 'link', 'unlink', 'clear', 'confirm-link', 'cancel-link'], description: 'Rich text toolbar formatting action' },
|
|
13
|
+
'data-ln-editor-source': { type: 'string', description: 'Selector or ID of backing textarea synchronized with editor content' }
|
|
14
|
+
};
|
|
15
|
+
|
|
9
16
|
// ─── Allowed tags for paste sanitization ───────────────────
|
|
10
17
|
const ALLOWED_TAGS = {
|
|
11
18
|
P: true, BR: true, STRONG: true, B: true, EM: true, I: true,
|
|
@@ -160,12 +167,23 @@ import { dispatch, dispatchCancelable, registerComponent, cloneTemplateScoped }
|
|
|
160
167
|
dispatch(self.dom, 'ln-editor:blur', { target: self.dom });
|
|
161
168
|
};
|
|
162
169
|
|
|
170
|
+
this._onTextareaInput = function () {
|
|
171
|
+
if (self._surface.innerHTML !== self._textarea.value) {
|
|
172
|
+
self._surface.innerHTML = self._textarea.value;
|
|
173
|
+
dispatch(self.dom, 'ln-editor:changed', {
|
|
174
|
+
html: self._textarea.value,
|
|
175
|
+
target: self.dom
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
163
180
|
// ─── Bind events ──────────────────────────────────────
|
|
164
181
|
this._surface.addEventListener('input', this._onInput);
|
|
165
182
|
this._surface.addEventListener('paste', this._onPaste);
|
|
166
183
|
this._surface.addEventListener('keydown', this._onKeydown);
|
|
167
184
|
this._surface.addEventListener('focus', this._onFocus);
|
|
168
185
|
this._surface.addEventListener('blur', this._onBlur);
|
|
186
|
+
this._textarea.addEventListener('input', this._onTextareaInput);
|
|
169
187
|
|
|
170
188
|
if (toolbar) {
|
|
171
189
|
toolbar.addEventListener('mousedown', this._onMousedownToolbar);
|
|
@@ -344,16 +362,22 @@ import { dispatch, dispatchCancelable, registerComponent, cloneTemplateScoped }
|
|
|
344
362
|
form.removeEventListener('reset', this._onFormReset);
|
|
345
363
|
}
|
|
346
364
|
|
|
347
|
-
// Restore textarea visibility
|
|
365
|
+
// Restore textarea visibility and remove input listener
|
|
348
366
|
if (this._textarea) {
|
|
367
|
+
if (this._onTextareaInput) {
|
|
368
|
+
this._textarea.removeEventListener('input', this._onTextareaInput);
|
|
369
|
+
}
|
|
349
370
|
this._textarea.removeAttribute('data-ln-editor-source');
|
|
350
371
|
}
|
|
351
372
|
|
|
352
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
373
|
+
// Close/remove link popover if present
|
|
374
|
+
if (this._closeLinkPopover) {
|
|
375
|
+
this._closeLinkPopover();
|
|
376
|
+
} else {
|
|
377
|
+
const popover = this.dom.querySelector('.ln-editor__link-popover');
|
|
378
|
+
if (popover) popover.remove();
|
|
379
|
+
}
|
|
355
380
|
|
|
356
|
-
dispatch(this.dom, 'ln-editor:destroyed', { target: this.dom });
|
|
357
381
|
delete this.dom[DOM_ATTRIBUTE];
|
|
358
382
|
};
|
|
359
383
|
|
|
@@ -520,9 +544,10 @@ import { dispatch, dispatchCancelable, registerComponent, cloneTemplateScoped }
|
|
|
520
544
|
// Save the current selection range so we can restore it
|
|
521
545
|
const savedRange = sel.getRangeAt(0).cloneRange();
|
|
522
546
|
|
|
523
|
-
//
|
|
524
|
-
|
|
525
|
-
|
|
547
|
+
// Clean up any existing active popover for this instance
|
|
548
|
+
if (instance._closeLinkPopover) {
|
|
549
|
+
instance._closeLinkPopover();
|
|
550
|
+
}
|
|
526
551
|
|
|
527
552
|
// Clone popover template
|
|
528
553
|
const fragment = cloneTemplateScoped(instance.dom, 'ln-editor-link-popover', 'ln-editor');
|
|
@@ -554,9 +579,15 @@ import { dispatch, dispatchCancelable, registerComponent, cloneTemplateScoped }
|
|
|
554
579
|
sel.addRange(savedRange);
|
|
555
580
|
}
|
|
556
581
|
|
|
582
|
+
function _cleanupPopover() {
|
|
583
|
+
document.removeEventListener('mousedown', _onDocClick);
|
|
584
|
+
instance._closeLinkPopover = null;
|
|
585
|
+
popover.remove();
|
|
586
|
+
}
|
|
587
|
+
|
|
557
588
|
function _apply() {
|
|
558
589
|
const url = input.value.trim();
|
|
559
|
-
|
|
590
|
+
_cleanupPopover();
|
|
560
591
|
|
|
561
592
|
_restoreSelection();
|
|
562
593
|
instance._surface.focus();
|
|
@@ -594,11 +625,24 @@ import { dispatch, dispatchCancelable, registerComponent, cloneTemplateScoped }
|
|
|
594
625
|
}
|
|
595
626
|
|
|
596
627
|
function _cancel() {
|
|
597
|
-
|
|
628
|
+
_cleanupPopover();
|
|
598
629
|
_restoreSelection();
|
|
599
630
|
instance._surface.focus();
|
|
600
631
|
}
|
|
601
632
|
|
|
633
|
+
function _dismiss() {
|
|
634
|
+
_cleanupPopover();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function _onDocClick(e) {
|
|
638
|
+
const inThisLinkBtn = instance.dom.contains(e.target) && e.target.closest('[data-ln-editor-action="link"]');
|
|
639
|
+
if (!popover.contains(e.target) && !inThisLinkBtn) {
|
|
640
|
+
_dismiss();
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
instance._closeLinkPopover = _cleanupPopover;
|
|
645
|
+
|
|
602
646
|
confirmBtn.addEventListener('click', _apply);
|
|
603
647
|
removeBtn.addEventListener('click', _cancel);
|
|
604
648
|
|
|
@@ -611,9 +655,13 @@ import { dispatch, dispatchCancelable, registerComponent, cloneTemplateScoped }
|
|
|
611
655
|
_cancel();
|
|
612
656
|
}
|
|
613
657
|
});
|
|
658
|
+
|
|
659
|
+
document.addEventListener('mousedown', _onDocClick);
|
|
614
660
|
}
|
|
615
661
|
|
|
616
662
|
// ─── Init ─────────────────────────────────────────────────
|
|
617
663
|
|
|
618
|
-
registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-editor'
|
|
664
|
+
registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-editor', {
|
|
665
|
+
attributes: ATTRIBUTES
|
|
666
|
+
});
|
|
619
667
|
})();
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# ln-external-links
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Runs globally across `document.body` watching all `<a>` and `<area>` elements. When an element's `href` host
|
|
4
|
+
> differs from `window.location.host`, it sets `target="_blank"`, merges `rel="noopener noreferrer"`,
|
|
5
|
+
> appends a `<span class="sr-only">(opens in new tab)</span>`, and marks it with `data-ln-external-link="processed"` to prevent redundant mutations.
|
|
4
6
|
|
|
5
7
|
## Markup anatomy
|
|
6
8
|
|
|
@@ -306,22 +308,22 @@ When loaded via the main bundle, the component automatically initializes and reg
|
|
|
306
308
|
If you only need the external links auto-decoration functionality without the rest of the `ln-ashlar` suite, you can load the compiled standalone IIFE version:
|
|
307
309
|
|
|
308
310
|
```html
|
|
309
|
-
<script src="
|
|
311
|
+
<script src="components/ln-external-links/ln-external-links.js" defer></script>
|
|
310
312
|
```
|
|
311
313
|
|
|
312
314
|
This self-contained script executes immediately and sets up the necessary hooks and global initializer `window.lnExternalLinks` without requiring any external dependencies.
|
|
313
315
|
|
|
314
316
|
### 3. Source & Development Path
|
|
315
317
|
|
|
316
|
-
* **Active Development Source:** [
|
|
317
|
-
* **Compiled Standalone:** [
|
|
318
|
+
* **Active Development Source:** [components/ln-external-links/src/ln-external-links.js](file:///c:/laragon/www/ln-ashlar/components/ln-external-links/src/ln-external-links.js) — The authoring source code containing the ES module implementation. Any new features, bug fixes, or behavioral changes must be written here.
|
|
319
|
+
* **Compiled Standalone:** [components/ln-external-links/ln-external-links.js](file:///c:/laragon/www/ln-ashlar/components/ln-external-links/ln-external-links.js) — The compiled distribution file generated from the source code during the build process. Do not edit this file directly.
|
|
318
320
|
|
|
319
321
|
## Related
|
|
320
322
|
|
|
321
323
|
- **`@mixin ln-icon`** is unused here — there's no icon decoration on
|
|
322
324
|
external links by default. If you want a "↗" indicator, add it in
|
|
323
325
|
project SCSS via `a[data-ln-external-link="processed"]::after`.
|
|
324
|
-
- **`.sr-only`** (`
|
|
326
|
+
- **`.sr-only`** (`theme/utilities/_utilities.scss`) — the
|
|
325
327
|
visually-hidden utility used by the appended hint span. If your
|
|
326
328
|
project does not include ln-ashlar utilities, supply your own
|
|
327
329
|
`.sr-only` definition or the hint will be visible.
|
|
@@ -339,7 +341,7 @@ This self-contained script executes immediately and sets up the necessary hooks
|
|
|
339
341
|
|
|
340
342
|
## 🔧 Internals
|
|
341
343
|
|
|
342
|
-
Source: `
|
|
344
|
+
Source: `components/ln-external-links/ln-external-links.js`. A **global service** (js skill §10) — no `registerComponent`, no consumer-facing `data-ln-*` opt-in attribute. It loads, runs once, and sets up a click delegate + `MutationObserver` on `document.body`.
|
|
343
345
|
|
|
344
346
|
### "External" test
|
|
345
347
|
|
|
@@ -347,7 +349,7 @@ The single test: truthy `link.hostname` AND `link.hostname !== window.location.h
|
|
|
347
349
|
|
|
348
350
|
### Script-load lifecycle
|
|
349
351
|
|
|
350
|
-
Both the click delegate and the observer are wrapped in `guardBody` (`ln-core
|
|
352
|
+
Both the click delegate and the observer are wrapped in `guardBody` (`ln-core`): if `document.body` is `null` at script eval (e.g. loaded in `<head>` without `defer`), setup re-schedules itself for `DOMContentLoaded`. The initial `_processLinks()` scan follows the same `readyState === 'loading'` check. A `window.lnExternalLinks` sentinel guards against double-execution if the script loads twice.
|
|
351
353
|
|
|
352
354
|
### Processing pipeline
|
|
353
355
|
|
|
@@ -377,4 +379,4 @@ The sr-only hint has no i18n hook — the string is hard-coded English. Projects
|
|
|
377
379
|
|
|
378
380
|
### Cross-component coordination
|
|
379
381
|
|
|
380
|
-
Imports only `dispatch` and `guardBody` from `ln-core
|
|
382
|
+
Imports only `dispatch` and `guardBody` from `ln-core`. Does not listen for any `ln-*` event and is not signaled by `ln-ajax`/data-loading components when they inject markup — decoration is purely insertion-driven via the shared observer.
|