@0001-labs/design-system-1 0.3.0-alpha.13 → 0.3.0-alpha.15
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/DS1/0-face/calendar.ts +194 -0
- package/DS1/0-face/carousel.ts +623 -0
- package/DS1/0-face/component-visibility.ts +14 -0
- package/DS1/0-face/copy-text.ts +96 -0
- package/DS1/0-face/device.ts +8 -58
- package/DS1/0-face/dialog-swipe.ts +249 -0
- package/DS1/0-face/dialogs.ts +208 -0
- package/DS1/0-face/drawer.ts +258 -0
- package/DS1/0-face/feedback-words.ts +36 -0
- package/DS1/0-face/feedback.ts +40 -0
- package/DS1/0-face/format.ts +700 -0
- package/DS1/0-face/generated/toast-assets.ts +18 -0
- package/DS1/0-face/grid-fit.ts +1 -1
- package/DS1/0-face/grid.ts +3 -2
- package/DS1/0-face/header.ts +78 -0
- package/DS1/0-face/i18n.ts +93 -18
- package/DS1/0-face/keys.ts +276 -0
- package/DS1/0-face/languages.ts +4 -4
- package/DS1/0-face/layout-inspector.ts +6 -6
- package/DS1/0-face/library-styles.ts +33 -0
- package/DS1/0-face/load-toaster.ts +7 -0
- package/DS1/0-face/preference-options.ts +29 -0
- package/DS1/0-face/preferences.ts +3 -11
- package/DS1/0-face/pricing.ts +98 -43
- package/DS1/0-face/scaling.ts +160 -4
- package/DS1/0-face/shared-words.ts +9 -4
- package/DS1/0-face/sidebar.ts +201 -0
- package/DS1/0-face/theme-bootstrap.js +184 -0
- package/DS1/0-face/theme.ts +89 -47
- package/DS1/0-face/toast-queue.ts +157 -0
- package/DS1/0-face/toaster.ts +384 -0
- package/DS1/0-face/tooltips.ts +157 -15
- package/DS1/0-face/translations/da.json +60 -1
- package/DS1/0-face/translations/de.json +60 -1
- package/DS1/0-face/translations/en.json +60 -1
- package/DS1/0-face/translations/es.json +60 -1
- package/DS1/0-face/translations/fi.json +60 -1
- package/DS1/0-face/translations/fr.json +60 -1
- package/DS1/0-face/translations/is.json +60 -1
- package/DS1/0-face/translations/it.json +60 -1
- package/DS1/0-face/translations/ja.json +60 -1
- package/DS1/0-face/translations/ko.json +60 -1
- package/DS1/0-face/translations/nb.json +60 -1
- package/DS1/0-face/translations/pt-BR.json +60 -1
- package/DS1/0-face/translations/pt.json +60 -1
- package/DS1/0-face/translations/sv.json +60 -1
- package/DS1/0-face/translations/zh.json +60 -1
- package/DS1/0-face/translations-format.ts +3 -4
- package/DS1/1-root/card.css +192 -0
- package/DS1/1-root/carousel.css +136 -0
- package/DS1/1-root/cjk.css +23 -0
- package/DS1/1-root/component-metrics.css +1 -0
- package/DS1/1-root/component-tokens.css +51 -8
- package/DS1/1-root/controls.css +891 -14
- package/DS1/1-root/dialog-placement.css +191 -0
- package/DS1/1-root/document.css +183 -0
- package/DS1/1-root/drawer.css +83 -0
- package/DS1/1-root/empty.css +83 -0
- package/DS1/1-root/figures.css +141 -0
- package/DS1/1-root/fonts/Iosevka-LICENSE.md +3 -1
- package/DS1/1-root/fonts/Iosevka-Regular.woff2 +0 -0
- package/DS1/1-root/fonts.css +69 -0
- package/DS1/1-root/footer.css +186 -0
- package/DS1/1-root/header.css +165 -0
- package/DS1/1-root/library-tokens.ts +91 -0
- package/DS1/1-root/measures.ts +53 -0
- package/DS1/1-root/notice.css +165 -0
- package/DS1/1-root/one.css +14 -84
- package/DS1/1-root/one.semantic.css +200 -148
- package/DS1/1-root/palette.css +58 -0
- package/DS1/1-root/palette.ts +19 -0
- package/DS1/1-root/progress.css +356 -0
- package/DS1/1-root/prose.css +202 -0
- package/DS1/1-root/row.css +300 -0
- package/DS1/1-root/table.css +111 -0
- package/DS1/1-root/text.css +70 -0
- package/DS1/2-core/button-split.ts +55 -0
- package/DS1/2-core/chart/chart.css +169 -0
- package/DS1/2-core/chart/grid-chart.ts +554 -0
- package/DS1/2-core/ds-avatar.ts +25 -6
- package/DS1/2-core/ds-badge.ts +10 -10
- package/DS1/2-core/ds-button.ts +111 -26
- package/DS1/2-core/ds-card.ts +135 -105
- package/DS1/2-core/ds-chart.ts +327 -0
- package/DS1/2-core/ds-composer.ts +8 -2
- package/DS1/2-core/ds-cycle.ts +97 -21
- package/DS1/2-core/ds-empty.ts +120 -0
- package/DS1/2-core/ds-icon.ts +11 -2
- package/DS1/2-core/ds-input.ts +165 -6
- package/DS1/2-core/ds-message.ts +218 -0
- package/DS1/2-core/ds-notice.ts +10 -0
- package/DS1/2-core/ds-progress.ts +380 -0
- package/DS1/2-core/ds-stat.ts +174 -0
- package/DS1/2-core/ds-switch.ts +1 -0
- package/DS1/2-core/ds-text.ts +15 -2
- package/DS1/2-core/ds-toast.ts +354 -0
- package/DS1/2-core/figures.ts +105 -0
- package/DS1/2-core/generated/ds-icon-map.ts +238 -128
- package/DS1/2-core/generated/icon-avatar.ts +2 -0
- package/DS1/2-core/generated/icons-extra.ts +120 -65
- package/DS1/2-core/generated/icons-immediate.ts +1 -1
- package/DS1/2-core/styles/ds-avatar.css +9 -8
- package/DS1/2-core/styles/ds-button-host.css +26 -3
- package/DS1/2-core/styles/ds-button-split.css +67 -0
- package/DS1/2-core/styles/ds-button.css +49 -15
- package/DS1/2-core/styles/ds-card.css +34 -0
- package/DS1/2-core/styles/ds-composer.css +13 -0
- package/DS1/2-core/styles/ds-cycle.css +10 -0
- package/DS1/2-core/styles/ds-empty.css +18 -0
- package/DS1/2-core/styles/ds-gap.css +1 -0
- package/DS1/2-core/styles/ds-icon.css +1 -0
- package/DS1/2-core/styles/ds-input.css +59 -6
- package/DS1/2-core/styles/ds-notice.css +32 -0
- package/DS1/2-core/styles/ds-progress.css +25 -0
- package/DS1/2-core/styles/ds-select.css +2 -2
- package/DS1/2-core/styles/ds-split.css +2 -62
- package/DS1/2-core/styles/ds-stat.css +34 -0
- package/DS1/2-core/styles/ds-switch.css +16 -1
- package/DS1/2-core/styles/ds-text.css +43 -12
- package/DS1/2-core/styles/ds-toast.css +182 -0
- package/DS1/2-core/styles/ds-tooltip.css +1 -0
- package/DS1/3-unit/calendar-base.ts +263 -0
- package/DS1/3-unit/ds-accordion.ts +9 -1
- package/DS1/3-unit/ds-avatar-group.ts +1 -0
- package/DS1/3-unit/ds-calendar-large.ts +349 -0
- package/DS1/3-unit/ds-calendar.ts +344 -0
- package/DS1/3-unit/ds-carousel.ts +368 -0
- package/DS1/3-unit/ds-date-picker.ts +540 -0
- package/DS1/3-unit/ds-form.ts +45 -37
- package/DS1/3-unit/ds-list.ts +48 -1
- package/DS1/3-unit/ds-matrix.ts +318 -0
- package/DS1/3-unit/ds-menu.ts +8 -4
- package/DS1/3-unit/ds-row.ts +84 -1
- package/DS1/3-unit/ds-sidebar.ts +366 -0
- package/DS1/3-unit/ds-table.ts +75 -34
- package/DS1/3-unit/styles/ds-accordion.css +1 -0
- package/DS1/3-unit/styles/ds-avatar-group.css +2 -4
- package/DS1/3-unit/styles/ds-calendar-large.css +165 -0
- package/DS1/3-unit/styles/ds-calendar.css +238 -0
- package/DS1/3-unit/styles/ds-carousel.css +30 -0
- package/DS1/3-unit/styles/ds-date-picker.css +124 -0
- package/DS1/3-unit/styles/ds-list.css +1 -0
- package/DS1/3-unit/styles/ds-matrix.css +203 -0
- package/DS1/3-unit/styles/ds-menu.css +31 -6
- package/DS1/3-unit/styles/ds-row.css +84 -2
- package/DS1/3-unit/styles/ds-sidebar.css +134 -0
- package/DS1/3-unit/styles/ds-table.css +3 -74
- package/DS1/4-page/ds-dialog.ts +40 -4
- package/DS1/4-page/ds-document.ts +240 -0
- package/DS1/4-page/ds-footer.ts +165 -0
- package/DS1/4-page/ds-grid.ts +39 -59
- package/DS1/4-page/ds-header.ts +171 -0
- package/DS1/4-page/ds-page.ts +184 -0
- package/DS1/4-page/email.ts +2 -3
- package/DS1/4-page/styles/ds-container.css +1 -0
- package/DS1/4-page/styles/ds-document.css +18 -0
- package/DS1/4-page/styles/ds-footer.css +29 -0
- package/DS1/4-page/styles/ds-grid.css +31 -85
- package/DS1/4-page/styles/ds-header.css +57 -0
- package/DS1/4-page/styles/ds-page.css +214 -0
- package/DS1/components.ts +7 -1
- package/DS1/index.ts +24 -6
- package/DS1/react/avatar.ts +4 -5
- package/DS1/react/button-split.ts +29 -0
- package/DS1/react/button.ts +32 -13
- package/DS1/react/carousel.ts +76 -0
- package/DS1/react/dialog.ts +36 -5
- package/DS1/react/document.ts +14 -0
- package/DS1/react/empty.ts +32 -0
- package/DS1/react/footer.ts +17 -0
- package/DS1/react/header.ts +15 -0
- package/DS1/react/input.ts +28 -17
- package/DS1/react/message.ts +73 -0
- package/DS1/react/notice.ts +59 -0
- package/DS1/react/page.ts +13 -0
- package/DS1/react/progress.ts +99 -0
- package/DS1/react/sidebar.ts +71 -0
- package/DS1/react/stat.ts +73 -0
- package/DS1/react/toast.ts +82 -0
- package/DS1/x-icon/Tabler-LICENSE.md +27 -0
- package/DS1/x-icon/airtable.svg +5 -0
- package/DS1/x-icon/attach.svg +3 -0
- package/DS1/x-icon/audio.svg +3 -0
- package/DS1/x-icon/bag-alt.svg +3 -0
- package/DS1/x-icon/bank.svg +3 -0
- package/DS1/x-icon/banknote.svg +3 -0
- package/DS1/x-icon/bolt.svg +3 -0
- package/DS1/x-icon/book-alt.svg +3 -0
- package/DS1/x-icon/book.svg +4 -0
- package/DS1/x-icon/box.svg +3 -0
- package/DS1/x-icon/briefcase.svg +3 -0
- package/DS1/x-icon/building.svg +4 -0
- package/DS1/x-icon/car.svg +3 -0
- package/DS1/x-icon/card.svg +3 -0
- package/DS1/x-icon/cart.svg +3 -0
- package/DS1/x-icon/{ezo-android-screen-stats.svg → chevron-down.svg} +1 -1
- package/DS1/x-icon/{ezo-item-button-start.svg → chevron-left.svg} +1 -1
- package/DS1/x-icon/{ezo-android-screen-lists.svg → chevron-right.svg} +1 -1
- package/DS1/x-icon/{ezo-item-button-reopen.svg → chevron-up.svg} +1 -1
- package/DS1/x-icon/cloud.svg +3 -0
- package/DS1/x-icon/coffee.svg +3 -0
- package/DS1/x-icon/coins.svg +3 -0
- package/DS1/x-icon/cursor.svg +3 -0
- package/DS1/x-icon/curve.svg +7 -0
- package/DS1/x-icon/dislike.svg +4 -0
- package/DS1/x-icon/dot-bank.svg +3 -0
- package/DS1/x-icon/dot-banknote.svg +3 -0
- package/DS1/x-icon/dot-banknotes.svg +3 -0
- package/DS1/x-icon/dot-card.svg +3 -0
- package/DS1/x-icon/dot-coin.svg +3 -0
- package/DS1/x-icon/dot-coins.svg +3 -0
- package/DS1/x-icon/dot-receipt.svg +3 -0
- package/DS1/x-icon/dot-safe.svg +3 -0
- package/DS1/x-icon/dot-transfer.svg +3 -0
- package/DS1/x-icon/dot-wallet.svg +3 -0
- package/DS1/x-icon/download.svg +4 -0
- package/DS1/x-icon/drive.svg +5 -0
- package/DS1/x-icon/drop.svg +3 -0
- package/DS1/x-icon/dropbox.svg +3 -0
- package/DS1/x-icon/dumbbell.svg +3 -0
- package/DS1/x-icon/eye-off.svg +3 -0
- package/DS1/x-icon/eye.svg +3 -0
- package/DS1/x-icon/facebook.svg +3 -0
- package/DS1/x-icon/figma.svg +5 -0
- package/DS1/x-icon/flame.svg +3 -0
- package/DS1/x-icon/flip.svg +5 -0
- package/DS1/x-icon/food.svg +3 -0
- package/DS1/x-icon/fuel.svg +3 -0
- package/DS1/x-icon/game.svg +3 -0
- package/DS1/x-icon/gift.svg +3 -0
- package/DS1/x-icon/github.svg +3 -0
- package/DS1/x-icon/heart.svg +3 -0
- package/DS1/x-icon/history.svg +3 -0
- package/DS1/x-icon/home-alt.svg +3 -0
- package/DS1/x-icon/income.svg +3 -0
- package/DS1/x-icon/info.svg +5 -0
- package/DS1/x-icon/instagram.svg +5 -0
- package/DS1/x-icon/jpbank.svg +3 -0
- package/DS1/x-icon/key.svg +4 -0
- package/DS1/x-icon/keyboard.svg +3 -0
- package/DS1/x-icon/like.svg +4 -0
- package/DS1/x-icon/linkedin.svg +7 -0
- package/DS1/x-icon/loom.svg +6 -0
- package/DS1/x-icon/map.svg +4 -0
- package/DS1/x-icon/medical.svg +3 -0
- package/DS1/x-icon/minus.svg +3 -0
- package/DS1/x-icon/moon.svg +3 -0
- package/DS1/x-icon/move.svg +3 -0
- package/DS1/x-icon/music-alt.svg +3 -0
- package/DS1/x-icon/music.svg +5 -0
- package/DS1/x-icon/notion.svg +6 -0
- package/DS1/x-icon/offline.svg +3 -0
- package/DS1/x-icon/pause.svg +4 -0
- package/DS1/x-icon/paw.svg +3 -0
- package/DS1/x-icon/pdf.svg +3 -0
- package/DS1/x-icon/people.svg +5 -0
- package/DS1/x-icon/percent.svg +3 -0
- package/DS1/x-icon/phone.svg +3 -0
- package/DS1/x-icon/pill.svg +3 -0
- package/DS1/x-icon/plane.svg +3 -0
- package/DS1/x-icon/postal.svg +3 -0
- package/DS1/x-icon/present.svg +4 -0
- package/DS1/x-icon/reaction.svg +5 -0
- package/DS1/x-icon/receipt.svg +3 -0
- package/DS1/x-icon/refresh.svg +3 -0
- package/DS1/x-icon/refund.svg +3 -0
- package/DS1/x-icon/repeat.svg +3 -0
- package/DS1/x-icon/rotate.svg +4 -0
- package/DS1/x-icon/safe.svg +3 -0
- package/DS1/x-icon/school-alt.svg +3 -0
- package/DS1/x-icon/shield-alt.svg +3 -0
- package/DS1/x-icon/shield.svg +4 -0
- package/DS1/x-icon/shirt.svg +3 -0
- package/DS1/x-icon/shop.svg +3 -0
- package/DS1/x-icon/sign-in.svg +3 -0
- package/DS1/x-icon/sign-out.svg +3 -0
- package/DS1/x-icon/slack.svg +6 -0
- package/DS1/x-icon/sparkle.svg +3 -0
- package/DS1/x-icon/start.svg +4 -0
- package/DS1/x-icon/stop.svg +3 -0
- package/DS1/x-icon/sun.svg +3 -0
- package/DS1/x-icon/system.svg +4 -0
- package/DS1/x-icon/tag.svg +3 -0
- package/DS1/x-icon/ticket.svg +3 -0
- package/DS1/x-icon/title-struck.svg +4 -0
- package/DS1/x-icon/train.svg +3 -0
- package/DS1/x-icon/transfer.svg +3 -0
- package/DS1/x-icon/tv.svg +3 -0
- package/DS1/x-icon/upload.svg +4 -0
- package/DS1/x-icon/video.svg +4 -0
- package/DS1/x-icon/vimeo.svg +3 -0
- package/DS1/x-icon/wallet.svg +3 -0
- package/DS1/x-icon/wifi.svg +3 -0
- package/DS1/x-icon/window.svg +4 -0
- package/DS1/x-icon/wrench.svg +3 -0
- package/DS1/x-icon/x-brand.svg +4 -0
- package/DS1/x-icon/youtube.svg +4 -0
- package/README.md +3 -3
- package/dist/0-face/calendar.d.ts +75 -0
- package/dist/0-face/calendar.d.ts.map +1 -0
- package/dist/0-face/calendar.js +149 -0
- package/dist/0-face/carousel.d.ts +174 -0
- package/dist/0-face/carousel.d.ts.map +1 -0
- package/dist/0-face/carousel.js +583 -0
- package/dist/0-face/component-visibility.d.ts +7 -0
- package/dist/0-face/component-visibility.d.ts.map +1 -0
- package/dist/0-face/component-visibility.js +11 -0
- package/dist/0-face/copy-text.d.ts +11 -0
- package/dist/0-face/copy-text.d.ts.map +1 -0
- package/dist/0-face/copy-text.js +97 -0
- package/dist/0-face/device.d.ts +3 -7
- package/dist/0-face/device.d.ts.map +1 -1
- package/dist/0-face/device.js +7 -41
- package/dist/0-face/dialog-swipe.d.ts +49 -0
- package/dist/0-face/dialog-swipe.d.ts.map +1 -0
- package/dist/0-face/dialog-swipe.js +239 -0
- package/dist/0-face/dialogs.d.ts +33 -0
- package/dist/0-face/dialogs.d.ts.map +1 -0
- package/dist/0-face/dialogs.js +173 -0
- package/dist/0-face/drawer.d.ts +64 -0
- package/dist/0-face/drawer.d.ts.map +1 -0
- package/dist/0-face/drawer.js +256 -0
- package/dist/0-face/feedback-words.d.ts +21 -0
- package/dist/0-face/feedback-words.d.ts.map +1 -0
- package/dist/0-face/feedback-words.js +30 -0
- package/dist/0-face/feedback.d.ts +41 -0
- package/dist/0-face/feedback.d.ts.map +1 -0
- package/dist/0-face/feedback.js +35 -0
- package/dist/0-face/format.d.ts +166 -0
- package/dist/0-face/format.d.ts.map +1 -0
- package/dist/0-face/format.js +445 -0
- package/dist/0-face/generated/toast-assets.d.ts +11 -0
- package/dist/0-face/generated/toast-assets.d.ts.map +1 -0
- package/dist/0-face/generated/toast-assets.js +14 -0
- package/dist/0-face/grid-fit.js +1 -1
- package/dist/0-face/grid.d.ts.map +1 -1
- package/dist/0-face/grid.js +2 -1
- package/dist/0-face/header.d.ts +38 -0
- package/dist/0-face/header.d.ts.map +1 -0
- package/dist/0-face/header.js +80 -0
- package/dist/0-face/i18n.d.ts +32 -2
- package/dist/0-face/i18n.d.ts.map +1 -1
- package/dist/0-face/i18n.js +65 -19
- package/dist/0-face/keys.d.ts +104 -0
- package/dist/0-face/keys.d.ts.map +1 -0
- package/dist/0-face/keys.js +253 -0
- package/dist/0-face/languages.d.ts +2 -2
- package/dist/0-face/languages.js +3 -3
- package/dist/0-face/layout-inspector.js +6 -6
- package/dist/0-face/library-styles.d.ts +7 -0
- package/dist/0-face/library-styles.d.ts.map +1 -0
- package/dist/0-face/library-styles.js +29 -0
- package/dist/0-face/load-toaster.d.ts +8 -0
- package/dist/0-face/load-toaster.d.ts.map +1 -0
- package/dist/0-face/load-toaster.js +7 -0
- package/dist/0-face/preference-options.d.ts +34 -0
- package/dist/0-face/preference-options.d.ts.map +1 -0
- package/dist/0-face/preference-options.js +1 -0
- package/dist/0-face/preferences.d.ts +3 -12
- package/dist/0-face/preferences.d.ts.map +1 -1
- package/dist/0-face/pricing.d.ts +19 -9
- package/dist/0-face/pricing.d.ts.map +1 -1
- package/dist/0-face/pricing.js +81 -40
- package/dist/0-face/scaling.d.ts +71 -1
- package/dist/0-face/scaling.d.ts.map +1 -1
- package/dist/0-face/scaling.js +108 -3
- package/dist/0-face/shared-words.d.ts +9 -4
- package/dist/0-face/shared-words.d.ts.map +1 -1
- package/dist/0-face/sidebar.d.ts +69 -0
- package/dist/0-face/sidebar.d.ts.map +1 -0
- package/dist/0-face/sidebar.js +191 -0
- package/dist/0-face/theme-bootstrap.d.ts +1 -0
- package/dist/0-face/theme-bootstrap.d.ts.map +1 -0
- package/dist/0-face/theme-bootstrap.js +214 -0
- package/dist/0-face/theme.d.ts +32 -0
- package/dist/0-face/theme.d.ts.map +1 -1
- package/dist/0-face/theme.js +69 -43
- package/dist/0-face/toast-queue.d.ts +110 -0
- package/dist/0-face/toast-queue.d.ts.map +1 -0
- package/dist/0-face/toast-queue.js +98 -0
- package/dist/0-face/toaster.d.ts +27 -0
- package/dist/0-face/toaster.d.ts.map +1 -0
- package/dist/0-face/toaster.js +373 -0
- package/dist/0-face/tooltips.d.ts +24 -1
- package/dist/0-face/tooltips.d.ts.map +1 -1
- package/dist/0-face/tooltips.js +155 -16
- package/dist/0-face/translations/da.json +60 -1
- package/dist/0-face/translations/de.json +60 -1
- package/dist/0-face/translations/en.json +60 -1
- package/dist/0-face/translations/es.json +60 -1
- package/dist/0-face/translations/fi.json +60 -1
- package/dist/0-face/translations/fr.json +60 -1
- package/dist/0-face/translations/is.json +60 -1
- package/dist/0-face/translations/it.json +60 -1
- package/dist/0-face/translations/ja.json +60 -1
- package/dist/0-face/translations/ko.json +60 -1
- package/dist/0-face/translations/nb.json +60 -1
- package/dist/0-face/translations/pt-BR.json +60 -1
- package/dist/0-face/translations/pt.json +60 -1
- package/dist/0-face/translations/sv.json +60 -1
- package/dist/0-face/translations/zh.json +60 -1
- package/dist/0-face/translations-format.d.ts.map +1 -1
- package/dist/0-face/translations-format.js +3 -4
- package/dist/1-root/library-tokens.d.ts +31 -0
- package/dist/1-root/library-tokens.d.ts.map +1 -0
- package/dist/1-root/library-tokens.js +84 -0
- package/dist/1-root/measures.d.ts +52 -0
- package/dist/1-root/measures.d.ts.map +1 -0
- package/dist/1-root/measures.js +49 -0
- package/dist/1-root/palette.d.ts +5 -0
- package/dist/1-root/palette.d.ts.map +1 -1
- package/dist/1-root/palette.js +18 -0
- package/dist/2-core/button-split.d.ts +21 -0
- package/dist/2-core/button-split.d.ts.map +1 -0
- package/dist/2-core/button-split.js +48 -0
- package/dist/2-core/chart/grid-chart.d.ts +113 -0
- package/dist/2-core/chart/grid-chart.d.ts.map +1 -0
- package/dist/2-core/chart/grid-chart.js +466 -0
- package/dist/2-core/ds-avatar.d.ts +20 -3
- package/dist/2-core/ds-avatar.d.ts.map +1 -1
- package/dist/2-core/ds-avatar.js +19 -5
- package/dist/2-core/ds-badge.js +10 -13
- package/dist/2-core/ds-button.d.ts +76 -0
- package/dist/2-core/ds-button.d.ts.map +1 -1
- package/dist/2-core/ds-button.js +101 -26
- package/dist/2-core/ds-card.d.ts +76 -11
- package/dist/2-core/ds-card.d.ts.map +1 -1
- package/dist/2-core/ds-card.js +129 -105
- package/dist/2-core/ds-chart.d.ts +105 -0
- package/dist/2-core/ds-chart.d.ts.map +1 -0
- package/dist/2-core/ds-chart.js +390 -0
- package/dist/2-core/ds-composer.d.ts +7 -0
- package/dist/2-core/ds-composer.d.ts.map +1 -1
- package/dist/2-core/ds-composer.js +1 -1
- package/dist/2-core/ds-cycle.d.ts +36 -2
- package/dist/2-core/ds-cycle.d.ts.map +1 -1
- package/dist/2-core/ds-cycle.js +95 -18
- package/dist/2-core/ds-empty.d.ts +74 -0
- package/dist/2-core/ds-empty.d.ts.map +1 -0
- package/dist/2-core/ds-empty.js +96 -0
- package/dist/2-core/ds-icon.d.ts +6 -0
- package/dist/2-core/ds-icon.d.ts.map +1 -1
- package/dist/2-core/ds-icon.js +12 -2
- package/dist/2-core/ds-input.d.ts +39 -1
- package/dist/2-core/ds-input.d.ts.map +1 -1
- package/dist/2-core/ds-input.js +150 -6
- package/dist/2-core/ds-message.d.ts +154 -0
- package/dist/2-core/ds-message.d.ts.map +1 -0
- package/dist/2-core/ds-message.js +177 -0
- package/dist/2-core/ds-notice.d.ts +13 -0
- package/dist/2-core/ds-notice.d.ts.map +1 -0
- package/dist/2-core/ds-notice.js +11 -0
- package/dist/2-core/ds-progress.d.ts +200 -0
- package/dist/2-core/ds-progress.d.ts.map +1 -0
- package/dist/2-core/ds-progress.js +319 -0
- package/dist/2-core/ds-stat.d.ts +126 -0
- package/dist/2-core/ds-stat.d.ts.map +1 -0
- package/dist/2-core/ds-stat.js +137 -0
- package/dist/2-core/ds-switch.d.ts +1 -0
- package/dist/2-core/ds-switch.d.ts.map +1 -1
- package/dist/2-core/ds-switch.js +1 -0
- package/dist/2-core/ds-text.d.ts +15 -2
- package/dist/2-core/ds-text.d.ts.map +1 -1
- package/dist/2-core/ds-text.js +13 -0
- package/dist/2-core/ds-toast.d.ts +139 -0
- package/dist/2-core/ds-toast.d.ts.map +1 -0
- package/dist/2-core/ds-toast.js +329 -0
- package/dist/2-core/figures.d.ts +56 -0
- package/dist/2-core/figures.d.ts.map +1 -0
- package/dist/2-core/figures.js +70 -0
- package/dist/2-core/generated/ds-icon-map.d.ts +120 -65
- package/dist/2-core/generated/ds-icon-map.d.ts.map +1 -1
- package/dist/2-core/generated/ds-icon-map.js +238 -128
- package/dist/2-core/generated/icon-avatar.d.ts +2 -0
- package/dist/2-core/generated/icon-avatar.d.ts.map +1 -0
- package/dist/2-core/generated/icon-avatar.js +2 -0
- package/dist/2-core/generated/icons-extra.d.ts.map +1 -1
- package/dist/2-core/generated/icons-extra.js +120 -65
- package/dist/2-core/generated/icons-immediate.d.ts +1 -1
- package/dist/2-core/generated/icons-immediate.d.ts.map +1 -1
- package/dist/2-core/generated/icons-immediate.js +1 -1
- package/dist/3-unit/calendar-base.d.ts +149 -0
- package/dist/3-unit/calendar-base.d.ts.map +1 -0
- package/dist/3-unit/calendar-base.js +172 -0
- package/dist/3-unit/ds-accordion.d.ts +9 -0
- package/dist/3-unit/ds-accordion.d.ts.map +1 -1
- package/dist/3-unit/ds-accordion.js +7 -1
- package/dist/3-unit/ds-avatar-group.d.ts +1 -0
- package/dist/3-unit/ds-avatar-group.d.ts.map +1 -1
- package/dist/3-unit/ds-calendar-large.d.ts +138 -0
- package/dist/3-unit/ds-calendar-large.d.ts.map +1 -0
- package/dist/3-unit/ds-calendar-large.js +305 -0
- package/dist/3-unit/ds-calendar.d.ts +175 -0
- package/dist/3-unit/ds-calendar.d.ts.map +1 -0
- package/dist/3-unit/ds-calendar.js +303 -0
- package/dist/3-unit/ds-carousel.d.ts +182 -0
- package/dist/3-unit/ds-carousel.d.ts.map +1 -0
- package/dist/3-unit/ds-carousel.js +327 -0
- package/dist/3-unit/ds-date-picker.d.ts +203 -0
- package/dist/3-unit/ds-date-picker.d.ts.map +1 -0
- package/dist/3-unit/ds-date-picker.js +499 -0
- package/dist/3-unit/ds-form.d.ts.map +1 -1
- package/dist/3-unit/ds-form.js +38 -28
- package/dist/3-unit/ds-list.d.ts +12 -0
- package/dist/3-unit/ds-list.d.ts.map +1 -1
- package/dist/3-unit/ds-list.js +52 -1
- package/dist/3-unit/ds-matrix.d.ts +146 -0
- package/dist/3-unit/ds-matrix.d.ts.map +1 -0
- package/dist/3-unit/ds-matrix.js +248 -0
- package/dist/3-unit/ds-menu.d.ts +1 -1
- package/dist/3-unit/ds-menu.d.ts.map +1 -1
- package/dist/3-unit/ds-menu.js +7 -4
- package/dist/3-unit/ds-row.d.ts +66 -0
- package/dist/3-unit/ds-row.d.ts.map +1 -1
- package/dist/3-unit/ds-row.js +80 -1
- package/dist/3-unit/ds-sidebar.d.ts +149 -0
- package/dist/3-unit/ds-sidebar.d.ts.map +1 -0
- package/dist/3-unit/ds-sidebar.js +342 -0
- package/dist/3-unit/ds-table.d.ts +28 -10
- package/dist/3-unit/ds-table.d.ts.map +1 -1
- package/dist/3-unit/ds-table.js +53 -25
- package/dist/4-page/ds-dialog.d.ts +16 -1
- package/dist/4-page/ds-dialog.d.ts.map +1 -1
- package/dist/4-page/ds-dialog.js +39 -3
- package/dist/4-page/ds-document.d.ts +98 -0
- package/dist/4-page/ds-document.d.ts.map +1 -0
- package/dist/4-page/ds-document.js +213 -0
- package/dist/4-page/ds-footer.d.ts +100 -0
- package/dist/4-page/ds-footer.d.ts.map +1 -0
- package/dist/4-page/ds-footer.js +119 -0
- package/dist/4-page/ds-grid.d.ts +32 -12
- package/dist/4-page/ds-grid.d.ts.map +1 -1
- package/dist/4-page/ds-grid.js +30 -39
- package/dist/4-page/ds-header.d.ts +84 -0
- package/dist/4-page/ds-header.d.ts.map +1 -0
- package/dist/4-page/ds-header.js +143 -0
- package/dist/4-page/ds-page.d.ts +110 -0
- package/dist/4-page/ds-page.d.ts.map +1 -0
- package/dist/4-page/ds-page.js +155 -0
- package/dist/4-page/email.d.ts +2 -3
- package/dist/4-page/email.d.ts.map +1 -1
- package/dist/4-page/email.js +2 -3
- package/dist/components.d.ts +4 -1
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +4 -1
- package/dist/design-system-1.bundle.js +12322 -5523
- package/dist/design-system-1.bundle.js.map +1 -1
- package/dist/design-system-1.bundle.min.js +8771 -4115
- package/dist/design-system-1.bundle.min.js.map +1 -1
- package/dist/design-system-1.css +1 -1
- package/dist/feedback.js +541 -0
- package/dist/feedback.js.map +1 -0
- package/dist/{icons-extra-BoAACA4X.js → icons-extra-CiDDs7_-.js} +404 -312
- package/dist/icons-extra-CiDDs7_-.js.map +1 -0
- package/dist/{icons-extra-DlxTo_Y4.js → icons-extra-hauwCx07.js} +121 -66
- package/dist/icons-extra-hauwCx07.js.map +1 -0
- package/dist/index.d.ts +21 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -6
- package/dist/react/avatar.d.ts +5 -9
- package/dist/react/avatar.d.ts.map +1 -1
- package/dist/react/avatar.js +2 -2
- package/dist/react/button-split.d.ts +19 -0
- package/dist/react/button-split.d.ts.map +1 -0
- package/dist/react/{split.js → button-split.js} +12 -9
- package/dist/react/button.d.ts +18 -7
- package/dist/react/button.d.ts.map +1 -1
- package/dist/react/button.js +13 -4
- package/dist/react/carousel.d.ts +63 -0
- package/dist/react/carousel.d.ts.map +1 -0
- package/dist/react/carousel.js +47 -0
- package/dist/react/choice.d.ts +1 -1
- package/dist/react/context-menu.d.ts +337 -337
- package/dist/react/dialog.d.ts +684 -678
- package/dist/react/dialog.d.ts.map +1 -1
- package/dist/react/dialog.js +29 -3
- package/dist/react/document.d.ts +11 -0
- package/dist/react/document.d.ts.map +1 -0
- package/dist/react/document.js +10 -0
- package/dist/react/empty.d.ts +34 -0
- package/dist/react/empty.d.ts.map +1 -0
- package/dist/react/empty.js +8 -0
- package/dist/react/file-picker.d.ts +1 -1
- package/dist/react/footer.d.ts +13 -0
- package/dist/react/footer.d.ts.map +1 -0
- package/dist/react/footer.js +12 -0
- package/dist/react/header.d.ts +12 -0
- package/dist/react/header.d.ts.map +1 -0
- package/dist/react/header.js +11 -0
- package/dist/react/input.d.ts +31 -9
- package/dist/react/input.d.ts.map +1 -1
- package/dist/react/input.js +94 -94
- package/dist/react/message.d.ts +66 -0
- package/dist/react/message.d.ts.map +1 -0
- package/dist/react/message.js +37 -0
- package/dist/react/notice.d.ts +45 -0
- package/dist/react/notice.d.ts.map +1 -0
- package/dist/react/notice.js +34 -0
- package/dist/react/page.d.ts +10 -0
- package/dist/react/page.d.ts.map +1 -0
- package/dist/react/page.js +9 -0
- package/dist/react/progress.d.ts +107 -0
- package/dist/react/progress.d.ts.map +1 -0
- package/dist/react/progress.js +47 -0
- package/dist/react/sidebar.d.ts +54 -0
- package/dist/react/sidebar.d.ts.map +1 -0
- package/dist/react/sidebar.js +48 -0
- package/dist/react/stat.d.ts +78 -0
- package/dist/react/stat.d.ts.map +1 -0
- package/dist/react/stat.js +25 -0
- package/dist/react/toast.d.ts +37 -0
- package/dist/react/toast.d.ts.map +1 -0
- package/dist/react/toast.js +55 -0
- package/dist/shared-words-CyZYc_De.js +1818 -0
- package/dist/shared-words-CyZYc_De.js.map +1 -0
- package/dist/shared-words-DAVSET4Z.js +1800 -0
- package/dist/shared-words-DAVSET4Z.js.map +1 -0
- package/package.json +19 -6
- package/DS1/2-core/ds-banner.ts +0 -122
- package/DS1/2-core/ds-date.ts +0 -24
- package/DS1/2-core/ds-language.ts +0 -94
- package/DS1/2-core/ds-pagedots.ts +0 -52
- package/DS1/2-core/ds-split.ts +0 -122
- package/DS1/2-core/ds-textarea.ts +0 -136
- package/DS1/2-core/styles/ds-banner.css +0 -77
- package/DS1/2-core/styles/ds-date.css +0 -9
- package/DS1/2-core/styles/ds-language.css +0 -78
- package/DS1/2-core/styles/ds-pagedots.css +0 -31
- package/DS1/2-core/styles/ds-textarea.css +0 -27
- package/DS1/4-page/ds-layout.ts +0 -949
- package/DS1/4-page/styles/ds-layout.css +0 -246
- package/DS1/react/split.ts +0 -42
- package/DS1/x-icon/ezo-accordion-chevron.svg +0 -11
- package/DS1/x-icon/ezo-account-close.svg +0 -1
- package/DS1/x-icon/ezo-android-screen-archive.svg +0 -4
- package/DS1/x-icon/ezo-android-screen-calendar.svg +0 -6
- package/DS1/x-icon/ezo-android-screen-camera.svg +0 -5
- package/DS1/x-icon/ezo-android-screen-check.svg +0 -3
- package/DS1/x-icon/ezo-android-screen-compose.svg +0 -4
- package/DS1/x-icon/ezo-android-screen-globe.svg +0 -1
- package/DS1/x-icon/ezo-android-screen-shared.svg +0 -5
- package/DS1/x-icon/ezo-android-screen-store.svg +0 -4
- package/DS1/x-icon/ezo-android-screen-views.svg +0 -4
- package/DS1/x-icon/ezo-android-screen-work.svg +0 -3
- package/DS1/x-icon/ezo-arrow-cursor.svg +0 -8
- package/DS1/x-icon/ezo-block-accordion.svg +0 -1
- package/DS1/x-icon/ezo-block-divider.svg +0 -1
- package/DS1/x-icon/ezo-block-group.svg +0 -1
- package/DS1/x-icon/ezo-block-image.svg +0 -1
- package/DS1/x-icon/ezo-block-section.svg +0 -1
- package/DS1/x-icon/ezo-block-table.svg +0 -1
- package/DS1/x-icon/ezo-code.svg +0 -14
- package/DS1/x-icon/ezo-down.svg +0 -3
- package/DS1/x-icon/ezo-figma-premium-star.svg +0 -5
- package/DS1/x-icon/ezo-item-button-complete.svg +0 -4
- package/DS1/x-icon/ezo-item-button-header.svg +0 -3
- package/DS1/x-icon/ezo-item-button-open.svg +0 -3
- package/DS1/x-icon/ezo-item-button-star.svg +0 -3
- package/DS1/x-icon/ezo-item-button-unheader.svg +0 -7
- package/DS1/x-icon/ezo-item-button-unstar.svg +0 -5
- package/DS1/x-icon/ezo-item-star.svg +0 -5
- package/DS1/x-icon/ezo-left.svg +0 -3
- package/DS1/x-icon/ezo-logo.svg +0 -1
- package/DS1/x-icon/ezo-mic.svg +0 -8
- package/DS1/x-icon/ezo-move-column.svg +0 -14
- package/DS1/x-icon/ezo-move-row.svg +0 -18
- package/DS1/x-icon/ezo-navigation-check.svg +0 -1
- package/DS1/x-icon/ezo-note-back.svg +0 -5
- package/DS1/x-icon/ezo-note-menu-complete.svg +0 -11
- package/DS1/x-icon/ezo-note-menu-delete.svg +0 -4
- package/DS1/x-icon/ezo-note-menu-duplicate.svg +0 -8
- package/DS1/x-icon/ezo-note-menu-note.svg +0 -5
- package/DS1/x-icon/ezo-note-menu-page.svg +0 -3
- package/DS1/x-icon/ezo-note-menu-star.svg +0 -5
- package/DS1/x-icon/ezo-note-menu-start.svg +0 -6
- package/DS1/x-icon/ezo-note-menu-stop.svg +0 -6
- package/DS1/x-icon/ezo-note-more.svg +0 -7
- package/DS1/x-icon/ezo-note-star.svg +0 -5
- package/DS1/x-icon/ezo-pattern-call-to-action.svg +0 -1
- package/DS1/x-icon/ezo-play.svg +0 -5
- package/DS1/x-icon/ezo-premium-star.svg +0 -5
- package/DS1/x-icon/ezo-setting-1xdots.svg +0 -13
- package/DS1/x-icon/ezo-setting-1xgrid.svg +0 -10
- package/DS1/x-icon/ezo-setting-1xlines.svg +0 -7
- package/DS1/x-icon/ezo-setting-2xdots.svg +0 -8
- package/DS1/x-icon/ezo-setting-2xgrid.svg +0 -8
- package/DS1/x-icon/ezo-setting-2xlines.svg +0 -6
- package/DS1/x-icon/ezo-setting-blank.svg +0 -5
- package/DS1/x-icon/ezo-store-chevron.svg +0 -1
- package/DS1/x-icon/ezo-upgrade-close.svg +0 -5
- package/DS1/x-icon/product-sources.json +0 -2557
- package/dist/2-core/ds-banner.d.ts +0 -68
- package/dist/2-core/ds-banner.d.ts.map +0 -1
- package/dist/2-core/ds-banner.js +0 -98
- package/dist/2-core/ds-date.d.ts +0 -16
- package/dist/2-core/ds-date.d.ts.map +0 -1
- package/dist/2-core/ds-date.js +0 -15
- package/dist/2-core/ds-language.d.ts +0 -75
- package/dist/2-core/ds-language.d.ts.map +0 -1
- package/dist/2-core/ds-language.js +0 -74
- package/dist/2-core/ds-pagedots.d.ts +0 -32
- package/dist/2-core/ds-pagedots.d.ts.map +0 -1
- package/dist/2-core/ds-pagedots.js +0 -36
- package/dist/2-core/ds-split.d.ts +0 -75
- package/dist/2-core/ds-split.d.ts.map +0 -1
- package/dist/2-core/ds-split.js +0 -101
- package/dist/2-core/ds-textarea.d.ts +0 -46
- package/dist/2-core/ds-textarea.d.ts.map +0 -1
- package/dist/2-core/ds-textarea.js +0 -121
- package/dist/4-page/ds-layout.d.ts +0 -31
- package/dist/4-page/ds-layout.d.ts.map +0 -1
- package/dist/4-page/ds-layout.js +0 -930
- package/dist/icons-extra-BoAACA4X.js.map +0 -1
- package/dist/icons-extra-DlxTo_Y4.js.map +0 -1
- package/dist/react/split.d.ts +0 -15
- package/dist/react/split.d.ts.map +0 -1
- package/dist/shared-words-Cg6Phtx0.js +0 -915
- package/dist/shared-words-Cg6Phtx0.js.map +0 -1
- package/dist/shared-words-Ddz0yUEF.js +0 -933
- package/dist/shared-words-Ddz0yUEF.js.map +0 -1
- /package/DS1/x-icon/{ezo-password-visibility-see.svg → password-see.svg} +0 -0
- /package/DS1/x-icon/{ezo-password-visibility-unsee.svg → password-unsee.svg} +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy text to the clipboard and say so in a toast, for pages with or
|
|
3
|
+
* without a framework.
|
|
4
|
+
*
|
|
5
|
+
* It writes with the Clipboard API, and where that is missing or refused (a
|
|
6
|
+
* page served over plain HTTP, an older browser) it copies from a hidden field
|
|
7
|
+
* instead. The toast is the shared word "Copied" in the page's language,
|
|
8
|
+
* shown by `<ds-toast>` where the page has one and by the page toaster
|
|
9
|
+
* otherwise; a failure says "Could not copy".
|
|
10
|
+
*
|
|
11
|
+
* It is small on its own: the page toaster (`./toaster.ts`) loads only when a
|
|
12
|
+
* toast is shown and nothing in the page draws toasts yet, so a page that
|
|
13
|
+
* copies with `toast: false`, or has `<ds-toast>`, never loads it. Import it
|
|
14
|
+
* from here, not from `./feedback.ts`, where the page needs nothing else.
|
|
15
|
+
*/
|
|
16
|
+
import { feedbackWord } from "./feedback-words";
|
|
17
|
+
import { toast, toasterListening } from "./toast-queue";
|
|
18
|
+
|
|
19
|
+
export interface CopyTextOptions {
|
|
20
|
+
/** The toast's words when it is copied; `false` shows no toast at all. "Copied" by default. */
|
|
21
|
+
toast?: string | false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The open modal dialog, if any: the rest of the page is inert, so the hidden field goes in it. */
|
|
25
|
+
function modalDialog(): HTMLDialogElement | null {
|
|
26
|
+
try {
|
|
27
|
+
return document.querySelector<HTMLDialogElement>("dialog:modal");
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The fallback: select the text in a hidden field and copy the selection. */
|
|
34
|
+
function copyFromField(text: string): boolean {
|
|
35
|
+
if (typeof document === "undefined" || !document.body) return false;
|
|
36
|
+
const active = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
37
|
+
const selection = document.getSelection();
|
|
38
|
+
const ranges = selection ? Array.from({ length: selection.rangeCount }, (_, index) => selection.getRangeAt(index)) : [];
|
|
39
|
+
const field = document.createElement("textarea");
|
|
40
|
+
field.value = text;
|
|
41
|
+
field.setAttribute("readonly", "");
|
|
42
|
+
field.setAttribute("aria-hidden", "true");
|
|
43
|
+
field.tabIndex = -1;
|
|
44
|
+
field.style.cssText = "position:fixed;top:0;left:0;width:1px;height:1px;margin:0;padding:0;border:0;opacity:0;pointer-events:none";
|
|
45
|
+
(modalDialog() ?? document.body).appendChild(field);
|
|
46
|
+
field.focus({ preventScroll: true });
|
|
47
|
+
field.select();
|
|
48
|
+
field.setSelectionRange(0, text.length);
|
|
49
|
+
let copied = false;
|
|
50
|
+
try {
|
|
51
|
+
copied = document.execCommand("copy");
|
|
52
|
+
} catch {
|
|
53
|
+
copied = false;
|
|
54
|
+
}
|
|
55
|
+
field.remove();
|
|
56
|
+
active?.focus({ preventScroll: true });
|
|
57
|
+
if (selection) {
|
|
58
|
+
selection.removeAllRanges();
|
|
59
|
+
for (const range of ranges) selection.addRange(range);
|
|
60
|
+
}
|
|
61
|
+
return copied;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Copy `text` and confirm it with a toast. Resolves to whether it was copied.
|
|
66
|
+
* Call it from the click or key press that asks for it: browsers allow writing
|
|
67
|
+
* to the clipboard only then.
|
|
68
|
+
*/
|
|
69
|
+
export async function copyText(text: string, options: CopyTextOptions = {}): Promise<boolean> {
|
|
70
|
+
let copied = false;
|
|
71
|
+
const clipboard = typeof navigator === "undefined" ? undefined : navigator.clipboard;
|
|
72
|
+
if (clipboard?.writeText) {
|
|
73
|
+
try {
|
|
74
|
+
await clipboard.writeText(text);
|
|
75
|
+
copied = true;
|
|
76
|
+
} catch {
|
|
77
|
+
copied = false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!copied) copied = copyFromField(text);
|
|
81
|
+
if (options.toast !== false) await say(copied, copied ? options.toast ?? feedbackWord("Copied") : feedbackWord("Could not copy"));
|
|
82
|
+
return copied;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The toast, with the page toaster loaded first where nothing in the page draws toasts yet. */
|
|
86
|
+
async function say(copied: boolean, words: string): Promise<void> {
|
|
87
|
+
if (!toasterListening()) {
|
|
88
|
+
try {
|
|
89
|
+
(await import("./load-toaster")).pageToaster();
|
|
90
|
+
} catch {
|
|
91
|
+
// No toaster could load: the copy itself still answers.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (copied) toast.success(words);
|
|
95
|
+
else toast.error(words);
|
|
96
|
+
}
|
package/DS1/0-face/device.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// Device detection, context utilities, and responsive scaling
|
|
3
3
|
|
|
4
4
|
import { signal } from "@lit-labs/signals";
|
|
5
|
-
import { calculateScalingFactor as calculateScale, defaultScalingConfig, type ScalingConfig } from "./scaling";
|
|
6
|
-
export type { ScalingConfig, ScalingMode } from "./scaling";
|
|
5
|
+
import { applyScaling, calculateScalingFactor as calculateScale, defaultScalingConfig, detectMobileDevice, type ScalingConfig } from "./scaling";
|
|
6
|
+
export type { ScalingConfig, ScalingMode, ScalingOptions, ScalingSnap } from "./scaling";
|
|
7
|
+
// One rule for which screens scale, shared with pages that copy scaling.ts alone.
|
|
8
|
+
export { applyScaling, detectMobileDevice, startScaling, toDevicePixel, toLayoutStep } from "./scaling";
|
|
7
9
|
|
|
8
10
|
// ============================================================================
|
|
9
11
|
// Types
|
|
@@ -32,38 +34,6 @@ export const scalingConfig = signal<ScalingConfig>(defaultScalingConfig);
|
|
|
32
34
|
// Device Detection
|
|
33
35
|
// ============================================================================
|
|
34
36
|
|
|
35
|
-
/**
|
|
36
|
-
* Comprehensive mobile device detection
|
|
37
|
-
* Combines user agent detection, touch capability, and viewport size
|
|
38
|
-
*/
|
|
39
|
-
export function detectMobileDevice(): boolean {
|
|
40
|
-
if (typeof navigator === "undefined" || typeof window === "undefined") {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const nav = navigator as any;
|
|
45
|
-
const win = window as any;
|
|
46
|
-
const ua: string =
|
|
47
|
-
(nav && (nav.userAgent || nav.vendor)) || (win && win.opera) || "";
|
|
48
|
-
|
|
49
|
-
// User agent based detection
|
|
50
|
-
const uaMatchesMobile =
|
|
51
|
-
/Mobile|Android|iP(ad|hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)|Windows Phone|Phone|Tablet/i.test(
|
|
52
|
-
ua
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
// Touch capability detection
|
|
56
|
-
const touchPoints = (nav && nav.maxTouchPoints) || 0;
|
|
57
|
-
const isTouchCapable = touchPoints > 1;
|
|
58
|
-
|
|
59
|
-
// Viewport detection
|
|
60
|
-
const narrowViewport = win
|
|
61
|
-
? Math.min(win.innerWidth || 0, win.innerHeight || 0) <= 820
|
|
62
|
-
: false;
|
|
63
|
-
|
|
64
|
-
return uaMatchesMobile || (isTouchCapable && narrowViewport);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
37
|
/**
|
|
68
38
|
* Get detailed device information
|
|
69
39
|
*/
|
|
@@ -132,39 +102,19 @@ export function setScalingConfig(config: Partial<ScalingConfig>): void {
|
|
|
132
102
|
|
|
133
103
|
/**
|
|
134
104
|
* Update the scaling factor based on current viewport
|
|
135
|
-
* Desktop always uses factor 1; mobile uses computed factor
|
|
105
|
+
* Desktop always uses factor 1; mobile uses computed factor (see scaling.ts)
|
|
136
106
|
*/
|
|
137
107
|
export function updateScalingFactor(): void {
|
|
138
108
|
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
139
109
|
return;
|
|
140
110
|
}
|
|
141
111
|
|
|
142
|
-
const isMobile = detectMobileDevice();
|
|
143
|
-
|
|
144
|
-
// Desktop: always use factor 1
|
|
145
|
-
if (!isMobile) {
|
|
146
|
-
const factor = 1;
|
|
147
|
-
scalingFactor.set(factor);
|
|
148
|
-
document.documentElement.style.setProperty("--sf", factor.toString());
|
|
149
|
-
window.dispatchEvent(
|
|
150
|
-
new CustomEvent("scaling-changed", {
|
|
151
|
-
detail: { scalingFactor: factor, config: scalingConfig.get() },
|
|
152
|
-
})
|
|
153
|
-
);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Mobile: compute scaling based on viewport
|
|
158
|
-
const viewportWidth = document.documentElement.clientWidth;
|
|
159
112
|
const config = scalingConfig.get();
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
scalingFactor.set(newFactor);
|
|
163
|
-
document.documentElement.style.setProperty("--sf", newFactor.toString());
|
|
164
|
-
|
|
113
|
+
const factor = applyScaling(config);
|
|
114
|
+
scalingFactor.set(factor);
|
|
165
115
|
window.dispatchEvent(
|
|
166
116
|
new CustomEvent("scaling-changed", {
|
|
167
|
-
detail: { scalingFactor:
|
|
117
|
+
detail: { scalingFactor: factor, config },
|
|
168
118
|
})
|
|
169
119
|
);
|
|
170
120
|
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swipe a sheet or a drawer away: a finger that drags a dialog towards the
|
|
3
|
+
* edge it came from moves it, and letting go far or fast enough dismisses it.
|
|
4
|
+
* `ds-dialog` binds it for `placement="sheet"`, `"end"` and `"start"`; a page
|
|
5
|
+
* without Lit binds its own `dialog.ds-dialog--sheet` (or `--end`, `--start`)
|
|
6
|
+
* with `bindDialogSwipe(dialog)`. Plain TypeScript with no framework.
|
|
7
|
+
*
|
|
8
|
+
* It follows the finger as touch does on Apple's platforms:
|
|
9
|
+
*
|
|
10
|
+
* - Touch alone; a mouse or a trackpad closes with Escape or the close button.
|
|
11
|
+
* - Ten pixels of movement before it decides, then it tracks the finger one
|
|
12
|
+
* to one from where it was grabbed, a moving dialog included (it starts
|
|
13
|
+
* from the dialog's place on screen, not from where it was going).
|
|
14
|
+
* - A drag along the content's own scroll is the content's: a sheet scrolled
|
|
15
|
+
* down scrolls back up first, a horizontal strip in a drawer scrolls, and a
|
|
16
|
+
* slider, a range input or anything marked `data-ds-dialog-no-swipe` keeps
|
|
17
|
+
* its touches.
|
|
18
|
+
* - Dragged the other way, it gives a little and pulls back (rubber-banding).
|
|
19
|
+
* - On release it projects where the throw would come to rest (the
|
|
20
|
+
* deceleration of a scroll, rate 0.998) and dismisses when that is past half
|
|
21
|
+
* its size and the finger is not moving back; otherwise it settles home.
|
|
22
|
+
* - The backdrop fades as the dialog goes (`--ds-dialog-progress`).
|
|
23
|
+
*
|
|
24
|
+
* Dismissing asks first, as a close button does: the element sends
|
|
25
|
+
* `ds-close-request` with the reason `"swipe"`, the native frame a cancelable
|
|
26
|
+
* `ds-close-request` on the dialog, and closes it when no listener prevents
|
|
27
|
+
* that. Declined (a form with unsaved changes asking first), it settles home.
|
|
28
|
+
* The motion itself is dialog-placement.css's, in transform alone.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
export type DialogPlacement = "center" | "sheet" | "end" | "start";
|
|
32
|
+
|
|
33
|
+
export interface DialogSwipeOptions {
|
|
34
|
+
/** Where the dialog stands, read at each touch; from its class or `data-ds-placement` by default. */
|
|
35
|
+
placement?: DialogPlacement | (() => DialogPlacement);
|
|
36
|
+
/**
|
|
37
|
+
* Called when a swipe would dismiss it. By default a cancelable
|
|
38
|
+
* `ds-close-request` (`detail.reason` `"swipe"`) on the dialog, then
|
|
39
|
+
* `dialog.close()` unless a listener prevented it. Still open two frames
|
|
40
|
+
* later, the dialog settles home.
|
|
41
|
+
*/
|
|
42
|
+
onDismiss?: (dialog: HTMLDialogElement) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Ten pixels before a touch is a drag, as the platforms wait. */
|
|
46
|
+
const SLOP = 10;
|
|
47
|
+
/** A scroll's deceleration: where a throw at this speed comes to rest. */
|
|
48
|
+
const DECELERATION = 0.998;
|
|
49
|
+
const project = (velocity: number) => ((velocity / 1000) * DECELERATION) / (1 - DECELERATION);
|
|
50
|
+
/** Past its edge the dialog follows less the further it goes. */
|
|
51
|
+
const rubberband = (overshoot: number, dimension: number, constant = 0.55) =>
|
|
52
|
+
(overshoot * dimension * constant) / (dimension + constant * Math.abs(overshoot));
|
|
53
|
+
|
|
54
|
+
const PLACEMENTS = new Set<DialogPlacement>(["center", "sheet", "end", "start"]);
|
|
55
|
+
|
|
56
|
+
/** A dialog's placement, from `data-ds-placement` or its `ds-dialog--` class. */
|
|
57
|
+
export function dialogPlacement(dialog: HTMLDialogElement): DialogPlacement {
|
|
58
|
+
const marked = dialog.getAttribute("data-ds-placement") as DialogPlacement | null;
|
|
59
|
+
if (marked && PLACEMENTS.has(marked)) return marked;
|
|
60
|
+
for (const placement of ["sheet", "end", "start"] as const) if (dialog.classList.contains(`ds-dialog--${placement}`)) return placement;
|
|
61
|
+
return "center";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type Gesture = {
|
|
65
|
+
id: number;
|
|
66
|
+
x: number;
|
|
67
|
+
y: number;
|
|
68
|
+
/** Where the dialog stood when the finger came down, towards its edge. */
|
|
69
|
+
base: number;
|
|
70
|
+
/** How far the finger had moved when the drag began. */
|
|
71
|
+
start: number;
|
|
72
|
+
mode: "pending" | "drag" | "ignore";
|
|
73
|
+
offset: number;
|
|
74
|
+
samples: { time: number; offset: number }[];
|
|
75
|
+
target: EventTarget | null;
|
|
76
|
+
path: EventTarget[];
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** The axis the dialog leaves along, and the sign of the way out on it. */
|
|
80
|
+
function direction(dialog: HTMLDialogElement, placement: DialogPlacement): { axis: "x" | "y"; sign: 1 | -1 } {
|
|
81
|
+
if (placement === "sheet") return { axis: "y", sign: 1 };
|
|
82
|
+
const rtl = dialog.ownerDocument.defaultView?.getComputedStyle(dialog).direction === "rtl";
|
|
83
|
+
const right = (placement === "end") !== rtl;
|
|
84
|
+
return { axis: "x", sign: right ? 1 : -1 };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* True when the content under the finger would scroll for this drag, so it
|
|
89
|
+
* keeps the touch: a finger moving down on content scrolled down scrolls it
|
|
90
|
+
* back up first, and one moving up scrolls it on while there is more.
|
|
91
|
+
*/
|
|
92
|
+
function contentScrolls(path: EventTarget[], dialog: HTMLDialogElement, axis: "x" | "y", along: number): boolean {
|
|
93
|
+
const view = dialog.ownerDocument.defaultView;
|
|
94
|
+
for (const node of path) {
|
|
95
|
+
if (!(node instanceof Element)) continue;
|
|
96
|
+
const style = view?.getComputedStyle(node);
|
|
97
|
+
if (axis === "y") {
|
|
98
|
+
const scrolls = /auto|scroll/.test(style?.overflowY ?? "") && node.scrollHeight > node.clientHeight;
|
|
99
|
+
if (scrolls && (along > 0 ? node.scrollTop > 0 : node.scrollTop + node.clientHeight < node.scrollHeight - 1)) return true;
|
|
100
|
+
} else {
|
|
101
|
+
const scrolls = /auto|scroll/.test(style?.overflowX ?? "") && node.scrollWidth > node.clientWidth;
|
|
102
|
+
if (scrolls) return true;
|
|
103
|
+
}
|
|
104
|
+
if (node === dialog) break;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The dialog's place on screen along its axis, towards its edge, from its live transform. */
|
|
110
|
+
function presented(dialog: HTMLDialogElement, axis: "x" | "y", sign: 1 | -1): number {
|
|
111
|
+
const view = dialog.ownerDocument.defaultView;
|
|
112
|
+
const transform = view?.getComputedStyle(dialog).transform;
|
|
113
|
+
if (!transform || transform === "none" || typeof DOMMatrixReadOnly === "undefined") return 0;
|
|
114
|
+
try {
|
|
115
|
+
const matrix = new DOMMatrixReadOnly(transform);
|
|
116
|
+
return sign * (axis === "y" ? matrix.m42 : matrix.m41);
|
|
117
|
+
} catch {
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Let a finger swipe a sheet or drawer away. Returns the function that
|
|
124
|
+
* unbinds it and clears what it set on the dialog.
|
|
125
|
+
*/
|
|
126
|
+
export function bindDialogSwipe(dialog: HTMLDialogElement, options: DialogSwipeOptions = {}): () => void {
|
|
127
|
+
const placementNow = (): DialogPlacement => {
|
|
128
|
+
const given = typeof options.placement === "function" ? options.placement() : options.placement;
|
|
129
|
+
return given ?? dialogPlacement(dialog);
|
|
130
|
+
};
|
|
131
|
+
let gesture: Gesture | null = null;
|
|
132
|
+
let settleTimer = 0;
|
|
133
|
+
const view = dialog.ownerDocument.defaultView;
|
|
134
|
+
const frame = (callback: () => void) => (view?.requestAnimationFrame ? view.requestAnimationFrame(callback) : view?.setTimeout(callback, 16) ?? 0);
|
|
135
|
+
|
|
136
|
+
const write = (offset: number, size: number, sign: 1 | -1) => {
|
|
137
|
+
dialog.style.setProperty("--ds-dialog-drag", `${sign * offset}px`);
|
|
138
|
+
dialog.style.setProperty("--ds-dialog-progress", String(size > 0 ? Math.min(1, Math.max(0, offset / size)) : 0));
|
|
139
|
+
};
|
|
140
|
+
const clear = () => {
|
|
141
|
+
dialog.style.removeProperty("--ds-dialog-drag");
|
|
142
|
+
dialog.style.removeProperty("--ds-dialog-progress");
|
|
143
|
+
dialog.removeAttribute("data-ds-dialog-dragging");
|
|
144
|
+
};
|
|
145
|
+
/** Home: the stylesheet's transition takes it back from where it is. */
|
|
146
|
+
const settle = () => {
|
|
147
|
+
dialog.removeAttribute("data-ds-dialog-dragging");
|
|
148
|
+
dialog.style.setProperty("--ds-dialog-drag", "0px");
|
|
149
|
+
dialog.style.setProperty("--ds-dialog-progress", "0");
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const dismiss = () => {
|
|
153
|
+
dialog.removeAttribute("data-ds-dialog-dragging");
|
|
154
|
+
if (options.onDismiss) options.onDismiss(dialog);
|
|
155
|
+
else {
|
|
156
|
+
const request = new CustomEvent("ds-close-request", { detail: { reason: "swipe" }, bubbles: true, composed: true, cancelable: true });
|
|
157
|
+
if (dialog.dispatchEvent(request)) dialog.close();
|
|
158
|
+
}
|
|
159
|
+
// A product may close it a moment later (a framework's state); declined, it goes home.
|
|
160
|
+
const token = ++settleTimer;
|
|
161
|
+
frame(() => frame(() => { if (token === settleTimer && dialog.open) settle(); }));
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const onStart = (event: TouchEvent) => {
|
|
165
|
+
const placement = placementNow();
|
|
166
|
+
if (!dialog.open || placement === "center") return;
|
|
167
|
+
if (event.touches.length !== 1) {
|
|
168
|
+
if (gesture?.mode === "drag") settle();
|
|
169
|
+
gesture = null;
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const path = event.composedPath();
|
|
173
|
+
if (path.some(node => node instanceof Element && node.matches('[data-ds-dialog-no-swipe], input[type="range"], [role="slider"]'))) return;
|
|
174
|
+
const touch = event.touches[0]!;
|
|
175
|
+
const { axis, sign } = direction(dialog, placement);
|
|
176
|
+
gesture = { id: touch.identifier, x: touch.clientX, y: touch.clientY, base: Math.max(0, presented(dialog, axis, sign)), start: 0, mode: "pending", offset: 0, samples: [], target: event.target, path };
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const onMove = (event: TouchEvent) => {
|
|
180
|
+
if (!gesture || gesture.mode === "ignore") return;
|
|
181
|
+
const touch = [...event.touches].find(each => each.identifier === gesture!.id);
|
|
182
|
+
if (!touch) return;
|
|
183
|
+
const placement = placementNow();
|
|
184
|
+
if (placement === "center") { gesture = null; return; }
|
|
185
|
+
const { axis, sign } = direction(dialog, placement);
|
|
186
|
+
const dx = touch.clientX - gesture.x, dy = touch.clientY - gesture.y;
|
|
187
|
+
const along = axis === "y" ? dy * sign : dx * sign;
|
|
188
|
+
const across = axis === "y" ? dx : dy;
|
|
189
|
+
if (gesture.mode === "pending") {
|
|
190
|
+
if (Math.hypot(dx, dy) < SLOP) return;
|
|
191
|
+
if (Math.abs(along) <= Math.abs(across) || contentScrolls(gesture.path, dialog, axis, along)) {
|
|
192
|
+
gesture.mode = "ignore";
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
gesture.mode = "drag";
|
|
196
|
+
gesture.start = along;
|
|
197
|
+
settleTimer++;
|
|
198
|
+
dialog.setAttribute("data-ds-dialog-dragging", "");
|
|
199
|
+
}
|
|
200
|
+
event.preventDefault();
|
|
201
|
+
const rect = dialog.getBoundingClientRect();
|
|
202
|
+
const size = axis === "y" ? rect.height : rect.width;
|
|
203
|
+
const raw = gesture.base + along - gesture.start;
|
|
204
|
+
gesture.offset = raw < 0 ? rubberband(raw, size || 1) : raw;
|
|
205
|
+
write(gesture.offset, size, sign);
|
|
206
|
+
const time = event.timeStamp || (view?.performance?.now() ?? Date.now());
|
|
207
|
+
gesture.samples.push({ time, offset: gesture.offset });
|
|
208
|
+
while (gesture.samples.length > 2 && time - gesture.samples[0]!.time > 150) gesture.samples.shift();
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const onEnd = (event: TouchEvent) => {
|
|
212
|
+
if (!gesture) return;
|
|
213
|
+
if ([...event.touches].some(each => each.identifier === gesture!.id)) return;
|
|
214
|
+
const ended = gesture;
|
|
215
|
+
gesture = null;
|
|
216
|
+
if (ended.mode !== "drag") return;
|
|
217
|
+
const placement = placementNow();
|
|
218
|
+
const { axis } = direction(dialog, placement === "center" ? "sheet" : placement);
|
|
219
|
+
const rect = dialog.getBoundingClientRect();
|
|
220
|
+
const size = axis === "y" ? rect.height : rect.width;
|
|
221
|
+
// The speed of the last tenth of a second: a finger held still before letting go throws nothing.
|
|
222
|
+
const now = event.timeStamp || (view?.performance?.now() ?? Date.now());
|
|
223
|
+
const recent = ended.samples.filter(sample => now - sample.time <= 100);
|
|
224
|
+
const first = recent[0], last = recent[recent.length - 1];
|
|
225
|
+
const elapsed = first && last ? last.time - first.time : 0;
|
|
226
|
+
const velocity = first && last && elapsed > 0 ? ((last.offset - first.offset) / elapsed) * 1000 : 0;
|
|
227
|
+
const resting = ended.offset + project(velocity);
|
|
228
|
+
if (event.type !== "touchcancel" && placement !== "center" && ended.offset > 0 && resting > size / 2 && velocity > -100) dismiss();
|
|
229
|
+
else settle();
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Open again: it starts from its edge, with nothing left of the last drag.
|
|
233
|
+
const observer = new MutationObserver(() => { if (dialog.open) { settleTimer++; gesture = null; clear(); } });
|
|
234
|
+
observer.observe(dialog, { attributes: true, attributeFilter: ["open"] });
|
|
235
|
+
dialog.addEventListener("touchstart", onStart, { passive: true });
|
|
236
|
+
dialog.addEventListener("touchmove", onMove, { passive: false });
|
|
237
|
+
dialog.addEventListener("touchend", onEnd, { passive: true });
|
|
238
|
+
dialog.addEventListener("touchcancel", onEnd, { passive: true });
|
|
239
|
+
return () => {
|
|
240
|
+
observer.disconnect();
|
|
241
|
+
dialog.removeEventListener("touchstart", onStart);
|
|
242
|
+
dialog.removeEventListener("touchmove", onMove);
|
|
243
|
+
dialog.removeEventListener("touchend", onEnd);
|
|
244
|
+
dialog.removeEventListener("touchcancel", onEnd);
|
|
245
|
+
settleTimer++;
|
|
246
|
+
gesture = null;
|
|
247
|
+
clear();
|
|
248
|
+
};
|
|
249
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialogs that answer with a promise, in place of the browser's `alert()`,
|
|
3
|
+
* `confirm()` and `prompt()`, for pages without Lit or any other framework.
|
|
4
|
+
*
|
|
5
|
+
* Each is drawn on the native `dialog.ds-dialog` frame from `1-root/controls.css`
|
|
6
|
+
* (which the page loads) with its native buttons, `.ds-button--primary`,
|
|
7
|
+
* `--secondary` and `--danger`, and opened with `showModal()`, so the browser
|
|
8
|
+
* keeps the rest of the page inert behind it. Their words default to the
|
|
9
|
+
* shared words (OK, Cancel) in the page's language where the page loads the
|
|
10
|
+
* language module, and English otherwise (`./feedback-words.ts`).
|
|
11
|
+
*
|
|
12
|
+
* - Focus moves into the dialog when it opens, and back to what had it when it
|
|
13
|
+
* closes.
|
|
14
|
+
* - Escape cancels; Enter in a prompt's field confirms it.
|
|
15
|
+
* - A click on the backdrop cancels, except on a destructive confirmation
|
|
16
|
+
* (`danger`), which takes an answer.
|
|
17
|
+
* - Two never open at once: a dialog asked for while another is open waits
|
|
18
|
+
* for it to close.
|
|
19
|
+
* - A dialog asked for in a click opens within that click, so a phone shows
|
|
20
|
+
* its keyboard for a prompt's field.
|
|
21
|
+
*/
|
|
22
|
+
import { feedbackWord } from "./feedback-words";
|
|
23
|
+
|
|
24
|
+
export interface AlertOptions {
|
|
25
|
+
title: string;
|
|
26
|
+
text?: string;
|
|
27
|
+
/** The button's words; the shared word "OK" by default. */
|
|
28
|
+
confirm?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ConfirmOptions {
|
|
32
|
+
title: string;
|
|
33
|
+
text?: string;
|
|
34
|
+
/** The confirming button's words; the shared word "OK" by default. Name the action when it has one ("Delete"). */
|
|
35
|
+
confirm?: string;
|
|
36
|
+
/** The cancelling button's words; the shared word "Cancel" by default. */
|
|
37
|
+
cancel?: string;
|
|
38
|
+
/** A destructive action: the confirming button in the danger colour, Cancel focused, and no backdrop dismissal. */
|
|
39
|
+
danger?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface PromptOptions {
|
|
43
|
+
title: string;
|
|
44
|
+
text?: string;
|
|
45
|
+
/** The field's label, shown above it; without one, the title names the field. */
|
|
46
|
+
label?: string;
|
|
47
|
+
value?: string;
|
|
48
|
+
placeholder?: string;
|
|
49
|
+
confirm?: string;
|
|
50
|
+
cancel?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type Kind = "alert" | "confirm" | "prompt";
|
|
54
|
+
interface Spec {
|
|
55
|
+
kind: Kind;
|
|
56
|
+
title: string;
|
|
57
|
+
text?: string;
|
|
58
|
+
confirm: string;
|
|
59
|
+
cancel?: string;
|
|
60
|
+
danger?: boolean;
|
|
61
|
+
field?: { label?: string; value: string; placeholder?: string };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* One dialog at a time; the next waits in line. */
|
|
65
|
+
const waiting: Array<() => void> = [];
|
|
66
|
+
let open = false;
|
|
67
|
+
let count = 0;
|
|
68
|
+
|
|
69
|
+
function inLine<T>(show: () => Promise<T>): Promise<T> {
|
|
70
|
+
return new Promise<T>((resolve, reject) => {
|
|
71
|
+
const start = () => {
|
|
72
|
+
open = true;
|
|
73
|
+
show().then(resolve, reject).finally(() => {
|
|
74
|
+
open = false;
|
|
75
|
+
waiting.shift()?.();
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
if (open) waiting.push(start);
|
|
79
|
+
else start();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The element with focus, inside open shadow roots too. */
|
|
84
|
+
function focused(): HTMLElement | null {
|
|
85
|
+
let active = document.activeElement;
|
|
86
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
87
|
+
return active instanceof HTMLElement ? active : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function element<K extends keyof HTMLElementTagNameMap>(name: K, attributes: Record<string, string> = {}, text?: string): HTMLElementTagNameMap[K] {
|
|
91
|
+
const node = document.createElement(name);
|
|
92
|
+
for (const [key, value] of Object.entries(attributes)) node.setAttribute(key, value);
|
|
93
|
+
if (text !== undefined) node.textContent = text;
|
|
94
|
+
return node;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Answers `true` for the confirming button, `false` for Cancel, Escape and the backdrop; a prompt answers its text. */
|
|
98
|
+
function show(spec: Spec): Promise<boolean | string | null> {
|
|
99
|
+
return new Promise(resolve => {
|
|
100
|
+
const id = `ds-feedback-${++count}`;
|
|
101
|
+
const opener = focused();
|
|
102
|
+
const dialog = element("dialog", {
|
|
103
|
+
class: "ds-dialog",
|
|
104
|
+
role: spec.kind === "prompt" ? "dialog" : "alertdialog",
|
|
105
|
+
"aria-labelledby": `${id}-title`,
|
|
106
|
+
"data-ds-feedback": spec.kind,
|
|
107
|
+
});
|
|
108
|
+
const header = element("div", { "data-ds-dialog": "header" });
|
|
109
|
+
header.appendChild(element("h2", { id: `${id}-title`, "data-ds-dialog": "title" }, spec.title));
|
|
110
|
+
if (spec.text) {
|
|
111
|
+
header.appendChild(element("p", { id: `${id}-text`, "data-ds-dialog": "description" }, spec.text));
|
|
112
|
+
dialog.setAttribute("aria-describedby", `${id}-text`);
|
|
113
|
+
}
|
|
114
|
+
dialog.appendChild(header);
|
|
115
|
+
|
|
116
|
+
let input: HTMLInputElement | undefined;
|
|
117
|
+
if (spec.field) {
|
|
118
|
+
const field = element("div", { "data-ds-dialog": "field" });
|
|
119
|
+
input = element("input", { id: `${id}-field`, class: "ds-input", type: "text", autocomplete: "off" });
|
|
120
|
+
input.value = spec.field.value;
|
|
121
|
+
if (spec.field.placeholder) input.placeholder = spec.field.placeholder;
|
|
122
|
+
if (spec.field.label) field.appendChild(element("label", { for: input.id, "data-ds-dialog": "label" }, spec.field.label));
|
|
123
|
+
else input.setAttribute("aria-labelledby", `${id}-title`);
|
|
124
|
+
field.appendChild(input);
|
|
125
|
+
dialog.appendChild(field);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const footer = element("div", { "data-ds-dialog": "footer" });
|
|
129
|
+
const cancel = spec.cancel === undefined ? undefined : element("button", { type: "button", class: "ds-button ds-button--secondary", value: "cancel" }, spec.cancel);
|
|
130
|
+
const confirm = element("button", { type: "button", class: spec.danger ? "ds-button ds-button--secondary ds-button--danger" : "ds-button ds-button--primary", value: "confirm" }, spec.confirm);
|
|
131
|
+
if (cancel) footer.appendChild(cancel);
|
|
132
|
+
footer.appendChild(confirm);
|
|
133
|
+
dialog.appendChild(footer);
|
|
134
|
+
|
|
135
|
+
const declined = spec.kind === "prompt" ? null : false;
|
|
136
|
+
let settled = false;
|
|
137
|
+
const finish = (confirmed: boolean) => {
|
|
138
|
+
if (settled) return;
|
|
139
|
+
settled = true;
|
|
140
|
+
if (dialog.open) dialog.close();
|
|
141
|
+
dialog.remove();
|
|
142
|
+
if (opener?.isConnected) opener.focus({ preventScroll: true });
|
|
143
|
+
resolve(spec.kind === "alert" ? true : confirmed ? (input ? input.value : true) : declined);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
confirm.addEventListener("click", () => finish(true));
|
|
147
|
+
cancel?.addEventListener("click", () => finish(false));
|
|
148
|
+
input?.addEventListener("keydown", event => {
|
|
149
|
+
if (event.key !== "Enter" || event.isComposing || event.shiftKey || event.altKey || event.metaKey || event.ctrlKey) return;
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
finish(true);
|
|
152
|
+
});
|
|
153
|
+
dialog.addEventListener("cancel", event => { event.preventDefault(); finish(false); });
|
|
154
|
+
// The browser can still close it (a second Escape): that is a cancel too.
|
|
155
|
+
dialog.addEventListener("close", () => finish(false));
|
|
156
|
+
|
|
157
|
+
// The backdrop is the dialog itself, outside its box; a press must start and end there.
|
|
158
|
+
const outside = (event: MouseEvent) => {
|
|
159
|
+
if (event.target !== dialog) return false;
|
|
160
|
+
const box = dialog.getBoundingClientRect();
|
|
161
|
+
return event.clientX < box.left || event.clientX > box.right || event.clientY < box.top || event.clientY > box.bottom;
|
|
162
|
+
};
|
|
163
|
+
let pressedOutside = false;
|
|
164
|
+
if (!spec.danger) {
|
|
165
|
+
dialog.addEventListener("pointerdown", event => { pressedOutside = event.button === 0 && outside(event); });
|
|
166
|
+
dialog.addEventListener("click", event => {
|
|
167
|
+
if (pressedOutside && outside(event)) finish(false);
|
|
168
|
+
pressedOutside = false;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const first = input ?? (spec.danger ? cancel : undefined) ?? confirm;
|
|
173
|
+
first.setAttribute("autofocus", "");
|
|
174
|
+
document.body.appendChild(dialog);
|
|
175
|
+
try {
|
|
176
|
+
dialog.showModal();
|
|
177
|
+
} catch {
|
|
178
|
+
dialog.setAttribute("open", "");
|
|
179
|
+
}
|
|
180
|
+
first.focus();
|
|
181
|
+
input?.select();
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** In place of `alert()`: a message with one button. Resolves when it is dismissed. */
|
|
186
|
+
export function alert(options: AlertOptions | string): Promise<void> {
|
|
187
|
+
const { title, text, confirm } = typeof options === "string" ? { title: options } as AlertOptions : options;
|
|
188
|
+
if (typeof document === "undefined") return Promise.resolve();
|
|
189
|
+
return inLine(() => show({ kind: "alert", title, text, confirm: confirm ?? feedbackWord("OK") })).then(() => undefined);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** In place of `confirm()`: resolves to `true` when confirmed, `false` otherwise. */
|
|
193
|
+
export function confirm(options: ConfirmOptions | string): Promise<boolean> {
|
|
194
|
+
const { title, text, confirm: yes, cancel, danger } = typeof options === "string" ? { title: options } as ConfirmOptions : options;
|
|
195
|
+
if (typeof document === "undefined") return Promise.resolve(false);
|
|
196
|
+
return inLine(() => show({ kind: "confirm", title, text, confirm: yes ?? feedbackWord("OK"), cancel: cancel ?? feedbackWord("Cancel"), danger })).then(answer => answer === true);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** In place of `prompt()`: resolves to the text written, or `null` when cancelled. */
|
|
200
|
+
export function prompt(options: PromptOptions | string): Promise<string | null> {
|
|
201
|
+
const { title, text, label, value = "", placeholder, confirm: yes, cancel } = typeof options === "string" ? { title: options } as PromptOptions : options;
|
|
202
|
+
if (typeof document === "undefined") return Promise.resolve(null);
|
|
203
|
+
return inLine(() => show({
|
|
204
|
+
kind: "prompt", title, text,
|
|
205
|
+
confirm: yes ?? feedbackWord("OK"), cancel: cancel ?? feedbackWord("Cancel"),
|
|
206
|
+
field: { label, value, placeholder },
|
|
207
|
+
})).then(answer => typeof answer === "string" ? answer : null);
|
|
208
|
+
}
|