@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,700 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dates, times, numbers, money and lists written the way the reader's
|
|
3
|
+
* language writes them, through the platform's `Intl` formatters.
|
|
4
|
+
*
|
|
5
|
+
* Every function formats in the current language (`currentLanguage.value`
|
|
6
|
+
* from `./i18n`), read again on every call, so the text drawn after a
|
|
7
|
+
* language change is in the new language. Pass `language` to format in
|
|
8
|
+
* another one: a server answering a request names the request's language,
|
|
9
|
+
* since its current language is nobody's in particular.
|
|
10
|
+
*
|
|
11
|
+
* Each formatter is made once per locale and options, then kept. No browser
|
|
12
|
+
* code: it runs in a Worker, Node or a test as it does in a page.
|
|
13
|
+
*/
|
|
14
|
+
import { currentLanguage } from "./i18n";
|
|
15
|
+
import { DEFAULT_LANGUAGE, LANGUAGES, normalizeLanguage } from "./languages";
|
|
16
|
+
import type { Language } from "./languages";
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Locales
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The locale each language of the list formats in: the language with the
|
|
24
|
+
* region it is most read in. `pt` is Portugal's Portuguese and `pt-BR`
|
|
25
|
+
* Brazil's; `Intl` alone reads a bare `pt` as Brazil's.
|
|
26
|
+
*/
|
|
27
|
+
export const LOCALE_TAGS: Readonly<Record<Language, string>> = {
|
|
28
|
+
da: "da-DK",
|
|
29
|
+
de: "de-DE",
|
|
30
|
+
en: "en-US",
|
|
31
|
+
es: "es-ES",
|
|
32
|
+
fi: "fi-FI",
|
|
33
|
+
fr: "fr-FR",
|
|
34
|
+
is: "is-IS",
|
|
35
|
+
it: "it-IT",
|
|
36
|
+
ja: "ja-JP",
|
|
37
|
+
ko: "ko-KR",
|
|
38
|
+
nb: "nb-NO",
|
|
39
|
+
nn: "nn-NO",
|
|
40
|
+
no: "no-NO",
|
|
41
|
+
pt: "pt-PT",
|
|
42
|
+
"pt-BR": "pt-BR",
|
|
43
|
+
sv: "sv-SE",
|
|
44
|
+
zh: "zh-CN",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type FormatSettings = {
|
|
48
|
+
/**
|
|
49
|
+
* The locale a language formats in, where a product's readers expect
|
|
50
|
+
* another region than `LOCALE_TAGS` gives: `{ en: "en-GB" }` writes
|
|
51
|
+
* English dates day first.
|
|
52
|
+
*/
|
|
53
|
+
locales?: Partial<Record<string, string>>;
|
|
54
|
+
/**
|
|
55
|
+
* The time zone dates and times are shown in, unless a call names one.
|
|
56
|
+
* Without it, the runtime's own zone (a Worker's is UTC).
|
|
57
|
+
*/
|
|
58
|
+
timeZone?: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
let settings: FormatSettings = {};
|
|
62
|
+
|
|
63
|
+
/** Each language or tag asked for, to the locale it formats in. */
|
|
64
|
+
const resolvedTags = new Map<string, string>();
|
|
65
|
+
|
|
66
|
+
/** Set the locales and time zone every call starts from (see `FormatSettings`). */
|
|
67
|
+
export function configureFormat(next: FormatSettings): void {
|
|
68
|
+
settings = {
|
|
69
|
+
...settings,
|
|
70
|
+
...next,
|
|
71
|
+
locales: { ...settings.locales, ...next.locales },
|
|
72
|
+
};
|
|
73
|
+
resolvedTags.clear();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function canonical(tag: string): string | null {
|
|
77
|
+
try {
|
|
78
|
+
return Intl.getCanonicalLocales(tag)[0] ?? null;
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function regional(language: Language): string {
|
|
85
|
+
const own = settings.locales?.[language];
|
|
86
|
+
return (own && canonical(own)) || LOCALE_TAGS[language];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The locale tag a language formats in: `da` gives `da-DK`, `pt` gives
|
|
91
|
+
* `pt-PT`, `pt-br` gives `pt-BR`. A tag with its own region or script keeps
|
|
92
|
+
* it (`en-GB`, `zh-Hant`), and a language outside the list passes through
|
|
93
|
+
* (`nl`). Without an argument, the current language's.
|
|
94
|
+
*/
|
|
95
|
+
export function localeTag(language?: string | null): string {
|
|
96
|
+
const tag = String(language || currentLanguage.value || "")
|
|
97
|
+
.trim()
|
|
98
|
+
.replace(/_/g, "-");
|
|
99
|
+
let resolved = resolvedTags.get(tag);
|
|
100
|
+
if (!resolved) {
|
|
101
|
+
resolved = resolveTag(tag);
|
|
102
|
+
// Tags come from a short list; a server reading arbitrary ones starts over
|
|
103
|
+
if (resolvedTags.size >= MAX_FORMATTERS) {
|
|
104
|
+
resolvedTags.clear();
|
|
105
|
+
}
|
|
106
|
+
resolvedTags.set(tag, resolved);
|
|
107
|
+
}
|
|
108
|
+
return resolved;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function resolveTag(tag: string): string {
|
|
112
|
+
if (!tag) {
|
|
113
|
+
return regional(DEFAULT_LANGUAGE);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const lower = tag.toLowerCase();
|
|
117
|
+
const member = LANGUAGES.find((code) => code.toLowerCase() === lower);
|
|
118
|
+
if (member) {
|
|
119
|
+
return regional(member);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// A tag of its own, when the runtime has its conventions (not the
|
|
123
|
+
// runtime's default standing in for one it lacks)
|
|
124
|
+
const written = canonical(tag);
|
|
125
|
+
if (written && supported(written)) {
|
|
126
|
+
return written;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Otherwise the list's language it names, else the default
|
|
130
|
+
const known = normalizeLanguage(tag, null);
|
|
131
|
+
return regional(known ?? DEFAULT_LANGUAGE);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function supported(tag: string): boolean {
|
|
135
|
+
try {
|
|
136
|
+
return Intl.NumberFormat.supportedLocalesOf([tag]).length > 0;
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ============================================================================
|
|
143
|
+
// Kept formatters
|
|
144
|
+
// ============================================================================
|
|
145
|
+
|
|
146
|
+
type WithLanguage = {
|
|
147
|
+
/** The language to format in, instead of the current one: a code of the list or any locale tag. */
|
|
148
|
+
language?: string;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const MAX_FORMATTERS = 256;
|
|
152
|
+
const formatters = new Map<string, unknown>();
|
|
153
|
+
|
|
154
|
+
function optionsKey(options: object): string {
|
|
155
|
+
const entries = Object.entries(options).filter(
|
|
156
|
+
([, value]) => value !== undefined
|
|
157
|
+
);
|
|
158
|
+
entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
159
|
+
return JSON.stringify(entries);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function kept<T>(
|
|
163
|
+
kind: string,
|
|
164
|
+
locale: string,
|
|
165
|
+
options: object,
|
|
166
|
+
make: () => T
|
|
167
|
+
): T {
|
|
168
|
+
const key = `${kind}|${locale}|${optionsKey(options)}`;
|
|
169
|
+
if (formatters.has(key)) {
|
|
170
|
+
return formatters.get(key) as T;
|
|
171
|
+
}
|
|
172
|
+
if (formatters.size >= MAX_FORMATTERS) {
|
|
173
|
+
// The oldest goes first
|
|
174
|
+
const oldest = formatters.keys().next();
|
|
175
|
+
if (!oldest.done) {
|
|
176
|
+
formatters.delete(oldest.value);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const formatter = make();
|
|
180
|
+
formatters.set(key, formatter);
|
|
181
|
+
return formatter;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function split<T extends WithLanguage>(
|
|
185
|
+
options: T
|
|
186
|
+
): [string, Omit<T, "language">] {
|
|
187
|
+
const { language, ...rest } = options;
|
|
188
|
+
return [localeTag(language), rest];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The kept `Intl.DateTimeFormat` for any options the named functions do not
|
|
193
|
+
* cover (`{ weekday: "short" }`), in the current language or `language`. The
|
|
194
|
+
* time zone set with `configureFormat` applies unless the options name one.
|
|
195
|
+
*/
|
|
196
|
+
export function dateFormatter(
|
|
197
|
+
options: Intl.DateTimeFormatOptions & WithLanguage = {}
|
|
198
|
+
): Intl.DateTimeFormat {
|
|
199
|
+
const [locale, rest] = split(options);
|
|
200
|
+
const resolved: Intl.DateTimeFormatOptions = {
|
|
201
|
+
...rest,
|
|
202
|
+
timeZone: rest.timeZone ?? settings.timeZone,
|
|
203
|
+
};
|
|
204
|
+
return kept("date", locale, resolved, () => new Intl.DateTimeFormat(locale, resolved));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The kept `Intl.NumberFormat` for any options, in the current language or `language`. */
|
|
208
|
+
export function numberFormatter(
|
|
209
|
+
options: Intl.NumberFormatOptions & WithLanguage = {}
|
|
210
|
+
): Intl.NumberFormat {
|
|
211
|
+
const [locale, rest] = split(options);
|
|
212
|
+
return kept("number", locale, rest, () => new Intl.NumberFormat(locale, rest));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** The kept `Intl.RelativeTimeFormat`, in the current language or `language`. */
|
|
216
|
+
export function relativeTimeFormatter(
|
|
217
|
+
options: Intl.RelativeTimeFormatOptions & WithLanguage = {}
|
|
218
|
+
): Intl.RelativeTimeFormat {
|
|
219
|
+
const [locale, rest] = split(options);
|
|
220
|
+
return kept(
|
|
221
|
+
"relative",
|
|
222
|
+
locale,
|
|
223
|
+
rest,
|
|
224
|
+
() => new Intl.RelativeTimeFormat(locale, rest)
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export type ListType = "conjunction" | "disjunction" | "unit";
|
|
229
|
+
export type ListStyle = "long" | "short" | "narrow";
|
|
230
|
+
|
|
231
|
+
/** What `Intl.ListFormat` offers, for runtimes whose types predate it. */
|
|
232
|
+
export type ListFormatter = {
|
|
233
|
+
format(items: Iterable<string>): string;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
type ListFormatConstructor = new (
|
|
237
|
+
locale: string,
|
|
238
|
+
options: { type?: ListType; style?: ListStyle }
|
|
239
|
+
) => ListFormatter;
|
|
240
|
+
|
|
241
|
+
/** The kept `Intl.ListFormat`, in the current language or `language`. */
|
|
242
|
+
export function listFormatter(
|
|
243
|
+
options: { type?: ListType; style?: ListStyle } & WithLanguage = {}
|
|
244
|
+
): ListFormatter {
|
|
245
|
+
const [locale, rest] = split(options);
|
|
246
|
+
return kept("list", locale, rest, () => {
|
|
247
|
+
const ListFormat = (Intl as unknown as { ListFormat?: ListFormatConstructor })
|
|
248
|
+
.ListFormat;
|
|
249
|
+
// A runtime without it joins with commas, as English would
|
|
250
|
+
return ListFormat
|
|
251
|
+
? new ListFormat(locale, rest)
|
|
252
|
+
: { format: (items: Iterable<string>) => [...items].join(", ") };
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ============================================================================
|
|
257
|
+
// Dates and times
|
|
258
|
+
// ============================================================================
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* A moment: a `Date`, milliseconds since 1970 or a date string. A date
|
|
262
|
+
* written without a time (`2026-10-17`) is that day wherever it is read.
|
|
263
|
+
*/
|
|
264
|
+
export type DateInput = Date | number | string;
|
|
265
|
+
|
|
266
|
+
export type DateLength = "short" | "medium" | "long";
|
|
267
|
+
|
|
268
|
+
export type DateOptions = WithLanguage & {
|
|
269
|
+
/** How much is written: `short` (10/17/26), `medium` (Oct 17, 2026) or `long` (October 17, 2026). */
|
|
270
|
+
length?: DateLength;
|
|
271
|
+
/** The time zone to show it in (`Asia/Tokyo`, `UTC`). */
|
|
272
|
+
timeZone?: string;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const DATE_ONLY = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
276
|
+
|
|
277
|
+
/** The moment, and whether it is a day without a time. */
|
|
278
|
+
function readDate(value: DateInput): { time: number; day: boolean } {
|
|
279
|
+
if (typeof value === "string") {
|
|
280
|
+
const day = DATE_ONLY.exec(value.trim());
|
|
281
|
+
if (day) {
|
|
282
|
+
return {
|
|
283
|
+
time: Date.UTC(Number(day[1]), Number(day[2]) - 1, Number(day[3])),
|
|
284
|
+
day: true,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return { time: Date.parse(value), day: false };
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
time: value instanceof Date ? value.getTime() : Number(value),
|
|
291
|
+
day: false,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function dateArguments(
|
|
296
|
+
options: DateOptions | DateLength | undefined,
|
|
297
|
+
fallback: DateLength
|
|
298
|
+
): DateOptions & { length: DateLength } {
|
|
299
|
+
if (typeof options === "string") {
|
|
300
|
+
return { length: options };
|
|
301
|
+
}
|
|
302
|
+
return { ...options, length: options?.length ?? fallback };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function formatMoment(
|
|
306
|
+
value: DateInput,
|
|
307
|
+
parts: Intl.DateTimeFormatOptions,
|
|
308
|
+
{ language, timeZone }: DateOptions
|
|
309
|
+
): string {
|
|
310
|
+
const { time, day } = readDate(value);
|
|
311
|
+
if (!Number.isFinite(time)) {
|
|
312
|
+
return "";
|
|
313
|
+
}
|
|
314
|
+
// A day without a time is kept on its own date in every zone
|
|
315
|
+
const zone = day ? "UTC" : timeZone;
|
|
316
|
+
return dateFormatter({ ...parts, timeZone: zone, language }).format(time);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** A date: `Oct 17, 2026` (medium, the default), `10/17/26` (short), `October 17, 2026` (long). */
|
|
320
|
+
export function formatDate(
|
|
321
|
+
value: DateInput,
|
|
322
|
+
options?: DateOptions | DateLength
|
|
323
|
+
): string {
|
|
324
|
+
const { length, ...rest } = dateArguments(options, "medium");
|
|
325
|
+
return formatMoment(value, { dateStyle: length }, rest);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** A time of day: `2:30 PM` (short, the default), `2:30:05 PM` (medium), `2:30:05 PM UTC` (long). */
|
|
329
|
+
export function formatTime(
|
|
330
|
+
value: DateInput,
|
|
331
|
+
options?: DateOptions | DateLength
|
|
332
|
+
): string {
|
|
333
|
+
const { length, ...rest } = dateArguments(options, "short");
|
|
334
|
+
return formatMoment(value, { timeStyle: length }, rest);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** A date at a time: the date at `length` (medium by default), the time to the minute. */
|
|
338
|
+
export function formatDateTime(
|
|
339
|
+
value: DateInput,
|
|
340
|
+
options?: DateOptions | DateLength
|
|
341
|
+
): string {
|
|
342
|
+
const { length, ...rest } = dateArguments(options, "medium");
|
|
343
|
+
return formatMoment(value, { dateStyle: length, timeStyle: "short" }, rest);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const MONTH: Record<DateLength, "numeric" | "short" | "long"> = {
|
|
347
|
+
short: "numeric",
|
|
348
|
+
medium: "short",
|
|
349
|
+
long: "long",
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
/** A month of a year: `October 2026` (long, the default), `Oct 2026` (medium), `10/2026` (short). */
|
|
353
|
+
export function formatMonthYear(
|
|
354
|
+
value: DateInput,
|
|
355
|
+
options?: DateOptions | DateLength
|
|
356
|
+
): string {
|
|
357
|
+
const { length, ...rest } = dateArguments(options, "long");
|
|
358
|
+
return formatMoment(value, { year: "numeric", month: MONTH[length] }, rest);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** A day of a month, without the year: `Oct 17` (medium, the default), `10/17` (short), `October 17` (long). */
|
|
362
|
+
export function formatMonthDay(
|
|
363
|
+
value: DateInput,
|
|
364
|
+
options?: DateOptions | DateLength
|
|
365
|
+
): string {
|
|
366
|
+
const { length, ...rest } = dateArguments(options, "medium");
|
|
367
|
+
return formatMoment(value, { month: MONTH[length], day: "numeric" }, rest);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* A day of a month at a time, without the year: `Oct 17, 2:30 PM` (medium,
|
|
372
|
+
* the default), `10/17, 2:30 PM` (short), `October 17 at 2:30 PM` (long).
|
|
373
|
+
*/
|
|
374
|
+
export function formatMonthDayTime(
|
|
375
|
+
value: DateInput,
|
|
376
|
+
options?: DateOptions | DateLength
|
|
377
|
+
): string {
|
|
378
|
+
const { length, ...rest } = dateArguments(options, "medium");
|
|
379
|
+
return formatMoment(
|
|
380
|
+
value,
|
|
381
|
+
{ month: MONTH[length], day: "numeric", hour: "numeric", minute: "2-digit" },
|
|
382
|
+
rest
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ============================================================================
|
|
387
|
+
// Relative time
|
|
388
|
+
// ============================================================================
|
|
389
|
+
|
|
390
|
+
export type RelativeUnit =
|
|
391
|
+
| "second"
|
|
392
|
+
| "minute"
|
|
393
|
+
| "hour"
|
|
394
|
+
| "day"
|
|
395
|
+
| "week"
|
|
396
|
+
| "month"
|
|
397
|
+
| "quarter"
|
|
398
|
+
| "year";
|
|
399
|
+
|
|
400
|
+
export type RelativeTimeOptions = WithLanguage & {
|
|
401
|
+
/** The moment it is measured from. Defaults to now. */
|
|
402
|
+
now?: DateInput;
|
|
403
|
+
/** Always count in this unit, rather than the one that reads best. */
|
|
404
|
+
unit?: RelativeUnit;
|
|
405
|
+
/** `auto` (the default) writes "yesterday" and "now"; `always` writes "1 day ago". */
|
|
406
|
+
numeric?: "auto" | "always";
|
|
407
|
+
/** `long` (the default), `short` ("3 min. ago") or `narrow`. */
|
|
408
|
+
style?: "long" | "short" | "narrow";
|
|
409
|
+
/** The zone whose calendar decides what "yesterday" is. Defaults as dates do. */
|
|
410
|
+
timeZone?: string;
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const SECOND = 1000;
|
|
414
|
+
const MINUTE = 60 * SECOND;
|
|
415
|
+
const HOUR = 60 * MINUTE;
|
|
416
|
+
const DAY = 24 * HOUR;
|
|
417
|
+
const MONTH_DAYS = 30.436875;
|
|
418
|
+
const YEAR_DAYS = 365.2425;
|
|
419
|
+
|
|
420
|
+
const UNIT_LENGTH: Record<RelativeUnit, number> = {
|
|
421
|
+
second: SECOND,
|
|
422
|
+
minute: MINUTE,
|
|
423
|
+
hour: HOUR,
|
|
424
|
+
day: DAY,
|
|
425
|
+
week: 7 * DAY,
|
|
426
|
+
month: MONTH_DAYS * DAY,
|
|
427
|
+
quarter: 3 * MONTH_DAYS * DAY,
|
|
428
|
+
year: YEAR_DAYS * DAY,
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
/** The calendar day a moment falls on in a zone, counted from 1970. */
|
|
432
|
+
function dayNumber(time: number, timeZone: string | undefined): number {
|
|
433
|
+
if (!timeZone) {
|
|
434
|
+
const date = new Date(time);
|
|
435
|
+
return Math.round(
|
|
436
|
+
Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / DAY
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
const parts = kept(
|
|
440
|
+
"calendar",
|
|
441
|
+
"en-US",
|
|
442
|
+
{ timeZone },
|
|
443
|
+
() =>
|
|
444
|
+
new Intl.DateTimeFormat("en-US-u-ca-gregory-nu-latn", {
|
|
445
|
+
timeZone,
|
|
446
|
+
year: "numeric",
|
|
447
|
+
month: "numeric",
|
|
448
|
+
day: "numeric",
|
|
449
|
+
})
|
|
450
|
+
).formatToParts(time);
|
|
451
|
+
const part = (type: string) =>
|
|
452
|
+
Number(parts.find((entry) => entry.type === type)?.value);
|
|
453
|
+
return Math.round(Date.UTC(part("year"), part("month") - 1, part("day")) / DAY);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* The count and unit a moment reads best in, from `now`: under 45 seconds is
|
|
458
|
+
* now, under 45 minutes counts minutes, under 22 hours (or the same day)
|
|
459
|
+
* hours, under a week calendar days (so "yesterday" is the day before
|
|
460
|
+
* today), under 26 days weeks, under 335 days months, then years. A day
|
|
461
|
+
* without a time (`2026-10-17`) counts days from today. Negative counts are
|
|
462
|
+
* in the past.
|
|
463
|
+
*/
|
|
464
|
+
export function relativeTimeUnit(
|
|
465
|
+
value: DateInput,
|
|
466
|
+
now: DateInput = Date.now(),
|
|
467
|
+
timeZone: string | undefined = settings.timeZone
|
|
468
|
+
): [number, RelativeUnit] {
|
|
469
|
+
const { time: at, day } = readDate(value);
|
|
470
|
+
const from = readDate(now).time;
|
|
471
|
+
const elapsed = at - from;
|
|
472
|
+
const size = Math.abs(elapsed);
|
|
473
|
+
const sign = elapsed < 0 ? -1 : 1;
|
|
474
|
+
|
|
475
|
+
if (day) {
|
|
476
|
+
// A day without a time is counted in days from today: "today", "yesterday"
|
|
477
|
+
const days = Math.round(at / DAY) - dayNumber(from, timeZone);
|
|
478
|
+
return days === 0 ? [0, "day"] : inDays(days);
|
|
479
|
+
}
|
|
480
|
+
if (size < 45 * SECOND) {
|
|
481
|
+
// -0 keeps the past's wording where a count is written ("0 seconds ago")
|
|
482
|
+
return [elapsed < 0 ? -0 : 0, "second"];
|
|
483
|
+
}
|
|
484
|
+
if (size < 45 * MINUTE) {
|
|
485
|
+
return [sign * Math.max(1, Math.round(size / MINUTE)), "minute"];
|
|
486
|
+
}
|
|
487
|
+
const days = dayNumber(at, timeZone) - dayNumber(from, timeZone);
|
|
488
|
+
if (size < 22 * HOUR || days === 0) {
|
|
489
|
+
return [sign * Math.max(1, Math.round(size / HOUR)), "hour"];
|
|
490
|
+
}
|
|
491
|
+
return inDays(days);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/** A count of calendar days (never 0) in the unit it reads best in. */
|
|
495
|
+
function inDays(days: number): [number, RelativeUnit] {
|
|
496
|
+
const whole = Math.abs(days);
|
|
497
|
+
const sign = days < 0 ? -1 : 1;
|
|
498
|
+
if (whole < 7) {
|
|
499
|
+
return [days, "day"];
|
|
500
|
+
}
|
|
501
|
+
if (whole < 26) {
|
|
502
|
+
return [sign * Math.round(whole / 7), "week"];
|
|
503
|
+
}
|
|
504
|
+
if (whole < 335) {
|
|
505
|
+
return [sign * Math.max(1, Math.round(whole / MONTH_DAYS)), "month"];
|
|
506
|
+
}
|
|
507
|
+
return [sign * Math.max(1, Math.round(whole / YEAR_DAYS)), "year"];
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* How long ago or how far ahead a moment is: "3 minutes ago", "yesterday",
|
|
512
|
+
* "in 2 days", "now", in the unit that reads best (see `relativeTimeUnit`)
|
|
513
|
+
* or the one given.
|
|
514
|
+
*/
|
|
515
|
+
export function formatRelativeTime(
|
|
516
|
+
value: DateInput,
|
|
517
|
+
options: RelativeTimeOptions = {}
|
|
518
|
+
): string {
|
|
519
|
+
const { now = Date.now(), unit, numeric = "auto", style = "long", language } =
|
|
520
|
+
options;
|
|
521
|
+
const timeZone = options.timeZone ?? settings.timeZone;
|
|
522
|
+
const at = readDate(value).time;
|
|
523
|
+
const from = readDate(now).time;
|
|
524
|
+
if (!Number.isFinite(at) || !Number.isFinite(from)) {
|
|
525
|
+
return "";
|
|
526
|
+
}
|
|
527
|
+
const [count, chosen] = unit
|
|
528
|
+
? [Math.round((at - from) / UNIT_LENGTH[unit]), unit]
|
|
529
|
+
: relativeTimeUnit(value, from, timeZone);
|
|
530
|
+
return relativeTimeFormatter({ numeric, style, language }).format(count, chosen);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// ============================================================================
|
|
534
|
+
// Numbers
|
|
535
|
+
// ============================================================================
|
|
536
|
+
|
|
537
|
+
export type NumberOptions = WithLanguage & Intl.NumberFormatOptions;
|
|
538
|
+
|
|
539
|
+
/** A number with the language's separators: `1,234.5`, `1.234,5`. Any `Intl.NumberFormat` option applies. */
|
|
540
|
+
export function formatNumber(value: number | bigint, options: NumberOptions = {}): string {
|
|
541
|
+
return numberFormatter(options).format(value);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/** A number in short: `12K`, `1.2M`, `1,2 mio.`, `1.2万`, to one decimal unless the options say otherwise. */
|
|
545
|
+
export function formatCompact(value: number | bigint, options: NumberOptions = {}): string {
|
|
546
|
+
return numberFormatter({
|
|
547
|
+
notation: "compact",
|
|
548
|
+
maximumFractionDigits: 1,
|
|
549
|
+
...options,
|
|
550
|
+
}).format(value);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** A fraction as a percentage: `0.25` gives `25%` (`25 %` in Danish). */
|
|
554
|
+
export function formatPercent(value: number, options: NumberOptions = {}): string {
|
|
555
|
+
return numberFormatter({ style: "percent", ...options }).format(value);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ============================================================================
|
|
559
|
+
// Money
|
|
560
|
+
// ============================================================================
|
|
561
|
+
|
|
562
|
+
export type CurrencyDisplay = "symbol" | "narrowSymbol" | "code" | "name";
|
|
563
|
+
|
|
564
|
+
export type MoneyOptions = WithLanguage &
|
|
565
|
+
Omit<Intl.NumberFormatOptions, "style" | "currency" | "currencyDisplay"> & {
|
|
566
|
+
/**
|
|
567
|
+
* The amount is counted in the currency's minor unit, as payment
|
|
568
|
+
* services send it: 1999 is $19.99, and 1999 yen are 1999 yen.
|
|
569
|
+
*/
|
|
570
|
+
minor?: boolean;
|
|
571
|
+
/** The currency as its symbol (`$`, the default), its narrow symbol, its code (`USD`) or its name. */
|
|
572
|
+
display?: CurrencyDisplay;
|
|
573
|
+
/** Whole units only, rounded: `$20`. */
|
|
574
|
+
whole?: boolean;
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
function currencyCode(currency: string): string {
|
|
578
|
+
return String(currency ?? "").trim().toUpperCase();
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* How many decimals a currency's minor unit has, as ISO 4217 counts them:
|
|
583
|
+
* 2 for USD, EUR and DKK, 0 for JPY and KRW, 3 for BHD. 2 for a code the
|
|
584
|
+
* runtime does not know.
|
|
585
|
+
*/
|
|
586
|
+
export function currencyDigits(currency: string): number {
|
|
587
|
+
const code = currencyCode(currency);
|
|
588
|
+
try {
|
|
589
|
+
return kept(
|
|
590
|
+
"digits",
|
|
591
|
+
"en",
|
|
592
|
+
{ currency: code },
|
|
593
|
+
() => new Intl.NumberFormat("en", { style: "currency", currency: code })
|
|
594
|
+
).resolvedOptions().maximumFractionDigits ?? 2;
|
|
595
|
+
} catch {
|
|
596
|
+
return 2;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/** An amount in minor units as whole units: 1999 cents are 19.99 dollars; yen stay as they are. */
|
|
601
|
+
export function fromMinorUnits(amount: number, currency: string): number {
|
|
602
|
+
return amount / 10 ** currencyDigits(currency);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** An amount in whole units as minor units, rounded: 19.99 dollars are 1999 cents. */
|
|
606
|
+
export function toMinorUnits(amount: number, currency: string): number {
|
|
607
|
+
return Math.round(amount * 10 ** currencyDigits(currency));
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function moneyFormatter(
|
|
611
|
+
currency: string,
|
|
612
|
+
options: MoneyOptions
|
|
613
|
+
): { format: Intl.NumberFormat | null; value: (amount: number) => number; code: string; digits: number } {
|
|
614
|
+
const { minor, display = "symbol", whole, language, ...rest } = options;
|
|
615
|
+
const code = currencyCode(currency);
|
|
616
|
+
const digits = currencyDigits(code);
|
|
617
|
+
const value = (amount: number) => (minor ? amount / 10 ** digits : amount);
|
|
618
|
+
try {
|
|
619
|
+
const format = numberFormatter({
|
|
620
|
+
...rest,
|
|
621
|
+
...(whole ? { minimumFractionDigits: 0, maximumFractionDigits: 0 } : {}),
|
|
622
|
+
style: "currency",
|
|
623
|
+
currency: code,
|
|
624
|
+
currencyDisplay: display,
|
|
625
|
+
language,
|
|
626
|
+
});
|
|
627
|
+
return { format, value, code, digits };
|
|
628
|
+
} catch {
|
|
629
|
+
return { format: null, value, code, digits };
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* An amount of money in the language's way: `$1,234.50`, `1.234,50 kr.`,
|
|
635
|
+
* `¥1,235`. The currency decides the decimals (none for yen). With `minor`,
|
|
636
|
+
* the amount is in cents, øre or the like. A currency code that is not one
|
|
637
|
+
* (not three letters) is written after the number (`12.00 US`) rather than
|
|
638
|
+
* throwing.
|
|
639
|
+
*/
|
|
640
|
+
export function formatMoney(
|
|
641
|
+
amount: number,
|
|
642
|
+
currency: string,
|
|
643
|
+
options: MoneyOptions = {}
|
|
644
|
+
): string {
|
|
645
|
+
const { format, value, code, digits } = moneyFormatter(currency, options);
|
|
646
|
+
if (format) {
|
|
647
|
+
return format.format(value(amount));
|
|
648
|
+
}
|
|
649
|
+
const places = options.whole ? 0 : digits;
|
|
650
|
+
return `${formatNumber(value(amount), {
|
|
651
|
+
minimumFractionDigits: places,
|
|
652
|
+
maximumFractionDigits: places,
|
|
653
|
+
language: options.language,
|
|
654
|
+
})} ${code}`;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* The parts of `formatMoney`'s text, as `Intl.NumberFormat.formatToParts`
|
|
659
|
+
* gives them (`currency`, `integer`, `group`, `decimal`, `fraction`, …), for
|
|
660
|
+
* drawing digits apart from the rest, each in a cell of its own width.
|
|
661
|
+
*/
|
|
662
|
+
export function formatMoneyToParts(
|
|
663
|
+
amount: number,
|
|
664
|
+
currency: string,
|
|
665
|
+
options: MoneyOptions = {}
|
|
666
|
+
): Intl.NumberFormatPart[] {
|
|
667
|
+
const { format, value } = moneyFormatter(currency, options);
|
|
668
|
+
if (format) {
|
|
669
|
+
return format.formatToParts(value(amount));
|
|
670
|
+
}
|
|
671
|
+
return [{ type: "literal", value: formatMoney(amount, currency, options) }];
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** The symbol the language writes a currency with: `$`, `kr.`, `€`, `US$` in British English. */
|
|
675
|
+
export function currencySymbol(
|
|
676
|
+
currency: string,
|
|
677
|
+
options: WithLanguage & { display?: "symbol" | "narrowSymbol" } = {}
|
|
678
|
+
): string {
|
|
679
|
+
const parts = formatMoneyToParts(0, currency, options);
|
|
680
|
+
return parts.find((part) => part.type === "currency")?.value ?? currencyCode(currency);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// ============================================================================
|
|
684
|
+
// Lists
|
|
685
|
+
// ============================================================================
|
|
686
|
+
|
|
687
|
+
export type ListOptions = WithLanguage & {
|
|
688
|
+
/** `conjunction` (the default) joins with "and", `disjunction` with "or", `unit` with neither. */
|
|
689
|
+
type?: ListType;
|
|
690
|
+
/** `long` (the default), `short` or `narrow`. */
|
|
691
|
+
style?: ListStyle;
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
/** Items joined the language's way: `A, B, and C`, `A, B og C`, `A、B、C`. */
|
|
695
|
+
export function formatList(items: Iterable<string>, options: ListOptions = {}): string {
|
|
696
|
+
const { type = "conjunction", style = "long", language } = options;
|
|
697
|
+
return listFormatter({ type, style, language }).format(
|
|
698
|
+
Array.from(items, (item) => String(item))
|
|
699
|
+
);
|
|
700
|
+
}
|