@lifesg/react-design-system 4.0.0-alpha.8 → 4.0.0-alpha.9
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/accordion/accordion-item.js.map +1 -1
- package/accordion/accordion.js.map +1 -1
- package/alert/alert.js.map +1 -1
- package/cjs/accordion/accordion-item.js.map +1 -1
- package/cjs/accordion/accordion.js.map +1 -1
- package/cjs/alert/alert.js.map +1 -1
- package/cjs/countdown-timer/countdown-timer.js.map +1 -1
- package/cjs/data-table/data-table.js +1 -1
- package/cjs/data-table/data-table.js.map +1 -1
- package/cjs/date-range-input/date-range-input.js.map +1 -1
- package/cjs/e-signature/e-signature-canvas.js.map +1 -1
- package/cjs/error-display/inactivity-timer.js.map +1 -1
- package/cjs/external/dompurify/dist/purify.es.js +2 -2
- package/cjs/external/dompurify/dist/purify.es.js.map +1 -1
- package/cjs/file-download/file-list-card/file-list-card.js.map +1 -1
- package/cjs/file-upload/custom-sensors.js.map +1 -1
- package/cjs/file-upload/file-list/file-list.js.map +1 -1
- package/cjs/filter/addons/filter-item-checkbox-helpers.js.map +1 -1
- package/cjs/filter/addons/filter-item-checkbox.js.map +1 -1
- package/cjs/filter/filter-item.js.map +1 -1
- package/cjs/filter/filter.js.map +1 -1
- package/cjs/form/form-textarea/form-textarea.js +1 -1
- package/cjs/form/form-textarea/form-textarea.js.map +1 -1
- package/cjs/fullscreen-image-carousel/stateful-image.js.map +1 -1
- package/cjs/histogram-slider/histogram-slider.js.map +1 -1
- package/cjs/input-range-slider/input-range-slider.js.map +1 -1
- package/cjs/input-slider/input-slider.js.map +1 -1
- package/cjs/input-textarea/textarea-counter.js.map +1 -1
- package/cjs/input-textarea/textarea.js +1 -1
- package/cjs/input-textarea/textarea.js.map +1 -1
- package/cjs/input-textarea/textarea.styles.js +1 -1
- package/cjs/input-textarea/textarea.styles.js.map +1 -1
- package/{input-textarea/textarea.styles_q77m5x.css → cjs/input-textarea/textarea.styles_6we7bs.css} +3 -1
- package/cjs/modal/index.js.map +1 -1
- package/cjs/modal-v2/modal-v2.js +1 -1
- package/cjs/modal-v2/modal-v2.js.map +1 -1
- package/cjs/modal-v2/slots/card.js +1 -1
- package/cjs/modal-v2/slots/card.js.map +1 -1
- package/cjs/modal-v2/slots/header.js +2 -0
- package/cjs/modal-v2/slots/header.js.map +1 -0
- package/cjs/modal-v2/slots/slot-styles.js +1 -1
- package/cjs/modal-v2/slots/slot-styles.js.map +1 -1
- package/cjs/modal-v2/slots/{slot-styles_13sd376.css → slot-styles_10ut1v7.css} +5 -1
- package/cjs/navbar/navbar-items.js +1 -1
- package/cjs/navbar/navbar-items.js.map +1 -1
- package/cjs/navbar/navbar-items.styles.js +1 -1
- package/cjs/navbar/navbar-items.styles.js.map +1 -1
- package/cjs/navbar/{navbar-items.styles_1i3ltot.css → navbar-items.styles_1b1zx.css} +9 -8
- package/cjs/navbar/navbar-logo-data.js +1 -1
- package/cjs/navbar/navbar-logo-data.js.map +1 -1
- package/cjs/navbar/navbar.js.map +1 -1
- package/cjs/otp-input/otp-input.js.map +1 -1
- package/cjs/overlay/overlay.js.map +1 -1
- package/cjs/pagination/pagination.js.map +1 -1
- package/cjs/phone-number-input/phone-number-input.js.map +1 -1
- package/cjs/predictive-text-input/predictive-text-input.js.map +1 -1
- package/cjs/shared/dropdown-list/dropdown-list.js.map +1 -1
- package/cjs/shared/internal-calendar/calendar-manager.js.map +1 -1
- package/cjs/shared/standalone-date-input/standalone-date-input.js.map +1 -1
- package/cjs/theme/types.js +1 -1
- package/cjs/theme/types.js.map +1 -1
- package/cjs/time-slot-bar/time-slot-bar.js.map +1 -1
- package/cjs/time-slot-bar-week/time-slot-bar-week-days.js.map +1 -1
- package/cjs/timetable/timetable.js.map +1 -1
- package/cjs/toggle/toggle-icon.js +1 -1
- package/cjs/toggle/toggle-icon.js.map +1 -1
- package/cjs/toggle/toggle-icon.styles.js +1 -1
- package/cjs/toggle/toggle-icon.styles.js.map +1 -1
- package/cjs/toggle/{toggle-icon.styles_ba71y.css → toggle-icon.styles_o6dxr3.css} +3 -2
- package/cjs/toggle/toggle.js +1 -1
- package/cjs/toggle/toggle.js.map +1 -1
- package/cjs/toggle/toggle.styles.js +1 -1
- package/cjs/toggle/toggle.styles.js.map +1 -1
- package/cjs/toggle/toggle.styles_11tmfsr.css +41 -0
- package/cjs/unit-number/unit-number-input.js.map +1 -1
- package/cjs/util/is-styled-components.js.map +1 -1
- package/cjs/util/string-helper.js +1 -1
- package/cjs/util/string-helper.js.map +1 -1
- package/cjs/util/use-event-listener.js.map +1 -1
- package/cjs/util/use-state-actions.js.map +1 -1
- package/countdown-timer/countdown-timer.js.map +1 -1
- package/data-table/data-table.js +1 -1
- package/data-table/data-table.js.map +1 -1
- package/data-table/types.d.ts +2 -2
- package/date-range-input/date-range-input.js.map +1 -1
- package/docs/component-catalog.json +1 -1
- package/e-signature/e-signature-canvas.js.map +1 -1
- package/error-display/inactivity-timer.js.map +1 -1
- package/external/dompurify/dist/purify.es.js +2 -2
- package/external/dompurify/dist/purify.es.js.map +1 -1
- package/file-download/file-list-card/file-list-card.js.map +1 -1
- package/file-upload/custom-sensors.d.ts +2 -2
- package/file-upload/custom-sensors.js.map +1 -1
- package/file-upload/file-list/file-list.js.map +1 -1
- package/filter/addons/filter-item-checkbox-helpers.js.map +1 -1
- package/filter/addons/filter-item-checkbox.js.map +1 -1
- package/filter/filter-item.js.map +1 -1
- package/filter/filter.js.map +1 -1
- package/form/form-textarea/form-textarea.js +1 -1
- package/form/form-textarea/form-textarea.js.map +1 -1
- package/fullscreen-image-carousel/stateful-image.js.map +1 -1
- package/fullscreen-image-carousel/types.d.ts +2 -2
- package/histogram-slider/histogram-slider.js.map +1 -1
- package/input-range-slider/input-range-slider.js.map +1 -1
- package/input-slider/input-slider.js.map +1 -1
- package/input-textarea/textarea-counter.js.map +1 -1
- package/input-textarea/textarea.js +1 -1
- package/input-textarea/textarea.js.map +1 -1
- package/input-textarea/textarea.styles.d.ts +2 -0
- package/input-textarea/textarea.styles.js +1 -1
- package/input-textarea/textarea.styles.js.map +1 -1
- package/{cjs/input-textarea/textarea.styles_q77m5x.css → input-textarea/textarea.styles_6we7bs.css} +3 -1
- package/input-textarea/types.d.ts +7 -0
- package/modal/index.d.ts +4 -0
- package/modal/index.js.map +1 -1
- package/modal/types.d.ts +2 -0
- package/modal-v2/modal-v2.d.ts +6 -0
- package/modal-v2/modal-v2.js +1 -1
- package/modal-v2/modal-v2.js.map +1 -1
- package/modal-v2/slots/card.js +1 -1
- package/modal-v2/slots/card.js.map +1 -1
- package/modal-v2/slots/header.d.ts +5 -0
- package/modal-v2/slots/header.js +2 -0
- package/modal-v2/slots/header.js.map +1 -0
- package/modal-v2/slots/index.d.ts +1 -0
- package/modal-v2/slots/slot-styles.d.ts +4 -0
- package/modal-v2/slots/slot-styles.js +1 -1
- package/modal-v2/slots/slot-styles.js.map +1 -1
- package/modal-v2/slots/{slot-styles_13sd376.css → slot-styles_10ut1v7.css} +5 -1
- package/modal-v2/types.d.ts +21 -0
- package/navbar/navbar-items.js +1 -1
- package/navbar/navbar-items.js.map +1 -1
- package/navbar/navbar-items.styles.d.ts +2 -1
- package/navbar/navbar-items.styles.js +1 -1
- package/navbar/navbar-items.styles.js.map +1 -1
- package/navbar/{navbar-items.styles_1i3ltot.css → navbar-items.styles_1b1zx.css} +9 -8
- package/navbar/navbar-logo-data.js +1 -1
- package/navbar/navbar-logo-data.js.map +1 -1
- package/navbar/navbar.js.map +1 -1
- package/otp-input/otp-input.js.map +1 -1
- package/overlay/overlay.js.map +1 -1
- package/package.json +1 -1
- package/pagination/pagination.js.map +1 -1
- package/phone-number-input/phone-number-input.js.map +1 -1
- package/predictive-text-input/predictive-text-input.js.map +1 -1
- package/shared/dropdown-list/dropdown-list.js.map +1 -1
- package/shared/internal-calendar/calendar-manager.js.map +1 -1
- package/shared/standalone-date-input/standalone-date-input.js.map +1 -1
- package/theme/styles/a11y-playground.css +358 -87
- package/theme/styles/bookingsg.css +391 -85
- package/theme/styles/careercompass.css +396 -90
- package/theme/styles/ccube.css +402 -45
- package/theme/styles/imda.css +348 -85
- package/theme/styles/lifesg.css +377 -87
- package/theme/styles/mylegacy.css +382 -85
- package/theme/styles/oneservice.css +349 -86
- package/theme/styles/pa.css +172 -63
- package/theme/styles/rbs.css +403 -46
- package/theme/styles/sgw-digital-lobby.css +352 -87
- package/theme/styles/smgs.css +401 -95
- package/theme/styles/spf.css +208 -99
- package/theme/styles/supportgowhere.css +387 -90
- package/theme/styles/tote-board.css +816 -0
- package/theme/types.d.ts +1 -1
- package/theme/types.js +1 -1
- package/theme/types.js.map +1 -1
- package/time-slot-bar/time-slot-bar.js.map +1 -1
- package/time-slot-bar-week/time-slot-bar-week-days.js.map +1 -1
- package/timetable/timetable.js.map +1 -1
- package/toggle/toggle-icon.d.ts +2 -1
- package/toggle/toggle-icon.js +1 -1
- package/toggle/toggle-icon.js.map +1 -1
- package/toggle/toggle-icon.styles.d.ts +1 -0
- package/toggle/toggle-icon.styles.js +1 -1
- package/toggle/toggle-icon.styles.js.map +1 -1
- package/toggle/{toggle-icon.styles_ba71y.css → toggle-icon.styles_o6dxr3.css} +3 -2
- package/toggle/toggle.js +1 -1
- package/toggle/toggle.js.map +1 -1
- package/toggle/toggle.styles.d.ts +18 -15
- package/toggle/toggle.styles.js +1 -1
- package/toggle/toggle.styles.js.map +1 -1
- package/toggle/toggle.styles_11tmfsr.css +41 -0
- package/unit-number/unit-number-input.js.map +1 -1
- package/util/is-styled-components.js.map +1 -1
- package/util/string-helper.d.ts +2 -2
- package/util/string-helper.js +1 -1
- package/util/string-helper.js.map +1 -1
- package/util/use-event-listener.js.map +1 -1
- package/util/use-state-actions.js.map +1 -1
- package/cjs/form/form-textarea/form-textarea.styles.js +0 -2
- package/cjs/form/form-textarea/form-textarea.styles.js.map +0 -1
- package/cjs/form/form-textarea/form-textarea.styles_1kneyif.css +0 -3
- package/cjs/toggle/toggle.styles_1t5qrye.css +0 -38
- package/form/form-textarea/form-textarea.styles.d.ts +0 -3
- package/form/form-textarea/form-textarea.styles.js +0 -2
- package/form/form-textarea/form-textarea.styles.js.map +0 -1
- package/form/form-textarea/form-textarea.styles_1kneyif.css +0 -3
- package/theme/styles/default.css +0 -158
- package/toggle/toggle.styles_1t5qrye.css +0 -38
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";require("./toggle.
|
|
1
|
+
"use strict";require("./toggle.styles_11tmfsr.css");exports.alertContainer="a15al1qe",exports.bgDefault="b1l0nsei",exports.bgDisabled="bd5xote",exports.bgError="bfiow01",exports.bgSelected="b17z2k4k",exports.bgSelectedDisabled="b15nuck4",exports.borderDefault="b1fyul7s",exports.borderDisabled="b11szvws",exports.borderError="bybgacj",exports.borderNone="bvkjim3",exports.borderSelected="b1aku79x",exports.borderSelectedDisabled="b12ofp2c",exports.children="cz4bsiy",exports.childrenIsFinalItem="c1k186w3",exports.colorTextDefault="cnwn55u",exports.colorTextDisabled="c1l2ct9g",exports.colorTextError="c1qqpl3b",exports.colorTextPrimary="c1graj5g",exports.container="c14lgw6r",exports.containerNoIndicator="covnc4j",exports.containerUseContentWidth="cangfw6",exports.disabledColorCursor="d9t56r4",exports.errorContainer="eeqryui",exports.expandButton="e1y0x62l",exports.expandButtonPaddingTopRequired="e1gmwtb7",exports.headerContainer="h1tat3es",exports.indicatorLabelContainer="ijwzsce",exports.indicatorLabelContainerAddPadding="i1q8ywpe",exports.input="i17sf3sp",exports.inputContainer="i1l466bv",exports.label="lh3od2r",exports.removeButton="r1mf9nso",exports.subLabel="shwglic",exports.textContainer="t1nsq3eo",exports.toggleContainerHoverDefault="tv1hw4w",exports.toggleContainerHoverError="t1oixyvr",exports.toggleContainerHoverSelected="t1elmp69",exports.toggleLabelDefault="t1izng22",exports.toggleLabelSelected="t194isif",exports.toggleTextContainerDisabledSelected="txm5wyu",exports.toggleTextContainerSelected="t1cyflot",exports.tokens={label:{desktopLineClamp:"--fds-internal-toggle-label-desktopLineClamp",tabletLineClamp:"--fds-internal-toggle-label-tabletLineClamp",mobileLineClamp:"--fds-internal-toggle-label-mobileLineClamp"}};
|
|
2
2
|
//# sourceMappingURL=toggle.styles.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toggle.styles.js","sources":["../../../src/toggle/toggle.styles.ts"],"sourcesContent":["import { css } from \"@linaria/core\";\n\nimport { Colour, Font, MediaQuery, Radius } from \"../theme\";\nimport * as toggleIconStyles from \"./toggle-icon.styles\";\n\nexport const tokens = {\n label: {\n desktopLineClamp: \"--fds-internal-toggle-label-desktopLineClamp\",\n tabletLineClamp: \"--fds-internal-toggle-label-tabletLineClamp\",\n mobileLineClamp: \"--fds-internal-toggle-label-mobileLineClamp\",\n },\n} as const;\n\n// =============================================================================\n// STYLING\n// =============================================================================\n\nexport const
|
|
1
|
+
{"version":3,"file":"toggle.styles.js","sources":["../../../src/toggle/toggle.styles.ts"],"sourcesContent":["import { css } from \"@linaria/core\";\n\nimport { Colour, Font, MediaQuery, Radius } from \"../theme\";\nimport * as toggleIconStyles from \"./toggle-icon.styles\";\n\nexport const tokens = {\n label: {\n desktopLineClamp: \"--fds-internal-toggle-label-desktopLineClamp\",\n tabletLineClamp: \"--fds-internal-toggle-label-tabletLineClamp\",\n mobileLineClamp: \"--fds-internal-toggle-label-mobileLineClamp\",\n },\n} as const;\n\n// =============================================================================\n// BASIC STYLING\n// =============================================================================\n\nexport const borderDefault = css`\n border: solid 1px ${Colour.border};\n`;\n\nexport const borderError = css`\n border: solid 1px ${Colour[\"border-error\"]};\n`;\n\nexport const borderNone = css`\n border: solid 1px transparent;\n`;\n\nexport const borderSelected = css`\n border: solid 1px ${Colour[\"border-selected\"]};\n`;\n\nexport const borderDisabled = css`\n border: solid 1px ${Colour[\"border-disabled\"]};\n`;\n\nexport const borderSelectedDisabled = css`\n border: solid 1px ${Colour[\"border-selected-disabled\"]};\n`;\n\nexport const bgDefault = css`\n background: ${Colour.bg};\n`;\n\nexport const bgSelected = css`\n background: ${Colour[\"bg-selected\"]};\n`;\n\nexport const bgError = css`\n background: ${Colour[\"bg-error\"]};\n`;\n\nexport const bgDisabled = css`\n background: ${Colour[\"bg-disabled\"]};\n`;\n\nexport const bgSelectedDisabled = css`\n background: ${Colour[\"bg-selected-disabled\"]};\n`;\n\nexport const colorTextDefault = css`\n color: ${Colour.text};\n`;\n\nexport const colorTextPrimary = css`\n color: ${Colour[\"text-primary\"]};\n`;\n\nexport const colorTextError = css`\n color: ${Colour[\"text-error\"]};\n`;\n\nexport const colorTextDisabled = css`\n color: ${Colour[\"text-disabled\"]};\n`;\n\n// =============================================================================\n// STYLING\n// =============================================================================\n\nexport const headerContainer = css`\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n`;\n\nexport const textContainer = css`\n display: flex;\n flex-direction: column;\n overflow-wrap: anywhere;\n width: 100%;\n overflow: hidden;\n`;\n\nexport const toggleTextContainerSelected = css`\n color: ${Colour[\"text-selected\"]};\n`;\n\nexport const toggleTextContainerDisabledSelected = css`\n color: ${Colour[\"text-selected-disabled\"]};\n`;\n\nexport const container = css`\n position: relative;\n display: inline-flex;\n min-width: 10.375rem;\n border-radius: ${Radius[\"sm\"]};\n overflow: hidden;\n flex-direction: column;\n height: fit-content;\n\n &:focus-within {\n outline: 2px solid ${Colour[\"focus-ring\"]};\n outline-offset: 0;\n }\n\n &.${borderError}, &.${bgError} {\n &:focus-within {\n outline: 2px solid ${Colour[\"border-error-focus\"]};\n }\n }\n`;\n\nexport const containerNoIndicator = css`\n justify-content: center;\n`;\n\nexport const containerUseContentWidth = css`\n min-width: unset;\n`;\n\nexport const toggleContainerHoverError = css`\n &:has(.${headerContainer}:hover) {\n @media (pointer: fine) {\n background: ${Colour[\"bg-error-hover\"]};\n }\n }\n`;\n\nexport const toggleContainerHoverSelected = css`\n &:has(.${headerContainer}:hover) {\n @media (pointer: fine) {\n background: ${Colour[\"bg-selected-hover\"]};\n\n & .${textContainer} {\n color: ${Colour[\"text-selected-hover\"]};\n }\n\n & .${toggleIconStyles.wrapperBase} {\n color: ${Colour[\"icon-selected-hover\"]};\n }\n }\n }\n`;\n\nexport const toggleContainerHoverDefault = css`\n &:has(.${headerContainer}:hover) {\n @media (pointer: fine) {\n background: ${Colour[\"bg-hover-subtle\"]};\n }\n }\n`;\n\nexport const input = css`\n position: absolute;\n height: 100%;\n width: 100%;\n cursor: pointer;\n top: 0;\n left: 0;\n opacity: 0;\n\n /* Hide appearance but keep it focusable using keyboard interactions */\n appearance: none;\n background: transparent;\n border: none;\n`;\n\nexport const inputContainer = css`\n display: flex;\n`;\n\nexport const label = css`\n overflow: hidden;\n display: -webkit-box;\n text-overflow: ellipsis;\n -webkit-box-orient: vertical;\n overflow-wrap: break-word;\n\n ${tokens.label.desktopLineClamp}: initial;\n ${tokens.label.tabletLineClamp}: initial;\n ${tokens.label.mobileLineClamp}: initial;\n\n -webkit-line-clamp: var(${tokens.label.desktopLineClamp}, none);\n ${MediaQuery.MaxWidth.lg} {\n -webkit-line-clamp: var(${tokens.label.tabletLineClamp}, none);\n }\n ${MediaQuery.MaxWidth.sm} {\n -webkit-line-clamp: var(${tokens.label.mobileLineClamp}, none);\n }\n`;\n\nexport const toggleLabelDefault = css`\n ${Font[\"body-baseline-regular\"]}\n`;\n\nexport const toggleLabelSelected = css`\n ${Font[\"body-baseline-semibold\"]}\n`;\n\nexport const subLabel = css`\n ${Font[\"body-md-regular\"]}\n margin-top: 0.5rem;\n\n z-index: 1; // forces sublabel to render above the input\n pointer-events: none; // to allow click events to be passed to the input\n\n strong,\n b {\n ${Font[\"body-md-semibold\"]}\n }\n`;\n\nexport const indicatorLabelContainer = css`\n display: flex;\n height: 100%;\n width: 100%;\n position: relative;\n padding: 0.6875rem 1rem;\n`;\n\nexport const indicatorLabelContainerAddPadding = css`\n padding: 0.6875rem 0rem 0.6875rem 1rem;\n`;\n\nexport const removeButton = css`\n color: ${Colour[\"text-error\"]};\n white-space: nowrap;\n ${Font[\"body-md-semibold\"]}\n height: fit-content;\n padding: 0.6875rem 1rem 0.6875rem 0.5rem;\n border: none;\n background: none;\n cursor: pointer;\n`;\n\nexport const disabledColorCursor = css`\n color: ${Colour[\"text-disabled\"]};\n cursor: not-allowed;\n`;\n\nexport const expandButton = css`\n ${Font[\"body-baseline-semibold\"]}\n display: flex;\n align-items: center;\n justify-content: flex-end;\n border: none;\n background-color: ${Colour.bg};\n cursor: pointer;\n padding: 0 1rem 0.6875rem 1rem;\n width: 100%;\n\n svg {\n width: 1em;\n height: 1em;\n margin-left: 0.5rem;\n }\n`;\n\nexport const expandButtonPaddingTopRequired = css`\n padding-top: 0.6875rem;\n`;\n\nexport const errorContainer = css`\n width: 100%;\n border: none;\n background: ${Colour.bg};\n cursor: pointer;\n padding: 0.6875rem 1rem 0.5rem 1rem;\n`;\n\nexport const children = css`\n padding: 0 1rem;\n padding-top: 0.6875rem;\n padding-bottom: 0.5rem;\n background-color: ${Colour.bg};\n`;\n\nexport const childrenIsFinalItem = css`\n padding-bottom: 0.6875rem;\n`;\n\nexport const alertContainer = css`\n width: 100%;\n user-select: none;\n`;\n"],"names":["label","desktopLineClamp","tabletLineClamp","mobileLineClamp"],"mappings":"iGAqS2B,6BA5PL,8BAYC,0BAJH,6BAJG,sCAYQ,iCAxCL,kCAgBC,+BAZH,6BAID,iCAII,0CAQQ,4BAqPd,sCAOW,oCApOH,oCAYC,kCAJH,oCAJE,6BAsCP,wCAqBW,2CAII,sCAuHL,iCA2BL,+BAtBF,kDAkBkB,mCA7Lf,2CA+IQ,oDAQU,yBApE5B,kCAeS,yBAIT,+BAqDO,4BAzBJ,gCA5HK,+CAqEc,4CAxBF,gDAQG,sCA+DV,uCAIC,uDA5GgB,8CAJR,0BA1FlB,CAClBA,MAAO,CACHC,iBAAkB,+CAClBC,gBAAiB,8CACjBC,gBAAiB"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
@layer fds{.b1fyul7s{border:solid 1px var(--fds-colour-border);}
|
|
2
|
+
.bybgacj{border:solid 1px var(--fds-colour-border-error);}
|
|
3
|
+
.bvkjim3{border:solid 1px transparent;}
|
|
4
|
+
.b1aku79x{border:solid 1px var(--fds-colour-border-selected);}
|
|
5
|
+
.b11szvws{border:solid 1px var(--fds-colour-border-disabled);}
|
|
6
|
+
.b12ofp2c{border:solid 1px var(--fds-colour-border-selected-disabled);}
|
|
7
|
+
.b1l0nsei{background:var(--fds-colour-bg);}
|
|
8
|
+
.b17z2k4k{background:var(--fds-colour-bg-selected);}
|
|
9
|
+
.bfiow01{background:var(--fds-colour-bg-error);}
|
|
10
|
+
.bd5xote{background:var(--fds-colour-bg-disabled);}
|
|
11
|
+
.b15nuck4{background:var(--fds-colour-bg-selected-disabled);}
|
|
12
|
+
.cnwn55u{color:var(--fds-colour-text);}
|
|
13
|
+
.c1graj5g{color:var(--fds-colour-text-primary);}
|
|
14
|
+
.c1qqpl3b{color:var(--fds-colour-text-error);}
|
|
15
|
+
.c1l2ct9g{color:var(--fds-colour-text-disabled);}
|
|
16
|
+
.h1tat3es{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:justify;-ms-flex-pack:space-between;-webkit-justify-content:space-between;justify-content:space-between;}
|
|
17
|
+
.t1nsq3eo{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;overflow-wrap:anywhere;width:100%;overflow:hidden;}
|
|
18
|
+
.t1cyflot{color:var(--fds-colour-text-selected);}
|
|
19
|
+
.txm5wyu{color:var(--fds-colour-text-selected-disabled);}
|
|
20
|
+
.c14lgw6r{position:relative;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;min-width:10.375rem;border-radius:var(--fds-radius-sm);overflow:hidden;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;}.c14lgw6r:focus-within{outline:2px solid var(--fds-colour-focus-ring);outline-offset:0;}.c14lgw6r.bybgacj:focus-within,.c14lgw6r.bfiow01:focus-within{outline:2px solid var(--fds-colour-border-error-focus);}
|
|
21
|
+
.covnc4j{-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;}
|
|
22
|
+
.cangfw6{min-width:unset;}
|
|
23
|
+
@media (pointer: fine){.t1oixyvr:has(.h1tat3es:hover){background:var(--fds-colour-bg-error-hover);}}
|
|
24
|
+
@media (pointer: fine){.t1elmp69:has(.h1tat3es:hover){background:var(--fds-colour-bg-selected-hover);}.t1elmp69:has(.h1tat3es:hover) .t1nsq3eo{color:var(--fds-colour-text-selected-hover);}.t1elmp69:has(.h1tat3es:hover) .wxufi5f{color:var(--fds-colour-icon-selected-hover);}}
|
|
25
|
+
@media (pointer: fine){.tv1hw4w:has(.h1tat3es:hover){background:var(--fds-colour-bg-hover-subtle);}}
|
|
26
|
+
.i17sf3sp{position:absolute;height:100%;width:100%;cursor:pointer;top:0;left:0;opacity:0;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;background:transparent;border:none;}
|
|
27
|
+
.i1l466bv{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}
|
|
28
|
+
.lh3od2r{overflow:hidden;display:-webkit-box;text-overflow:ellipsis;-webkit-box-orient:vertical;overflow-wrap:break-word;--fds-internal-toggle-label-desktopLineClamp:initial;--fds-internal-toggle-label-tabletLineClamp:initial;--fds-internal-toggle-label-mobileLineClamp:initial;-webkit-line-clamp:var(--fds-internal-toggle-label-desktopLineClamp, none);}:where(body.fds-breakpoint-lg-max) .lh3od2r{-webkit-line-clamp:var(--fds-internal-toggle-label-tabletLineClamp, none);}:where(body.fds-breakpoint-sm-max) .lh3od2r{-webkit-line-clamp:var(--fds-internal-toggle-label-mobileLineClamp, none);}
|
|
29
|
+
.t1izng22{font-family:var(--fds-font-family-body);font-variant:var(--fds-font-variant);font-size:var(--fds-font-body-size-baseline);font-weight:var(--fds-font-weight-regular);line-height:var(--fds-font-body-lh-baseline);letter-spacing:var(--fds-font-body-ls-baseline);}
|
|
30
|
+
.t194isif{font-family:var(--fds-font-family-body);font-variant:var(--fds-font-variant);font-size:var(--fds-font-body-size-baseline);font-weight:var(--fds-font-weight-semibold);line-height:var(--fds-font-body-lh-baseline);letter-spacing:var(--fds-font-body-ls-baseline);}
|
|
31
|
+
.shwglic{font-family:var(--fds-font-family-body);font-variant:var(--fds-font-variant);font-size:var(--fds-font-body-size-md);font-weight:var(--fds-font-weight-regular);line-height:var(--fds-font-body-lh-md);letter-spacing:var(--fds-font-body-ls-md);margin-top:0.5rem;z-index:1;pointer-events:none;}.shwglic strong,.shwglic b{font-family:var(--fds-font-family-body);font-variant:var(--fds-font-variant);font-size:var(--fds-font-body-size-md);font-weight:var(--fds-font-weight-semibold);line-height:var(--fds-font-body-lh-md);letter-spacing:var(--fds-font-body-ls-md);}
|
|
32
|
+
.ijwzsce{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;height:100%;width:100%;position:relative;padding:0.6875rem 1rem;}
|
|
33
|
+
.i1q8ywpe{padding:0.6875rem 0rem 0.6875rem 1rem;}
|
|
34
|
+
.r1mf9nso{color:var(--fds-colour-text-error);white-space:nowrap;font-family:var(--fds-font-family-body);font-variant:var(--fds-font-variant);font-size:var(--fds-font-body-size-md);font-weight:var(--fds-font-weight-semibold);line-height:var(--fds-font-body-lh-md);letter-spacing:var(--fds-font-body-ls-md);height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;padding:0.6875rem 1rem 0.6875rem 0.5rem;border:none;background:none;cursor:pointer;}
|
|
35
|
+
.d9t56r4{color:var(--fds-colour-text-disabled);cursor:not-allowed;}
|
|
36
|
+
.e1y0x62l{font-family:var(--fds-font-family-body);font-variant:var(--fds-font-variant);font-size:var(--fds-font-body-size-baseline);font-weight:var(--fds-font-weight-semibold);line-height:var(--fds-font-body-lh-baseline);letter-spacing:var(--fds-font-body-ls-baseline);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:end;-ms-flex-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end;border:none;background-color:var(--fds-colour-bg);cursor:pointer;padding:0 1rem 0.6875rem 1rem;width:100%;}.e1y0x62l svg{width:1em;height:1em;margin-left:0.5rem;}
|
|
37
|
+
.e1gmwtb7{padding-top:0.6875rem;}
|
|
38
|
+
.eeqryui{width:100%;border:none;background:var(--fds-colour-bg);cursor:pointer;padding:0.6875rem 1rem 0.5rem 1rem;}
|
|
39
|
+
.cz4bsiy{padding:0 1rem;padding-top:0.6875rem;padding-bottom:0.5rem;background-color:var(--fds-colour-bg);}
|
|
40
|
+
.c1k186w3{padding-bottom:0.6875rem;}
|
|
41
|
+
.a15al1qe{width:100%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unit-number-input.js","sources":["../../../src/unit-number/unit-number-input.tsx"],"sourcesContent":["import clsx from \"clsx\";\nimport type React from \"react\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { Input } from \"../input\";\nimport * as inputGroupStyles from \"../input-group/input-group.styles\";\nimport { concatIds, VisuallyHidden } from \"../shared/accessibility\";\nimport { InputWrapper } from \"../shared/input-wrapper\";\nimport { Typography } from \"../typography\";\nimport { StringHelper, useId, useNextInputState } from \"../util\";\nimport type { UnitNumberInputProps } from \"./types\";\nimport * as styles from \"./unit-number-input.styles\";\n\ntype FieldType = \"floor\" | \"unit\" | \"none\";\ntype ValueFieldTypes = Exclude<FieldType, \"none\">;\n\n// @catalog\n/**\n * A two-field input for entering Singapore-style unit numbers in `floor-unit` format.\n *\n * Use `UnitNumberInput` when you need a structured unit number entry that splits\n * floor and unit into separate fields.\n */\nexport const UnitNumberInput = ({\n disabled,\n error,\n value,\n onChange,\n onBlur,\n onChangeRaw,\n onBlurRaw,\n readOnly,\n placeholder = \"00-8888\",\n autoComplete,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n className,\n ...otherProps\n}: UnitNumberInputProps) => {\n // =============================================================================\n // CONST, STATE, REF\n // =============================================================================\n const [floorValue, _setFloorValue] = useState<string>(\"\");\n const [unitValue, _setUnitValue] = useState<string>(\"\");\n const [currentFocus, _setCurrentFocus] = useState<FieldType>(\"none\");\n const internalId = useId();\n const floorLabelId = `${internalId}-floor-label`;\n const unitLabelId = `${internalId}-unit-label`;\n const liveMessageId = `${internalId}-live-message`;\n\n const nodeRef = useRef<HTMLDivElement>(null);\n const floorInputRef = useRef<HTMLInputElement>(null);\n const unitInputRef = useRef<HTMLInputElement>(null);\n\n /**\n * Have to use refs to allow the state values to be accessible\n * by the event listener callback functions\n * Reference:\n * https://stackoverflow.com/questions/55265255/react-usestate-hook-event-handler-using-initial-state\n */\n\n const floorValueStateRef = useRef<string>(floorValue);\n const unitValueStateRef = useRef<string>(unitValue);\n const currentFocusStateRef = useRef<FieldType>(currentFocus);\n\n const formatter = (value: string) =>\n value.toLocaleUpperCase().replace(/[^0-9A-Za-z]/g, \"\");\n const getNextFloorInputState = useNextInputState({\n ref: floorInputRef,\n formatter,\n });\n const getNextUnitInputState = useNextInputState({\n ref: unitInputRef,\n formatter,\n });\n\n const liveMessage = useMemo(() => {\n let msg = \"\";\n const floorMsg = formatPhraseWithPrefix(\"Hash\", floorValue);\n const unitMsg = formatPhraseWithPrefix(\"Dash\", unitValue);\n switch (currentFocus) {\n case \"floor\":\n msg = floorMsg;\n break;\n case \"unit\":\n msg = floorValue ? [floorMsg, unitMsg].join(\" \") : unitMsg;\n break;\n default:\n msg = \"\";\n }\n return msg;\n }, [currentFocus, floorValue, unitValue]);\n\n // =============================================================================\n // REF FUNCTIONS\n // =============================================================================\n const setFloorValue = (data: string) => {\n floorValueStateRef.current = data;\n _setFloorValue(data);\n };\n\n const setUnitValue = (data: string) => {\n unitValueStateRef.current = data;\n _setUnitValue(data);\n };\n\n const setCurrentFocus = (data: FieldType) => {\n currentFocusStateRef.current = data;\n _setCurrentFocus(data);\n };\n\n // =============================================================================\n // EFFECTS\n // =============================================================================\n useEffect(() => {\n // Auto focus feature\n if (\n currentFocus === \"floor\" &&\n floorValue.length === 3 &&\n unitInputRef.current\n ) {\n unitInputRef.current.focus();\n }\n }, [floorValue]);\n\n useEffect(() => {\n updateValues(value);\n }, [value]);\n\n // =============================================================================\n // EVENT HANDLERS\n // =============================================================================\n const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {\n setCurrentFocus(event.target.name as FieldType);\n event.target.select();\n };\n\n const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {\n const targetName = event.target.name as FieldType;\n const targetValue = event.target.value;\n const formattedInput = formatInput(targetValue);\n\n if (targetName === \"floor\") {\n setFloorValue(formattedInput);\n if (formattedInput !== floorValue) {\n performOnChangeHandler(formattedInput, targetName);\n }\n } else if (targetName === \"unit\") {\n setUnitValue(formattedInput);\n if (formattedInput !== unitValue) {\n performOnChangeHandler(formattedInput, targetName);\n }\n }\n };\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n const targetName = event.target.name as FieldType;\n\n if (targetName === \"floor\") {\n const nextInputState = getNextFloorInputState();\n if (!nextInputState) return;\n\n const { nextValue, updateCaretPosition } = nextInputState;\n updateCaretPosition();\n setFloorValue(nextValue);\n performOnChangeHandler(nextValue, targetName);\n } else if (targetName === \"unit\") {\n const nextInputState = getNextUnitInputState();\n if (!nextInputState) return;\n\n const { nextValue, updateCaretPosition } = nextInputState;\n updateCaretPosition();\n setUnitValue(nextValue);\n performOnChangeHandler(nextValue, targetName);\n }\n };\n\n const handleNodeClick = () => {\n if (currentFocus === \"none\" && floorInputRef.current) {\n floorInputRef.current.focus();\n }\n };\n\n const handleNodeBlur = (event: React.FocusEvent) => {\n if (nodeRef.current && nodeRef.current.contains(event.relatedTarget)) {\n // internal element received focus\n return;\n }\n if (currentFocusStateRef.current !== \"none\") {\n setCurrentFocus(\"none\");\n performOnBlurHandler();\n }\n };\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n /**\n * Allow going to the field before if user presses Backspace\n * on an empty field\n */\n if (event.code === \"Backspace\" || event.key === \"Backspace\") {\n if (currentFocus === \"unit\" && unitValue.length === 0) {\n floorInputRef.current?.focus();\n }\n }\n };\n\n // =============================================================================\n // HELPER FUNCTIONS\n // =============================================================================\n const formatInput = (value: string) => {\n return /^[0-9]$/.test(value)\n ? StringHelper.padValue(value, true)\n : value.toLocaleUpperCase();\n };\n\n const updateValues = (value: string | undefined) => {\n if (value === INVALID_VALUE) {\n return;\n } else if (value === undefined || value.length === 0) {\n setFloorValue(\"\");\n setUnitValue(\"\");\n } else {\n const valueArr = value.split(\"-\");\n if (valueArr.length === 2) {\n // Valid value\n const floor = valueArr[0];\n const unit = valueArr[1];\n setFloorValue(\n currentFocus === \"floor\" ? floor : formatInput(floor)\n );\n setUnitValue(\n currentFocus === \"unit\" ? unit : formatInput(unit)\n );\n }\n }\n };\n\n const performOnChangeHandler = (\n changeValue: string,\n field: ValueFieldTypes\n ) => {\n if (!onChange && !onChangeRaw) {\n return;\n }\n\n const values: Record<ValueFieldTypes, string> = {\n floor: floorValueStateRef.current,\n unit: unitValueStateRef.current,\n };\n\n // Update the specific field value\n values[field] = changeValue;\n\n if (onChange) {\n const returnValue = getFormattedValue(values);\n onChange(returnValue);\n }\n\n if (onChangeRaw) {\n onChangeRaw([values.floor, values.unit]);\n }\n };\n\n const performOnBlurHandler = () => {\n if (!onBlur && !onBlurRaw) {\n return;\n }\n\n const values: Record<ValueFieldTypes, string> = {\n floor: formatInput(floorValueStateRef.current),\n unit: formatInput(unitValueStateRef.current),\n };\n\n if (onBlur) {\n const returnValue = getFormattedValue(values);\n onBlur(returnValue);\n }\n\n if (onBlurRaw) {\n onBlurRaw([values.floor, values.unit]);\n }\n };\n\n const getFormattedValue = (values: Record<ValueFieldTypes, string>) => {\n const valueArr = [values.floor, values.unit];\n\n if (values.floor.length > 0 && values.unit.length > 0) {\n return valueArr.join(\"-\");\n } else if (valueArr.every((value) => value === \"\")) {\n return \"\";\n } else {\n return INVALID_VALUE;\n }\n };\n\n const getPlaceholder = (value: string) => {\n return value.split(\"-\");\n };\n\n function formatPhraseWithPrefix(prefix: string, v: string) {\n return v ? `${prefix} ${Array.from(v).join(\" \")}` : \"\";\n }\n\n // =============================================================================\n // RENDER FUNCTION\n // =============================================================================\n const renderInputs = () => (\n <>\n <Input\n name=\"floor\"\n maxLength={3}\n value={floorValue}\n ref={floorInputRef}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onChange={handleChange}\n disabled={disabled}\n readOnly={readOnly}\n type=\"text\"\n pattern=\"[0-9A-Z]{2,3}\"\n data-testid=\"floor-input\"\n aria-labelledby={concatIds(ariaLabelledBy, floorLabelId)}\n aria-describedby={concatIds(ariaDescribedBy, liveMessageId)}\n aria-invalid={ariaInvalid}\n placeholder={\n currentFocus === \"floor\" && !readOnly && !disabled\n ? \"\"\n : getPlaceholder(placeholder)[0]\n }\n autoComplete={autoComplete}\n styleType=\"no-border\"\n className={clsx(styles.input, styles.floorInput)}\n />\n <VisuallyHidden aria-hidden id={floorLabelId}>\n Enter floor number\n </VisuallyHidden>\n <Typography.BodyBL\n className={clsx(\n styles.unitNumberDivider,\n floorValue.length === 0 && styles.unitNumberDividerInactive\n )}\n >\n -\n </Typography.BodyBL>\n <Input\n name=\"unit\"\n maxLength={5}\n value={unitValue}\n ref={unitInputRef}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n disabled={disabled}\n readOnly={readOnly}\n type=\"text\"\n pattern=\"[0-9A-Z]{2,5}\"\n data-testid=\"unit-input\"\n aria-labelledby={concatIds(ariaLabelledBy, unitLabelId)}\n aria-describedby={concatIds(ariaDescribedBy, liveMessageId)}\n aria-invalid={ariaInvalid}\n placeholder={\n currentFocus === \"unit\" && !readOnly && !disabled\n ? \"\"\n : getPlaceholder(placeholder)[1]\n }\n autoComplete={autoComplete}\n styleType=\"no-border\"\n className={clsx(\n styles.input,\n styles.floorInput,\n styles.unitInput\n )}\n />\n <VisuallyHidden aria-hidden id={unitLabelId}>\n Enter unit number\n </VisuallyHidden>\n {/** Live message for AT reader to read with the combination of prefix and current value for both floor input and unit input */}\n <VisuallyHidden id={liveMessageId} aria-live=\"polite\">\n {liveMessage}\n </VisuallyHidden>\n </>\n );\n\n const renderReadOnly = (displayValue: string) => {\n const displayValueArr = displayValue.split(\"-\");\n const liveMessageForReadOnly = [\n formatPhraseWithPrefix(\"Hash\", displayValueArr[0]),\n formatPhraseWithPrefix(\"Dash\", displayValueArr[1]),\n ].join(\" \");\n\n return (\n <div\n data-testid=\"readonly-display\"\n tabIndex={0}\n role=\"textbox\"\n aria-readonly\n aria-labelledby={ariaLabelledBy}\n aria-describedby={ariaDescribedBy}\n aria-invalid={ariaInvalid}\n className={styles.readOnlyContainer}\n >\n <Typography.BodyBL>{displayValueArr[0]}</Typography.BodyBL>\n <Typography.BodyBL className={styles.unitNumberDivider}>\n -\n </Typography.BodyBL>\n <Typography.BodyBL>{displayValueArr[1]}</Typography.BodyBL>\n <VisuallyHidden>{liveMessageForReadOnly}</VisuallyHidden>\n </div>\n );\n };\n\n return (\n <InputWrapper\n {...otherProps}\n ref={nodeRef}\n onClick={handleNodeClick}\n disabled={disabled}\n error={error}\n readOnly={readOnly}\n tabIndex={-1}\n onBlur={handleNodeBlur}\n className={className}\n >\n <div\n data-testid=\"addon\"\n className={clsx(\n styles.hashContainer,\n inputGroupStyles.labelAddonContainer\n )}\n data-disabled={disabled}\n data-read-only={readOnly}\n >\n #\n </div>\n {readOnly && value ? renderReadOnly(value) : renderInputs()}\n </InputWrapper>\n );\n};\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\nconst INVALID_VALUE = \"Invalid unit number\";\n"],"names":["INVALID_VALUE","_a","disabled","error","value","onChange","onBlur","onChangeRaw","onBlurRaw","readOnly","placeholder","autoComplete","ariaLabelledBy","ariaDescribedBy","ariaInvalid","className","otherProps","__rest","floorValue","_setFloorValue","useState","unitValue","_setUnitValue","currentFocus","_setCurrentFocus","internalId","useId","floorLabelId","unitLabelId","liveMessageId","nodeRef","useRef","floorInputRef","unitInputRef","floorValueStateRef","unitValueStateRef","currentFocusStateRef","formatter","toLocaleUpperCase","replace","getNextFloorInputState","useNextInputState","ref","getNextUnitInputState","liveMessage","useMemo","msg","floorMsg","formatPhraseWithPrefix","unitMsg","join","setFloorValue","data","current","setUnitValue","setCurrentFocus","useEffect","length","focus","updateValues","handleFocus","event","target","name","select","handleBlur","targetName","targetValue","formattedInput","formatInput","performOnChangeHandler","handleChange","nextInputState","nextValue","updateCaretPosition","handleKeyDown","code","key","test","StringHelper","padValue","undefined","valueArr","split","floor","unit","changeValue","field","values","returnValue","getFormattedValue","performOnBlurHandler","every","getPlaceholder","prefix","v","Array","from","_jsxs","InputWrapper","Object","assign","onClick","tabIndex","contains","relatedTarget","children","_jsx","clsx","styles.hashContainer","inputGroupStyles.labelAddonContainer","displayValue","displayValueArr","liveMessageForReadOnly","role","styles.readOnlyContainer","Typography","BodyBL","styles.unitNumberDivider","VisuallyHidden","renderReadOnly","_Fragment","Input","maxLength","onFocus","type","pattern","concatIds","styleType","styles.input","styles.floorInput","id","styles.unitNumberDividerInactive","onKeyDown","styles.unitInput"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"unit-number-input.js","sources":["../../../src/unit-number/unit-number-input.tsx"],"sourcesContent":["import clsx from \"clsx\";\nimport type React from \"react\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { Input } from \"../input\";\nimport * as inputGroupStyles from \"../input-group/input-group.styles\";\nimport { concatIds, VisuallyHidden } from \"../shared/accessibility\";\nimport { InputWrapper } from \"../shared/input-wrapper\";\nimport { Typography } from \"../typography\";\nimport { StringHelper, useId, useNextInputState } from \"../util\";\nimport type { UnitNumberInputProps } from \"./types\";\nimport * as styles from \"./unit-number-input.styles\";\n\ntype FieldType = \"floor\" | \"unit\" | \"none\";\ntype ValueFieldTypes = Exclude<FieldType, \"none\">;\n\n// @catalog\n/**\n * A two-field input for entering Singapore-style unit numbers in `floor-unit` format.\n *\n * Use `UnitNumberInput` when you need a structured unit number entry that splits\n * floor and unit into separate fields.\n */\nexport const UnitNumberInput = ({\n disabled,\n error,\n value,\n onChange,\n onBlur,\n onChangeRaw,\n onBlurRaw,\n readOnly,\n placeholder = \"00-8888\",\n autoComplete,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n className,\n ...otherProps\n}: UnitNumberInputProps) => {\n // =============================================================================\n // CONST, STATE, REF\n // =============================================================================\n const [floorValue, _setFloorValue] = useState<string>(\"\");\n const [unitValue, _setUnitValue] = useState<string>(\"\");\n const [currentFocus, _setCurrentFocus] = useState<FieldType>(\"none\");\n const internalId = useId();\n const floorLabelId = `${internalId}-floor-label`;\n const unitLabelId = `${internalId}-unit-label`;\n const liveMessageId = `${internalId}-live-message`;\n\n const nodeRef = useRef<HTMLDivElement>(null);\n const floorInputRef = useRef<HTMLInputElement>(null);\n const unitInputRef = useRef<HTMLInputElement>(null);\n\n /**\n * Have to use refs to allow the state values to be accessible\n * by the event listener callback functions\n * Reference:\n * https://stackoverflow.com/questions/55265255/react-usestate-hook-event-handler-using-initial-state\n */\n\n const floorValueStateRef = useRef<string>(floorValue);\n const unitValueStateRef = useRef<string>(unitValue);\n const currentFocusStateRef = useRef<FieldType>(currentFocus);\n\n const formatter = (value: string) =>\n value.toLocaleUpperCase().replace(/[^0-9A-Za-z]/g, \"\");\n const getNextFloorInputState = useNextInputState({\n ref: floorInputRef,\n formatter,\n });\n const getNextUnitInputState = useNextInputState({\n ref: unitInputRef,\n formatter,\n });\n\n const liveMessage = useMemo(() => {\n let msg = \"\";\n const floorMsg = formatPhraseWithPrefix(\"Hash\", floorValue);\n const unitMsg = formatPhraseWithPrefix(\"Dash\", unitValue);\n switch (currentFocus) {\n case \"floor\":\n msg = floorMsg;\n break;\n case \"unit\":\n msg = floorValue ? [floorMsg, unitMsg].join(\" \") : unitMsg;\n break;\n default:\n msg = \"\";\n }\n return msg;\n }, [currentFocus, floorValue, unitValue]);\n\n // =============================================================================\n // REF FUNCTIONS\n // =============================================================================\n const setFloorValue = (data: string) => {\n floorValueStateRef.current = data;\n _setFloorValue(data);\n };\n\n const setUnitValue = (data: string) => {\n unitValueStateRef.current = data;\n _setUnitValue(data);\n };\n\n const setCurrentFocus = (data: FieldType) => {\n currentFocusStateRef.current = data;\n _setCurrentFocus(data);\n };\n\n // =============================================================================\n // EFFECTS\n // =============================================================================\n useEffect(() => {\n // Auto focus feature\n if (\n currentFocus === \"floor\" &&\n floorValue.length === 3 &&\n unitInputRef.current\n ) {\n unitInputRef.current.focus();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [floorValue]);\n\n useEffect(() => {\n updateValues(value);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [value]);\n\n // =============================================================================\n // EVENT HANDLERS\n // =============================================================================\n const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {\n setCurrentFocus(event.target.name as FieldType);\n event.target.select();\n };\n\n const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {\n const targetName = event.target.name as FieldType;\n const targetValue = event.target.value;\n const formattedInput = formatInput(targetValue);\n\n if (targetName === \"floor\") {\n setFloorValue(formattedInput);\n if (formattedInput !== floorValue) {\n performOnChangeHandler(formattedInput, targetName);\n }\n } else if (targetName === \"unit\") {\n setUnitValue(formattedInput);\n if (formattedInput !== unitValue) {\n performOnChangeHandler(formattedInput, targetName);\n }\n }\n };\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n const targetName = event.target.name as FieldType;\n\n if (targetName === \"floor\") {\n const nextInputState = getNextFloorInputState();\n if (!nextInputState) return;\n\n const { nextValue, updateCaretPosition } = nextInputState;\n updateCaretPosition();\n setFloorValue(nextValue);\n performOnChangeHandler(nextValue, targetName);\n } else if (targetName === \"unit\") {\n const nextInputState = getNextUnitInputState();\n if (!nextInputState) return;\n\n const { nextValue, updateCaretPosition } = nextInputState;\n updateCaretPosition();\n setUnitValue(nextValue);\n performOnChangeHandler(nextValue, targetName);\n }\n };\n\n const handleNodeClick = () => {\n if (currentFocus === \"none\" && floorInputRef.current) {\n floorInputRef.current.focus();\n }\n };\n\n const handleNodeBlur = (event: React.FocusEvent) => {\n if (nodeRef.current && nodeRef.current.contains(event.relatedTarget)) {\n // internal element received focus\n return;\n }\n if (currentFocusStateRef.current !== \"none\") {\n setCurrentFocus(\"none\");\n performOnBlurHandler();\n }\n };\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n /**\n * Allow going to the field before if user presses Backspace\n * on an empty field\n */\n if (event.code === \"Backspace\" || event.key === \"Backspace\") {\n if (currentFocus === \"unit\" && unitValue.length === 0) {\n floorInputRef.current?.focus();\n }\n }\n };\n\n // =============================================================================\n // HELPER FUNCTIONS\n // =============================================================================\n const formatInput = (value: string) => {\n return /^[0-9]$/.test(value)\n ? StringHelper.padValue(value, true)\n : value.toLocaleUpperCase();\n };\n\n const updateValues = (value: string | undefined) => {\n if (value === INVALID_VALUE) {\n return;\n } else if (value === undefined || value.length === 0) {\n setFloorValue(\"\");\n setUnitValue(\"\");\n } else {\n const valueArr = value.split(\"-\");\n if (valueArr.length === 2) {\n // Valid value\n const floor = valueArr[0];\n const unit = valueArr[1];\n setFloorValue(\n currentFocus === \"floor\" ? floor : formatInput(floor)\n );\n setUnitValue(\n currentFocus === \"unit\" ? unit : formatInput(unit)\n );\n }\n }\n };\n\n const performOnChangeHandler = (\n changeValue: string,\n field: ValueFieldTypes\n ) => {\n if (!onChange && !onChangeRaw) {\n return;\n }\n\n const values: Record<ValueFieldTypes, string> = {\n floor: floorValueStateRef.current,\n unit: unitValueStateRef.current,\n };\n\n // Update the specific field value\n values[field] = changeValue;\n\n if (onChange) {\n const returnValue = getFormattedValue(values);\n onChange(returnValue);\n }\n\n if (onChangeRaw) {\n onChangeRaw([values.floor, values.unit]);\n }\n };\n\n const performOnBlurHandler = () => {\n if (!onBlur && !onBlurRaw) {\n return;\n }\n\n const values: Record<ValueFieldTypes, string> = {\n floor: formatInput(floorValueStateRef.current),\n unit: formatInput(unitValueStateRef.current),\n };\n\n if (onBlur) {\n const returnValue = getFormattedValue(values);\n onBlur(returnValue);\n }\n\n if (onBlurRaw) {\n onBlurRaw([values.floor, values.unit]);\n }\n };\n\n const getFormattedValue = (values: Record<ValueFieldTypes, string>) => {\n const valueArr = [values.floor, values.unit];\n\n if (values.floor.length > 0 && values.unit.length > 0) {\n return valueArr.join(\"-\");\n } else if (valueArr.every((value) => value === \"\")) {\n return \"\";\n } else {\n return INVALID_VALUE;\n }\n };\n\n const getPlaceholder = (value: string) => {\n return value.split(\"-\");\n };\n\n function formatPhraseWithPrefix(prefix: string, v: string) {\n return v ? `${prefix} ${Array.from(v).join(\" \")}` : \"\";\n }\n\n // =============================================================================\n // RENDER FUNCTION\n // =============================================================================\n const renderInputs = () => (\n <>\n <Input\n name=\"floor\"\n maxLength={3}\n value={floorValue}\n ref={floorInputRef}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onChange={handleChange}\n disabled={disabled}\n readOnly={readOnly}\n type=\"text\"\n pattern=\"[0-9A-Z]{2,3}\"\n data-testid=\"floor-input\"\n aria-labelledby={concatIds(ariaLabelledBy, floorLabelId)}\n aria-describedby={concatIds(ariaDescribedBy, liveMessageId)}\n aria-invalid={ariaInvalid}\n placeholder={\n currentFocus === \"floor\" && !readOnly && !disabled\n ? \"\"\n : getPlaceholder(placeholder)[0]\n }\n autoComplete={autoComplete}\n styleType=\"no-border\"\n className={clsx(styles.input, styles.floorInput)}\n />\n <VisuallyHidden aria-hidden id={floorLabelId}>\n Enter floor number\n </VisuallyHidden>\n <Typography.BodyBL\n className={clsx(\n styles.unitNumberDivider,\n floorValue.length === 0 && styles.unitNumberDividerInactive\n )}\n >\n -\n </Typography.BodyBL>\n <Input\n name=\"unit\"\n maxLength={5}\n value={unitValue}\n ref={unitInputRef}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n disabled={disabled}\n readOnly={readOnly}\n type=\"text\"\n pattern=\"[0-9A-Z]{2,5}\"\n data-testid=\"unit-input\"\n aria-labelledby={concatIds(ariaLabelledBy, unitLabelId)}\n aria-describedby={concatIds(ariaDescribedBy, liveMessageId)}\n aria-invalid={ariaInvalid}\n placeholder={\n currentFocus === \"unit\" && !readOnly && !disabled\n ? \"\"\n : getPlaceholder(placeholder)[1]\n }\n autoComplete={autoComplete}\n styleType=\"no-border\"\n className={clsx(\n styles.input,\n styles.floorInput,\n styles.unitInput\n )}\n />\n <VisuallyHidden aria-hidden id={unitLabelId}>\n Enter unit number\n </VisuallyHidden>\n {/** Live message for AT reader to read with the combination of prefix and current value for both floor input and unit input */}\n <VisuallyHidden id={liveMessageId} aria-live=\"polite\">\n {liveMessage}\n </VisuallyHidden>\n </>\n );\n\n const renderReadOnly = (displayValue: string) => {\n const displayValueArr = displayValue.split(\"-\");\n const liveMessageForReadOnly = [\n formatPhraseWithPrefix(\"Hash\", displayValueArr[0]),\n formatPhraseWithPrefix(\"Dash\", displayValueArr[1]),\n ].join(\" \");\n\n return (\n <div\n data-testid=\"readonly-display\"\n tabIndex={0}\n role=\"textbox\"\n aria-readonly\n aria-labelledby={ariaLabelledBy}\n aria-describedby={ariaDescribedBy}\n aria-invalid={ariaInvalid}\n className={styles.readOnlyContainer}\n >\n <Typography.BodyBL>{displayValueArr[0]}</Typography.BodyBL>\n <Typography.BodyBL className={styles.unitNumberDivider}>\n -\n </Typography.BodyBL>\n <Typography.BodyBL>{displayValueArr[1]}</Typography.BodyBL>\n <VisuallyHidden>{liveMessageForReadOnly}</VisuallyHidden>\n </div>\n );\n };\n\n return (\n <InputWrapper\n {...otherProps}\n ref={nodeRef}\n onClick={handleNodeClick}\n disabled={disabled}\n error={error}\n readOnly={readOnly}\n tabIndex={-1}\n onBlur={handleNodeBlur}\n className={className}\n >\n <div\n data-testid=\"addon\"\n className={clsx(\n styles.hashContainer,\n inputGroupStyles.labelAddonContainer\n )}\n data-disabled={disabled}\n data-read-only={readOnly}\n >\n #\n </div>\n {readOnly && value ? renderReadOnly(value) : renderInputs()}\n </InputWrapper>\n );\n};\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\nconst INVALID_VALUE = \"Invalid unit number\";\n"],"names":["INVALID_VALUE","_a","disabled","error","value","onChange","onBlur","onChangeRaw","onBlurRaw","readOnly","placeholder","autoComplete","ariaLabelledBy","ariaDescribedBy","ariaInvalid","className","otherProps","__rest","floorValue","_setFloorValue","useState","unitValue","_setUnitValue","currentFocus","_setCurrentFocus","internalId","useId","floorLabelId","unitLabelId","liveMessageId","nodeRef","useRef","floorInputRef","unitInputRef","floorValueStateRef","unitValueStateRef","currentFocusStateRef","formatter","toLocaleUpperCase","replace","getNextFloorInputState","useNextInputState","ref","getNextUnitInputState","liveMessage","useMemo","msg","floorMsg","formatPhraseWithPrefix","unitMsg","join","setFloorValue","data","current","setUnitValue","setCurrentFocus","useEffect","length","focus","updateValues","handleFocus","event","target","name","select","handleBlur","targetName","targetValue","formattedInput","formatInput","performOnChangeHandler","handleChange","nextInputState","nextValue","updateCaretPosition","handleKeyDown","code","key","test","StringHelper","padValue","undefined","valueArr","split","floor","unit","changeValue","field","values","returnValue","getFormattedValue","performOnBlurHandler","every","getPlaceholder","prefix","v","Array","from","_jsxs","InputWrapper","Object","assign","onClick","tabIndex","contains","relatedTarget","children","_jsx","clsx","styles.hashContainer","inputGroupStyles.labelAddonContainer","displayValue","displayValueArr","liveMessageForReadOnly","role","styles.readOnlyContainer","Typography","BodyBL","styles.unitNumberDivider","VisuallyHidden","renderReadOnly","_Fragment","Input","maxLength","onFocus","type","pattern","concatIds","styleType","styles.input","styles.floorInput","id","styles.unitNumberDividerInactive","onKeyDown","styles.unitInput"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"is-styled-components.js","sources":["../../../src/util/is-styled-components.ts"],"sourcesContent":["/*\n Extracted isStyledComponent function from styled-components repo \n Refer to the original implementation here:\n https://github.com/styled-components/styled-components/blob/main/packages/styled-components/src/utils/isStyledComponent.ts\n*/\n\ntype StyledComponentBrand = { readonly _sc: symbol };\n\nexport default function isStyledComponent(\n target: any\n): target is StyledComponentBrand {\n return (\n target != null &&\n (typeof target === \"function\" || typeof target === \"object\") &&\n \"styledComponentId\" in target\n );\n}\n"],"names":["target"],"mappings":"oFAQc,
|
|
1
|
+
{"version":3,"file":"is-styled-components.js","sources":["../../../src/util/is-styled-components.ts"],"sourcesContent":["/*\n Extracted isStyledComponent function from styled-components repo \n Refer to the original implementation here:\n https://github.com/styled-components/styled-components/blob/main/packages/styled-components/src/utils/isStyledComponent.ts\n*/\n\ntype StyledComponentBrand = { readonly _sc: symbol };\n\nexport default function isStyledComponent(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n target: any\n): target is StyledComponentBrand {\n return (\n target != null &&\n (typeof target === \"function\" || typeof target === \"object\") &&\n \"styledComponentId\" in target\n );\n}\n"],"names":["target"],"mappings":"oFAQc,SAEVA,GAEA,OACc,MAAVA,IACmB,mBAAXA,GAA2C,iBAAXA,IACxC,sBAAuBA,CAE/B"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";class t{static transformWithSpaces(t,e){const n="(.{"+e+"})";return t.replace(/[^\dA-Z]/gi,"").replace(new RegExp(n,"g"),"$1 ").trim()}static padValue(t,e){if("0"===t)return e?t:t.padStart(2,"0");if(""===t)return t;return parseInt(t).toString().padStart(2,"0")}static shouldTruncateToTwoLines(t,e){const n=Math.floor(e/9);return t.length>=2*n||1===t.split(" ").length&&t.length>n}static truncateOneLine(t,e,n,r,s=8){let
|
|
1
|
+
"use strict";class t{static transformWithSpaces(t,e){const n="(.{"+e+"})";return t.replace(/[^\dA-Z]/gi,"").replace(new RegExp(n,"g"),"$1 ").trim()}static padValue(t,e){if("0"===t)return e?t:t.padStart(2,"0");if(""===t)return t;return parseInt(t).toString().padStart(2,"0")}static shouldTruncateToTwoLines(t,e){const n=Math.floor(e/9);return t.length>=2*n||1===t.split(" ").length&&t.length>n}static truncateOneLine(t,e,n,r,s=8){let i=0;e>n&&(i=Math.floor((e-n)/s));const a=r+i;if(a<t.length){const e=Math.floor(a/2);return t.substring(0,e)+" ... "+t.substring(t.length-e,t.length)}return t}static truncateTwoLines(t,e,n,r=1.2){const s=Math.floor(2*e/(.6*n))*(2/r);if(t.length<=s)return t;const i=Math.floor(.6*s),a=Math.floor(.2*s);return`${t.substring(0,i)}...${t.substring(t.length-a)}`}static getTextWidth(t,e){if("undefined"==typeof document)return 0;const n=(this.getTextWidth.canvas||(this.getTextWidth.canvas=document.createElement("canvas"))).getContext("2d");n.font=e;return n.measureText(t).width}static maskValue(t,e){if(!t)return t;const{maskRange:n,unmaskRange:r,maskChar:s="•",maskRegex:i,maskTransformer:a}=e;if(a)return a(t);if(i)return t.replace(i,s);if(n){const{startIndex:e,endIndex:r}=this.determineStartAndEndIndex(n[0],n[1]);return t.substring(0,e)+s.repeat(t.substring(e,r+1).length)+t.substring(r+1)}if(r){const{startIndex:e,endIndex:n}=this.determineStartAndEndIndex(r[0],r[1]);return s.repeat(t.substring(0,e).length)+t.substring(e,n+1)+s.repeat(t.substring(n+1).length)}return t}static determineStartAndEndIndex(t,e){return t<e?{startIndex:t,endIndex:e}:{startIndex:e,endIndex:t}}static formatOrdinal(t){const e=this.ordinalPluralRules.select(t);return`${t}${this.suffixes.get(e)}`}static getMaskedDescription(t,e,n){if(!t||"masked"!==e||!n)return"";const[r,s]=n,i=r>0,a=s<t.length-1,u=i?t.substring(0,r):"",o=a?t.substring(s+1):"";return i&&a?`Starting with ${u} and ending with ${o}`:i?`Starting with ${u}`:a?`Ending with ${o}`:""}static joinNonEmptyStrings(t,e=", "){return t.filter(Boolean).join(e)}}t.ordinalPluralRules=new Intl.PluralRules("en",{type:"ordinal"}),t.suffixes=new Map([["one","st"],["two","nd"],["few","rd"],["other","th"]]),exports.StringHelper=t;
|
|
2
2
|
//# sourceMappingURL=string-helper.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"string-helper.js","sources":["../../../src/util/string-helper.ts"],"sourcesContent":["// =============================================================================\n// TYPINGS\n// =============================================================================\ninterface MaskValueOptions {\n maskRange?: number[] | undefined;\n unmaskRange?: number[] | undefined;\n maskRegex?: RegExp | undefined;\n maskTransformer?: ((value: string) => string) | undefined;\n maskChar?: string | undefined;\n}\n\n// =============================================================================\n// EXPORTS\n// =============================================================================\nexport class StringHelper {\n public static transformWithSpaces(\n value: string | any,\n groupLength: number\n ): string {\n const regex = \"(.{\" + groupLength + \"})\";\n return value\n .replace(/[^\\dA-Z]/gi, \"\")\n .replace(new RegExp(regex, \"g\"), \"$1 \")\n .trim();\n }\n\n /**\n * Pads single digit values with a zero and trims leading zero for\n * values of length more than 2\n * @param value the string value\n * @param retainSingleZero setting true will allow single zero value\n */\n public static padValue(value: string, retainSingleZero?: boolean): string {\n if (value === \"0\") {\n return retainSingleZero ? value : value.padStart(2, \"0\");\n }\n\n if (value === \"\") {\n return value;\n }\n\n const trimmedValue = parseInt(value);\n return trimmedValue.toString().padStart(2, \"0\");\n }\n\n /**\n * Checks if the text is required to be truncated and broken into two lines based\n * on the available width of container. **Note: This is relevant to font size 18px or 1.125rem only **\n * @param text input text\n * @param widthOfElement element width in px\n */\n public static shouldTruncateToTwoLines(\n text: string,\n widthOfElement: number\n ): boolean {\n // This is arbitrary and based on tests to derive that every 9px increment ~= 1 char length.\n // But this applies to font size 18px or 1.125rem only\n const estimatedCharOnLine = Math.floor(widthOfElement / 9);\n return (\n text.length >= estimatedCharOnLine * 2 ||\n (text.split(\" \").length === 1 && text.length > estimatedCharOnLine)\n );\n }\n\n /**\n * Performs a truncation by adding ellipsis in the middle of the text.\n * @param text input text\n * @param widthOfElement the dynamic width of the container in px\n * @param minDivSize the minimum container size (using size on mobileS viewport as baseline)\n * @param baselineCharLength the baseline amount of characters to be displayed before truncation kicks. This\n * will be increased if there is more space available (derived from `widthOfElement`)\n * @param incrementFactor the size (in px).\n */\n public static truncateOneLine(\n text: string,\n widthOfElement: number,\n minDivSize: number,\n baselineCharLength: number,\n incrementFactor = 8 // Arbitrary based on tests and applies to font size of 18px or 1.125rem\n ): string {\n let additionalCharAllowed = 0;\n if (widthOfElement > minDivSize) {\n additionalCharAllowed = Math.floor(\n (widthOfElement - minDivSize) / incrementFactor\n );\n }\n const allowedCharLength = baselineCharLength + additionalCharAllowed;\n if (allowedCharLength < text.length) {\n const thresholdIndex = Math.floor(allowedCharLength / 2); // We will cut it halfway to derive x num of characters\n return (\n text.substring(0, thresholdIndex) + // Use the first x\n \" ... \" +\n text.substring(text.length - thresholdIndex, text.length) // Use the last x\n );\n }\n\n return text;\n }\n\n /**\n * Performs a truncation by adding ellipsis in the middle of the text.\n * @param text input text\n * @param widthOfElement the dynamic width of the container in px\n * @param fontSize the fontsize of the text\n * @param lineHeight the line height of the text\n */\n public static truncateTwoLines(\n text: string,\n widthOfElement: number,\n fontSize: number,\n lineHeight = 1.2\n ): string {\n // Estimate characters per line based on width and font size\n const charsPerLine = Math.floor(\n (widthOfElement * 2) / (fontSize * 0.6) // 0.6 is an approximation for average char width\n );\n\n // Calculate total characters allowed in two lines\n const totalCharsAllowed = charsPerLine * (2 / lineHeight);\n\n if (text.length <= totalCharsAllowed) {\n return text; // No truncation needed\n }\n\n // Calculate the number of characters to keep\n const keepFromStart = Math.floor(totalCharsAllowed * 0.6); // Keep 60% from start\n const keepFromEnd = Math.floor(totalCharsAllowed * 0.2); // Keep 20% from end\n\n // Truncate the text\n const start = text.substring(0, keepFromStart);\n const end = text.substring(text.length - keepFromEnd);\n\n return `${start}...${end}`;\n }\n\n /**\n * Returns the width of the text in the specified font\n * https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393\n */\n public static getTextWidth(text: string, font: string): number {\n if (typeof document === \"undefined\") {\n return 0;\n }\n\n // create a reusable canvas object for better performance\n const canvas =\n (StringHelper.getTextWidth as any).canvas ||\n ((StringHelper.getTextWidth as any).canvas =\n document.createElement(\"canvas\"));\n const context = canvas.getContext(\"2d\");\n context.font = font;\n\n const metrics = context.measureText(text);\n return metrics.width;\n }\n\n public static maskValue(value: string, options: MaskValueOptions): string {\n if (!value) {\n return value;\n }\n\n const {\n maskRange,\n unmaskRange,\n maskChar = \"•\",\n maskRegex,\n maskTransformer,\n } = options;\n\n if (maskTransformer) {\n return maskTransformer(value);\n } else if (maskRegex) {\n return value.replace(maskRegex, maskChar);\n } else if (maskRange) {\n const { startIndex, endIndex } =\n StringHelper.determineStartAndEndIndex(\n maskRange[0],\n maskRange[1]\n );\n return (\n value.substring(0, startIndex) +\n maskChar.repeat(\n value.substring(startIndex, endIndex + 1).length\n ) +\n value.substring(endIndex + 1)\n );\n } else if (unmaskRange) {\n const { startIndex, endIndex } =\n StringHelper.determineStartAndEndIndex(\n unmaskRange[0],\n unmaskRange[1]\n );\n return (\n maskChar.repeat(value.substring(0, startIndex).length) +\n value.substring(startIndex, endIndex + 1) +\n maskChar.repeat(value.substring(endIndex + 1).length)\n );\n }\n\n return value;\n }\n\n private static determineStartAndEndIndex(index0: number, index1: number) {\n return index0 < index1\n ? { startIndex: index0, endIndex: index1 }\n : { startIndex: index1, endIndex: index0 };\n }\n\n private static ordinalPluralRules = new Intl.PluralRules(\"en\", {\n type: \"ordinal\",\n });\n\n private static suffixes = new Map([\n [\"one\", \"st\"],\n [\"two\", \"nd\"],\n [\"few\", \"rd\"],\n [\"other\", \"th\"],\n ]);\n\n public static formatOrdinal(n: number) {\n const rule = StringHelper.ordinalPluralRules.select(n);\n const suffix = StringHelper.suffixes.get(rule);\n return `${n}${suffix}`;\n }\n\n public static getMaskedDescription(\n value: string,\n displayMaskState: \"masked\" | \"unmasked\",\n maskRange?: number[]\n ): string {\n if (!value || displayMaskState !== \"masked\" || !maskRange) return \"\";\n\n const [startIndex, endIndex] = maskRange;\n const hasStart = startIndex > 0;\n const hasEnd = endIndex < value.length - 1;\n\n const start = hasStart ? value.substring(0, startIndex) : \"\";\n const end = hasEnd ? value.substring(endIndex + 1) : \"\";\n\n if (hasStart && hasEnd)\n return `Starting with ${start} and ending with ${end}`;\n if (hasStart) return `Starting with ${start}`;\n if (hasEnd) return `Ending with ${end}`;\n\n return \"\";\n }\n\n public static joinNonEmptyStrings(\n strings: (string | undefined | null)[],\n delimiter = \", \"\n ): string {\n return strings.filter(Boolean).join(delimiter);\n }\n}\n"],"names":["StringHelper","transformWithSpaces","value","groupLength","regex","replace","RegExp","trim","padValue","retainSingleZero","padStart","parseInt","toString","shouldTruncateToTwoLines","text","widthOfElement","estimatedCharOnLine","Math","floor","length","split","truncateOneLine","minDivSize","baselineCharLength","incrementFactor","additionalCharAllowed","allowedCharLength","thresholdIndex","substring","truncateTwoLines","fontSize","lineHeight","totalCharsAllowed","keepFromStart","keepFromEnd","getTextWidth","font","document","context","canvas","createElement","getContext","measureText","width","maskValue","options","maskRange","unmaskRange","maskChar","maskRegex","maskTransformer","startIndex","endIndex","determineStartAndEndIndex","repeat","index0","index1","formatOrdinal","n","rule","ordinalPluralRules","select","suffixes","get","getMaskedDescription","displayMaskState","hasStart","hasEnd","start","end","joinNonEmptyStrings","strings","delimiter","filter","Boolean","join","Intl","PluralRules","type","Map"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"string-helper.js","sources":["../../../src/util/string-helper.ts"],"sourcesContent":["// =============================================================================\n// TYPINGS\n// =============================================================================\ninterface MaskValueOptions {\n maskRange?: number[] | undefined;\n unmaskRange?: number[] | undefined;\n maskRegex?: RegExp | undefined;\n maskTransformer?: ((value: string) => string) | undefined;\n maskChar?: string | undefined;\n}\n\n// =============================================================================\n// EXPORTS\n// =============================================================================\nexport class StringHelper {\n public static transformWithSpaces(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n value: string | any,\n groupLength: number\n ): string {\n const regex = \"(.{\" + groupLength + \"})\";\n return value\n .replace(/[^\\dA-Z]/gi, \"\")\n .replace(new RegExp(regex, \"g\"), \"$1 \")\n .trim();\n }\n\n /**\n * Pads single digit values with a zero and trims leading zero for\n * values of length more than 2\n * @param value the string value\n * @param retainSingleZero setting true will allow single zero value\n */\n public static padValue(value: string, retainSingleZero?: boolean): string {\n if (value === \"0\") {\n return retainSingleZero ? value : value.padStart(2, \"0\");\n }\n\n if (value === \"\") {\n return value;\n }\n\n const trimmedValue = parseInt(value);\n return trimmedValue.toString().padStart(2, \"0\");\n }\n\n /**\n * Checks if the text is required to be truncated and broken into two lines based\n * on the available width of container. **Note: This is relevant to font size 18px or 1.125rem only **\n * @param text input text\n * @param widthOfElement element width in px\n */\n public static shouldTruncateToTwoLines(\n text: string,\n widthOfElement: number\n ): boolean {\n // This is arbitrary and based on tests to derive that every 9px increment ~= 1 char length.\n // But this applies to font size 18px or 1.125rem only\n const estimatedCharOnLine = Math.floor(widthOfElement / 9);\n return (\n text.length >= estimatedCharOnLine * 2 ||\n (text.split(\" \").length === 1 && text.length > estimatedCharOnLine)\n );\n }\n\n /**\n * Performs a truncation by adding ellipsis in the middle of the text.\n * @param text input text\n * @param widthOfElement the dynamic width of the container in px\n * @param minDivSize the minimum container size (using size on mobileS viewport as baseline)\n * @param baselineCharLength the baseline amount of characters to be displayed before truncation kicks. This\n * will be increased if there is more space available (derived from `widthOfElement`)\n * @param incrementFactor the size (in px).\n */\n public static truncateOneLine(\n text: string,\n widthOfElement: number,\n minDivSize: number,\n baselineCharLength: number,\n incrementFactor = 8 // Arbitrary based on tests and applies to font size of 18px or 1.125rem\n ): string {\n let additionalCharAllowed = 0;\n if (widthOfElement > minDivSize) {\n additionalCharAllowed = Math.floor(\n (widthOfElement - minDivSize) / incrementFactor\n );\n }\n const allowedCharLength = baselineCharLength + additionalCharAllowed;\n if (allowedCharLength < text.length) {\n const thresholdIndex = Math.floor(allowedCharLength / 2); // We will cut it halfway to derive x num of characters\n return (\n text.substring(0, thresholdIndex) + // Use the first x\n \" ... \" +\n text.substring(text.length - thresholdIndex, text.length) // Use the last x\n );\n }\n\n return text;\n }\n\n /**\n * Performs a truncation by adding ellipsis in the middle of the text.\n * @param text input text\n * @param widthOfElement the dynamic width of the container in px\n * @param fontSize the fontsize of the text\n * @param lineHeight the line height of the text\n */\n public static truncateTwoLines(\n text: string,\n widthOfElement: number,\n fontSize: number,\n lineHeight = 1.2\n ): string {\n // Estimate characters per line based on width and font size\n const charsPerLine = Math.floor(\n (widthOfElement * 2) / (fontSize * 0.6) // 0.6 is an approximation for average char width\n );\n\n // Calculate total characters allowed in two lines\n const totalCharsAllowed = charsPerLine * (2 / lineHeight);\n\n if (text.length <= totalCharsAllowed) {\n return text; // No truncation needed\n }\n\n // Calculate the number of characters to keep\n const keepFromStart = Math.floor(totalCharsAllowed * 0.6); // Keep 60% from start\n const keepFromEnd = Math.floor(totalCharsAllowed * 0.2); // Keep 20% from end\n\n // Truncate the text\n const start = text.substring(0, keepFromStart);\n const end = text.substring(text.length - keepFromEnd);\n\n return `${start}...${end}`;\n }\n\n /**\n * Returns the width of the text in the specified font\n * https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393\n */\n public static getTextWidth(text: string, font: string): number {\n if (typeof document === \"undefined\") {\n return 0;\n }\n\n // create a reusable canvas object for better performance\n const canvas =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this.getTextWidth as any).canvas ||\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ((this.getTextWidth as any).canvas =\n document.createElement(\"canvas\"));\n const context = canvas.getContext(\"2d\");\n context.font = font;\n\n const metrics = context.measureText(text);\n return metrics.width;\n }\n\n public static maskValue(value: string, options: MaskValueOptions): string {\n if (!value) {\n return value;\n }\n\n const {\n maskRange,\n unmaskRange,\n maskChar = \"•\",\n maskRegex,\n maskTransformer,\n } = options;\n\n if (maskTransformer) {\n return maskTransformer(value);\n } else if (maskRegex) {\n return value.replace(maskRegex, maskChar);\n } else if (maskRange) {\n const { startIndex, endIndex } = this.determineStartAndEndIndex(\n maskRange[0],\n maskRange[1]\n );\n return (\n value.substring(0, startIndex) +\n maskChar.repeat(\n value.substring(startIndex, endIndex + 1).length\n ) +\n value.substring(endIndex + 1)\n );\n } else if (unmaskRange) {\n const { startIndex, endIndex } = this.determineStartAndEndIndex(\n unmaskRange[0],\n unmaskRange[1]\n );\n return (\n maskChar.repeat(value.substring(0, startIndex).length) +\n value.substring(startIndex, endIndex + 1) +\n maskChar.repeat(value.substring(endIndex + 1).length)\n );\n }\n\n return value;\n }\n\n private static determineStartAndEndIndex(index0: number, index1: number) {\n return index0 < index1\n ? { startIndex: index0, endIndex: index1 }\n : { startIndex: index1, endIndex: index0 };\n }\n\n private static readonly ordinalPluralRules = new Intl.PluralRules(\"en\", {\n type: \"ordinal\",\n });\n\n private static readonly suffixes = new Map([\n [\"one\", \"st\"],\n [\"two\", \"nd\"],\n [\"few\", \"rd\"],\n [\"other\", \"th\"],\n ]);\n\n public static formatOrdinal(n: number) {\n const rule = this.ordinalPluralRules.select(n);\n const suffix = this.suffixes.get(rule);\n return `${n}${suffix}`;\n }\n\n public static getMaskedDescription(\n value: string,\n displayMaskState: \"masked\" | \"unmasked\",\n maskRange?: number[]\n ): string {\n if (!value || displayMaskState !== \"masked\" || !maskRange) return \"\";\n\n const [startIndex, endIndex] = maskRange;\n const hasStart = startIndex > 0;\n const hasEnd = endIndex < value.length - 1;\n\n const start = hasStart ? value.substring(0, startIndex) : \"\";\n const end = hasEnd ? value.substring(endIndex + 1) : \"\";\n\n if (hasStart && hasEnd)\n return `Starting with ${start} and ending with ${end}`;\n if (hasStart) return `Starting with ${start}`;\n if (hasEnd) return `Ending with ${end}`;\n\n return \"\";\n }\n\n public static joinNonEmptyStrings(\n strings: (string | undefined | null)[],\n delimiter = \", \"\n ): string {\n return strings.filter(Boolean).join(delimiter);\n }\n}\n"],"names":["StringHelper","transformWithSpaces","value","groupLength","regex","replace","RegExp","trim","padValue","retainSingleZero","padStart","parseInt","toString","shouldTruncateToTwoLines","text","widthOfElement","estimatedCharOnLine","Math","floor","length","split","truncateOneLine","minDivSize","baselineCharLength","incrementFactor","additionalCharAllowed","allowedCharLength","thresholdIndex","substring","truncateTwoLines","fontSize","lineHeight","totalCharsAllowed","keepFromStart","keepFromEnd","getTextWidth","font","document","context","this","canvas","createElement","getContext","measureText","width","maskValue","options","maskRange","unmaskRange","maskChar","maskRegex","maskTransformer","startIndex","endIndex","determineStartAndEndIndex","repeat","index0","index1","formatOrdinal","n","rule","ordinalPluralRules","select","suffixes","get","getMaskedDescription","displayMaskState","hasStart","hasEnd","start","end","joinNonEmptyStrings","strings","delimiter","filter","Boolean","join","Intl","PluralRules","type","Map"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-event-listener.js","sources":["../../../src/util/use-event-listener.tsx"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\ntype Target = \"window\" | \"document\" | HTMLElement | null;\ntype Element = Window | Document | HTMLElement | null;\ntype EventHandler<K extends keyof WindowEventMap> = (\n event: WindowEventMap[K]\n) => void;\n\nexport const useEventListener = <K extends keyof WindowEventMap>(\n eventName: K,\n handler: EventHandler<K>,\n target: Target = \"window\",\n options?: AddEventListenerOptions | boolean\n) => {\n // Create a ref that stores handler\n const savedHandler = useRef<EventHandler<K>>();\n // Update ref.current value if handler changes.\n // This allows our effect below to always get latest handler ...\n useEffect(() => {\n savedHandler.current = handler;\n }, [handler]);\n\n useEffect(\n () => {\n let element: Element;\n switch (target) {\n case \"window\":\n element = window;\n break;\n case \"document\":\n element = document;\n break;\n default:\n element = target;\n }\n\n // Make sure element supports addEventListener\n if (!element || !element.addEventListener) return;\n\n // Create event listener that calls handler function stored in ref\n const eventListener = ((event: WindowEventMap[K]) =>\n savedHandler.current?.(event)) as EventListener;\n // Add event listener\n element.addEventListener(eventName, eventListener, options);\n // Remove event listener on cleanup\n return () => {\n element?.removeEventListener(eventName, eventListener, options);\n };\n },\n [eventName, target] // Re-run if eventName or element changes\n );\n};\n"],"names":["eventName","handler","target","options","savedHandler","useRef","useEffect","current","element","window","document","addEventListener","eventListener","event","_a","call","removeEventListener"],"mappings":"6DAQgC,CAC5BA,EACAC,EACAC,EAAiB,SACjBC,KAGA,MAAMC,EAAeC,EAAAA,SAGrBC,EAAAA,WAAU,KACNF,EAAaG,QAAUN,CAAO,GAC/B,CAACA,IAEJK,EAAAA,WACI,KACI,IAAIE,EACJ,OAAQN,GACJ,IAAK,SACDM,EAAUC,OACV,MACJ,IAAK,WACDD,EAAUE,SACV,MACJ,QACIF,EAAUN,EAIlB,IAAKM,IAAYA,EAAQG,iBAAkB,OAG3C,MAAMC,EAAkBC,IAA4B,IAAAC,EAChD,OAAoB,UAApBV,EAAaG,eAAO,IAAAO,OAAA,EAAAA,EAAAC,KAAAX,EAAGS,EAAM,EAIjC,OAFAL,EAAQG,iBAAiBX,EAAWY,EAAeT,GAE5C,KACHK,SAAAA,EAASQ,oBAAoBhB,EAAWY,EAAeT,EAAQ,CAClE,
|
|
1
|
+
{"version":3,"file":"use-event-listener.js","sources":["../../../src/util/use-event-listener.tsx"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\ntype Target = \"window\" | \"document\" | HTMLElement | null;\ntype Element = Window | Document | HTMLElement | null;\ntype EventHandler<K extends keyof WindowEventMap> = (\n event: WindowEventMap[K]\n) => void;\n\nexport const useEventListener = <K extends keyof WindowEventMap>(\n eventName: K,\n handler: EventHandler<K>,\n target: Target = \"window\",\n options?: AddEventListenerOptions | boolean\n) => {\n // Create a ref that stores handler\n const savedHandler = useRef<EventHandler<K>>();\n // Update ref.current value if handler changes.\n // This allows our effect below to always get latest handler ...\n useEffect(() => {\n savedHandler.current = handler;\n }, [handler]);\n\n useEffect(\n () => {\n let element: Element;\n switch (target) {\n case \"window\":\n element = window;\n break;\n case \"document\":\n element = document;\n break;\n default:\n element = target;\n }\n\n // Make sure element supports addEventListener\n if (!element || !element.addEventListener) return;\n\n // Create event listener that calls handler function stored in ref\n const eventListener = ((event: WindowEventMap[K]) =>\n savedHandler.current?.(event)) as EventListener;\n // Add event listener\n element.addEventListener(eventName, eventListener, options);\n // Remove event listener on cleanup\n return () => {\n element?.removeEventListener(eventName, eventListener, options);\n };\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [eventName, target] // Re-run if eventName or element changes\n );\n};\n"],"names":["eventName","handler","target","options","savedHandler","useRef","useEffect","current","element","window","document","addEventListener","eventListener","event","_a","call","removeEventListener"],"mappings":"6DAQgC,CAC5BA,EACAC,EACAC,EAAiB,SACjBC,KAGA,MAAMC,EAAeC,EAAAA,SAGrBC,EAAAA,WAAU,KACNF,EAAaG,QAAUN,CAAO,GAC/B,CAACA,IAEJK,EAAAA,WACI,KACI,IAAIE,EACJ,OAAQN,GACJ,IAAK,SACDM,EAAUC,OACV,MACJ,IAAK,WACDD,EAAUE,SACV,MACJ,QACIF,EAAUN,EAIlB,IAAKM,IAAYA,EAAQG,iBAAkB,OAG3C,MAAMC,EAAkBC,IAA4B,IAAAC,EAChD,OAAoB,UAApBV,EAAaG,eAAO,IAAAO,OAAA,EAAAA,EAAAC,KAAAX,EAAGS,EAAM,EAIjC,OAFAL,EAAQG,iBAAiBX,EAAWY,EAAeT,GAE5C,KACHK,SAAAA,EAASQ,oBAAoBhB,EAAWY,EAAeT,EAAQ,CAClE,GAGL,CAACH,EAAWE,GACf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-state-actions.js","sources":["../../../src/util/use-state-actions.ts"],"sourcesContent":["import { useReducer } from \"react\";\n\nimport type { RequiredKeys } from \"./utility-types\";\n\ntype ReducerObj<State> = {\n [action: string]: (state: State, payload: any) => State;\n};\n\ntype Actions<State, Reducers extends ReducerObj<State>> = {\n [T in keyof Reducers]: Reducers[T] extends (state: State) => State\n ? () => void\n : Reducers[T] extends (\n state: State,\n payload: Parameters<Reducers[T]>[1]\n ) => State\n ? (\n ...args: RequiredKeys<Parameters<Reducers[T]>[1]> extends never\n ? [payload?: Parameters<Reducers[T]>[1]]\n : [payload: Parameters<Reducers[T]>[1]]\n ) => void\n : never;\n};\n\ninterface StateActions<State, Reducers> {\n /** reducer initial state */\n initialState: State;\n /** specify functions that return the new state. must be immutable */\n reducers: Reducers;\n /** identify the reducer for debugging */\n name?: string;\n /** enable debugging */\n debug?: boolean;\n}\n\n/**\n * An interface around `React.useReducer` to limit boilerplate. Each action\n * is a function that takes in state and payload, then returns the updated state\n * immutably. Inspired by redux toolkit's createSlice api.\n *\n * Example:\n * ```\n * const initialState = { count: 0 };\n * const reducers = {\n * increment: (state) => ({ ...state, count: state.count + 1 }),\n * multiplyBy: (state, multiplier: number) => ({\n * ...state,\n * count: state.count * multiplier,\n * }),\n * };\n * const [state, actions] = useStateActions({ initialState, reducers });\n * actions.increment();\n * actions.multiplyBy(2);\n * ```\n *\n */\nexport const useStateActions = <State, Reducers extends ReducerObj<State>>({\n initialState,\n reducers,\n name,\n debug,\n}: StateActions<State, Reducers>): [State, Actions<State, Reducers>] => {\n const reducer = (state: State, action: { type: string; payload: any }) => {\n if (reducers[action.type]) {\n return reducers[action.type](state, action.payload);\n }\n return state;\n };\n const [state, dispatch] = useReducer(reducer, initialState);\n const actions = Object.fromEntries(\n Object.keys(reducers).map((actionType) => {\n return [\n actionType,\n (payload: unknown) => {\n if (debug) {\n logAction(name, actionType, payload);\n }\n dispatch({\n type: actionType,\n payload,\n });\n },\n ];\n })\n );\n return [state, actions] as [State, Actions<State, Reducers>];\n};\n\nconst logAction = (\n name: string | undefined,\n type: string,\n payload: unknown\n) => {\n const args = [];\n if (name) args.push(`[${name}]`);\n args.push(type, payload);\n\n console.log(...args);\n};\n"],"names":["logAction","name","type","payload","args","push","console","log","initialState","reducers","debug","state","dispatch","useReducer","action","Object","fromEntries","keys","map","actionType"],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-state-actions.js","sources":["../../../src/util/use-state-actions.ts"],"sourcesContent":["import { useReducer } from \"react\";\n\nimport type { RequiredKeys } from \"./utility-types\";\n\ntype ReducerObj<State> = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [action: string]: (state: State, payload: any) => State;\n};\n\ntype Actions<State, Reducers extends ReducerObj<State>> = {\n [T in keyof Reducers]: Reducers[T] extends (state: State) => State\n ? () => void\n : Reducers[T] extends (\n state: State,\n payload: Parameters<Reducers[T]>[1]\n ) => State\n ? (\n ...args: RequiredKeys<Parameters<Reducers[T]>[1]> extends never\n ? [payload?: Parameters<Reducers[T]>[1]]\n : [payload: Parameters<Reducers[T]>[1]]\n ) => void\n : never;\n};\n\ninterface StateActions<State, Reducers> {\n /** reducer initial state */\n initialState: State;\n /** specify functions that return the new state. must be immutable */\n reducers: Reducers;\n /** identify the reducer for debugging */\n name?: string;\n /** enable debugging */\n debug?: boolean;\n}\n\n/**\n * An interface around `React.useReducer` to limit boilerplate. Each action\n * is a function that takes in state and payload, then returns the updated state\n * immutably. Inspired by redux toolkit's createSlice api.\n *\n * Example:\n * ```\n * const initialState = { count: 0 };\n * const reducers = {\n * increment: (state) => ({ ...state, count: state.count + 1 }),\n * multiplyBy: (state, multiplier: number) => ({\n * ...state,\n * count: state.count * multiplier,\n * }),\n * };\n * const [state, actions] = useStateActions({ initialState, reducers });\n * actions.increment();\n * actions.multiplyBy(2);\n * ```\n *\n */\nexport const useStateActions = <State, Reducers extends ReducerObj<State>>({\n initialState,\n reducers,\n name,\n debug,\n}: StateActions<State, Reducers>): [State, Actions<State, Reducers>] => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const reducer = (state: State, action: { type: string; payload: any }) => {\n if (reducers[action.type]) {\n return reducers[action.type](state, action.payload);\n }\n return state;\n };\n const [state, dispatch] = useReducer(reducer, initialState);\n const actions = Object.fromEntries(\n Object.keys(reducers).map((actionType) => {\n return [\n actionType,\n (payload: unknown) => {\n if (debug) {\n logAction(name, actionType, payload);\n }\n dispatch({\n type: actionType,\n payload,\n });\n },\n ];\n })\n );\n return [state, actions] as [State, Actions<State, Reducers>];\n};\n\nconst logAction = (\n name: string | undefined,\n type: string,\n payload: unknown\n) => {\n const args = [];\n if (name) args.push(`[${name}]`);\n args.push(type, payload);\n\n console.log(...args);\n};\n"],"names":["logAction","name","type","payload","args","push","console","log","initialState","reducers","debug","state","dispatch","useReducer","action","Object","fromEntries","keys","map","actionType"],"mappings":"oCAwDO,MAiCDA,EAAY,CACdC,EACAC,EACAC,KAEA,MAAMC,EAAO,GACHA,EAAKC,KAAK,IAAIJ,MACxBG,EAAKC,KAAKH,EAAMC,GAEhBG,QAAQC,OAAOH,EAAK,0BA1CO,EAC3BI,eACAC,WACAR,OACAS,YAGA,MAMOC,EAAOC,GAAYC,EAAAA,YANV,CAACF,EAAcG,IACvBL,EAASK,EAAOZ,MACTO,EAASK,EAAOZ,MAAMS,EAAOG,EAAOX,SAExCQ,GAEmCH,GAiB9C,MAAO,CAACG,EAhBQI,OAAOC,YACnBD,OAAOE,KAAKR,GAAUS,KAAKC,GAChB,CACHA,EACChB,IACOO,GACAV,EAAUC,EAAMkB,EAAYhB,GAEhCS,EAAS,CACLV,KAAMiB,EACNhB,WACF,MAK0C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"countdown-timer.js","sources":["../../src/countdown-timer/countdown-timer.tsx"],"sourcesContent":["import { ClockIcon } from \"@lifesg/react-icons\";\nimport clsx from \"clsx\";\nimport throttle from \"lodash/throttle\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useInView } from \"react-intersection-observer\";\n\nimport { inertValue, VisuallyHidden } from \"../shared/accessibility\";\nimport {\n formatUnitValue,\n useApplyStyle,\n useMaxWidthMediaQuery,\n} from \"../theme\";\nimport { useIsMounted } from \"../util\";\nimport { TimeHelper } from \"../util/time-helper\";\nimport * as styles from \"./countdown-timer.styles\";\nimport type { CountdownTimerProps } from \"./types\";\nimport { useTimer } from \"./use-timer\";\n\n// @catalog\n/**\n * Displays a live countdown of time remaining until a deadline.\n *\n * Use when a time-limited interaction — such as a session timeout or a timed\n * form — needs a persistent visual reminder.\n */\nexport const CountdownTimer = ({\n className,\n align = \"right\",\n timer,\n timestamp,\n notifyTimer,\n offset,\n mobileOffset,\n show,\n fixed = true,\n reminderInterval = 120,\n \"data-testid\": testId,\n onFinish,\n onNotify,\n onTick,\n ...otherProps\n}: CountdownTimerProps) => {\n // =============================================================================\n // CONST, STATE, REF\n // =============================================================================\n\n const wrapperRef = useRef<HTMLDivElement>(null);\n const fixedCountdownRef = useRef<HTMLDivElement>(null);\n\n const isNotified = useRef<boolean>(false);\n const [offsetY, setOffsetY] = useState<number>(0);\n const [clientRectRight, setClientRectRight] = useState<number>(0);\n const [clientRectX, setClientRectX] = useState<number>(0);\n const [isPlaying, setIsPlaying] = useState(false);\n const [announcement, setAnnouncement] = useState<string>(\"\");\n\n const isMounted = useIsMounted();\n const [remainingSeconds, initialSeconds] = useTimer(\n timer,\n timestamp,\n isPlaying\n );\n const { ref: stickyRef, inView } = useInView({\n threshold: 1,\n rootMargin: `${offsetY * -1}px 0px 0px 0px`,\n initialInView: true,\n });\n const isVisible = !fixed || inView;\n const warn =\n typeof notifyTimer === \"number\" && remainingSeconds <= notifyTimer;\n\n const isMobile = useMaxWidthMediaQuery(\"sm\");\n\n // =============================================================================\n // EFFECTS\n // =============================================================================\n\n useApplyStyle(fixedCountdownRef, {\n [styles.tokens.fixedCountdown.top]: formatUnitValue(offsetY, \"px\"),\n [styles.tokens.fixedCountdown.left]: formatUnitValue(\n getOffsetLeft(),\n \"px\"\n ),\n [styles.tokens.fixedCountdown.right]: formatUnitValue(\n getOffsetRight(),\n \"px\"\n ),\n });\n\n useEffect(() => {\n setIsPlaying(show);\n }, [show]);\n\n useEffect(() => {\n if (remainingSeconds === 0) {\n performOnFinishHandler();\n } else if (\n typeof notifyTimer === \"number\" &&\n remainingSeconds <= notifyTimer\n ) {\n performOnTickHandler();\n performOnNotifyHandler();\n }\n }, [remainingSeconds]);\n\n useEffect(() => {\n const y = getOffsetY();\n setOffsetY(y);\n }, [isMobile, offset?.top, mobileOffset?.top]);\n\n useEffect(() => {\n if (wrapperRef.current) {\n const handleResize = throttle(handleWindowResize, 300);\n handleWindowResize();\n\n window.addEventListener(\"resize\", handleResize);\n\n return () => {\n window.removeEventListener(\"resize\", handleResize);\n };\n }\n }, [wrapperRef.current]);\n\n // Announcements\n useEffect(() => {\n const timeElapsed = initialSeconds - remainingSeconds;\n if (timeElapsed === 0) return;\n // Polite notification\n if (timeElapsed % reminderInterval === 0) {\n if (\n typeof notifyTimer !== \"number\" ||\n remainingSeconds > notifyTimer\n ) {\n setAnnouncement(getAccessibleTimeText(remainingSeconds));\n }\n }\n\n // Assertive notification\n if (\n typeof notifyTimer === \"number\" &&\n remainingSeconds === notifyTimer\n ) {\n setAnnouncement(getAccessibleTimeText(remainingSeconds));\n }\n }, [remainingSeconds, initialSeconds, reminderInterval, notifyTimer]);\n\n useEffect(() => {\n // reset\n isNotified.current = false;\n }, [initialSeconds, show]);\n\n // =============================================================================\n // EVENT HANDLERS\n // =============================================================================\n const handleWindowResize = () => {\n if (!wrapperRef.current) return;\n\n const clientRect = wrapperRef.current.getBoundingClientRect();\n\n setClientRectX(clientRect.x);\n setClientRectRight(clientRect.right);\n };\n\n const handleOnFocus = () => {\n const focusMessage = getAccessibleTimeText(remainingSeconds);\n setAnnouncement(focusMessage);\n };\n\n // =============================================================================\n // HELPER FUNCTIONS\n // =============================================================================\n\n const performOnTickHandler = () => {\n if (onTick) {\n onTick(remainingSeconds);\n }\n };\n\n const performOnNotifyHandler = () => {\n if (onNotify && !isNotified.current) {\n isNotified.current = true;\n onNotify();\n }\n };\n\n const performOnFinishHandler = () => {\n setIsPlaying(false);\n\n if (onFinish) onFinish();\n };\n\n function getOffsetY() {\n const desktopTop = offset?.top ?? 168;\n const mobileTop = mobileOffset?.top ?? 80;\n const offsetY = isMobile ? mobileTop : desktopTop;\n\n return offsetY;\n }\n\n function getOffsetLeft() {\n if (!isMounted || align !== \"left\") return undefined;\n return offset?.left ?? clientRectX;\n }\n\n function getOffsetRight() {\n if (!isMounted || align !== \"right\") return undefined;\n return (\n offset?.right ??\n Math.floor(document.body.clientWidth - clientRectRight)\n );\n }\n\n const getAccessibleTimeText = (seconds: number): string => {\n const { minutes, seconds: secs } = TimeHelper.toMinutesSeconds(seconds);\n\n const minuteText = minutes === 1 ? \"minute\" : \"minutes\";\n const secondText = secs === 1 ? \"second\" : \"seconds\";\n\n if (minutes > 0 && secs > 0) {\n return `Time left: ${minutes} ${minuteText} and ${secs} ${secondText}`;\n } else if (minutes > 0) {\n return `Time left: ${minutes} ${minuteText}`;\n } else {\n return `Time left: ${secs} ${secondText}`;\n }\n };\n // =============================================================================\n // RENDER FUNCTION\n // =============================================================================\n\n const renderPeriodicAndWarningAnnouncement = () => (\n <VisuallyHidden\n aria-live={\n typeof notifyTimer === \"number\" &&\n remainingSeconds <= notifyTimer\n ? \"assertive\"\n : \"polite\"\n }\n aria-atomic=\"true\"\n >\n {announcement}\n </VisuallyHidden>\n );\n\n const renderTimer = () => {\n const { minutes, seconds } =\n TimeHelper.toMinutesSeconds(remainingSeconds);\n const m = minutes !== 1 ? \"mins\" : \"min\";\n const s = seconds !== 1 ? \"secs\" : \"sec\";\n return (\n <>\n <ClockIcon className={styles.timerIcon} data-warn={!!warn} />\n <div className={styles.timeLeft}>Time left:</div>\n <div\n className={styles.timer}\n aria-label={getAccessibleTimeText(remainingSeconds)}\n >\n {minutes} {m} {String(seconds).padStart(2, \"0\")} {s}\n </div>\n {renderPeriodicAndWarningAnnouncement()}\n </>\n );\n };\n\n const renderInlineCountdown = () => {\n return (\n <div\n data-testid={testId}\n data-id=\"countdown-wrapper\"\n data-warn={!!warn}\n ref={wrapperRef}\n inert={inertValue(!isVisible)}\n className={clsx(\n styles.countdown,\n styles.countdownInline,\n !isVisible && styles.countdownHidden\n )}\n tabIndex={0}\n role=\"timer\"\n aria-label=\"Countdown timer\"\n onFocus={handleOnFocus}\n >\n {renderTimer()}\n </div>\n );\n };\n\n const renderFixedCountdown = () => {\n return (\n <div\n ref={fixedCountdownRef}\n data-testid={testId}\n data-id=\"fixed-countdown-wrapper\"\n data-warn={!!warn}\n className={clsx(styles.countdown, styles.countdownFixed)}\n tabIndex={0}\n role=\"timer\"\n aria-label=\"Countdown timer\"\n onFocus={handleOnFocus}\n >\n {renderTimer()}\n </div>\n );\n };\n\n if (!isPlaying && remainingSeconds !== 0) return <></>;\n\n return (\n <div className={clsx(styles.wrapper, className)} {...otherProps}>\n <div ref={stickyRef}></div>\n {renderInlineCountdown()}\n {wrapperRef.current && !isVisible && renderFixedCountdown()}\n </div>\n );\n};\n"],"names":["CountdownTimer","_a","className","align","timer","timestamp","notifyTimer","offset","mobileOffset","show","fixed","reminderInterval","testId","onFinish","onNotify","onTick","otherProps","__rest","wrapperRef","useRef","fixedCountdownRef","isNotified","offsetY","setOffsetY","useState","clientRectRight","setClientRectRight","clientRectX","setClientRectX","isPlaying","setIsPlaying","announcement","setAnnouncement","isMounted","useIsMounted","remainingSeconds","initialSeconds","useTimer","ref","stickyRef","inView","useInView","threshold","rootMargin","initialInView","isVisible","warn","isMobile","useMaxWidthMediaQuery","useApplyStyle","styles.tokens","fixedCountdown","top","formatUnitValue","left","getOffsetLeft","right","Math","floor","document","body","clientWidth","getOffsetRight","useEffect","performOnFinishHandler","performOnTickHandler","performOnNotifyHandler","y","desktopTop","mobileTop","_b","getOffsetY","current","handleResize","throttle","handleWindowResize","window","addEventListener","removeEventListener","timeElapsed","getAccessibleTimeText","clientRect","getBoundingClientRect","x","handleOnFocus","focusMessage","seconds","minutes","secs","TimeHelper","toMinutesSeconds","minuteText","secondText","renderTimer","m","s","_jsxs","_Fragment","children","_jsx","ClockIcon","styles.timerIcon","styles.timeLeft","styles.timer","String","padStart","VisuallyHidden","Object","assign","clsx","styles.wrapper","inert","inertValue","styles.countdown","styles.countdownInline","styles.countdownHidden","tabIndex","role","onFocus","styles.countdownFixed"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"countdown-timer.js","sources":["../../src/countdown-timer/countdown-timer.tsx"],"sourcesContent":["import { ClockIcon } from \"@lifesg/react-icons\";\nimport clsx from \"clsx\";\nimport throttle from \"lodash/throttle\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useInView } from \"react-intersection-observer\";\n\nimport { inertValue, VisuallyHidden } from \"../shared/accessibility\";\nimport {\n formatUnitValue,\n useApplyStyle,\n useMaxWidthMediaQuery,\n} from \"../theme\";\nimport { useIsMounted } from \"../util\";\nimport { TimeHelper } from \"../util/time-helper\";\nimport * as styles from \"./countdown-timer.styles\";\nimport type { CountdownTimerProps } from \"./types\";\nimport { useTimer } from \"./use-timer\";\n\n// @catalog\n/**\n * Displays a live countdown of time remaining until a deadline.\n *\n * Use when a time-limited interaction — such as a session timeout or a timed\n * form — needs a persistent visual reminder.\n */\nexport const CountdownTimer = ({\n className,\n align = \"right\",\n timer,\n timestamp,\n notifyTimer,\n offset,\n mobileOffset,\n show,\n fixed = true,\n reminderInterval = 120,\n \"data-testid\": testId,\n onFinish,\n onNotify,\n onTick,\n ...otherProps\n}: CountdownTimerProps) => {\n // =============================================================================\n // CONST, STATE, REF\n // =============================================================================\n\n const wrapperRef = useRef<HTMLDivElement>(null);\n const fixedCountdownRef = useRef<HTMLDivElement>(null);\n\n const isNotified = useRef<boolean>(false);\n const [offsetY, setOffsetY] = useState<number>(0);\n const [clientRectRight, setClientRectRight] = useState<number>(0);\n const [clientRectX, setClientRectX] = useState<number>(0);\n const [isPlaying, setIsPlaying] = useState(false);\n const [announcement, setAnnouncement] = useState<string>(\"\");\n\n const isMounted = useIsMounted();\n const [remainingSeconds, initialSeconds] = useTimer(\n timer,\n timestamp,\n isPlaying\n );\n const { ref: stickyRef, inView } = useInView({\n threshold: 1,\n rootMargin: `${offsetY * -1}px 0px 0px 0px`,\n initialInView: true,\n });\n const isVisible = !fixed || inView;\n const warn =\n typeof notifyTimer === \"number\" && remainingSeconds <= notifyTimer;\n\n const isMobile = useMaxWidthMediaQuery(\"sm\");\n\n // =============================================================================\n // EFFECTS\n // =============================================================================\n\n useApplyStyle(fixedCountdownRef, {\n [styles.tokens.fixedCountdown.top]: formatUnitValue(offsetY, \"px\"),\n [styles.tokens.fixedCountdown.left]: formatUnitValue(\n getOffsetLeft(),\n \"px\"\n ),\n [styles.tokens.fixedCountdown.right]: formatUnitValue(\n getOffsetRight(),\n \"px\"\n ),\n });\n\n useEffect(() => {\n setIsPlaying(show);\n }, [show]);\n\n useEffect(() => {\n if (remainingSeconds === 0) {\n performOnFinishHandler();\n } else if (\n typeof notifyTimer === \"number\" &&\n remainingSeconds <= notifyTimer\n ) {\n performOnTickHandler();\n performOnNotifyHandler();\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [remainingSeconds]);\n\n useEffect(() => {\n const y = getOffsetY();\n setOffsetY(y);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isMobile, offset?.top, mobileOffset?.top]);\n\n useEffect(() => {\n if (wrapperRef.current) {\n const handleResize = throttle(handleWindowResize, 300);\n handleWindowResize();\n\n window.addEventListener(\"resize\", handleResize);\n\n return () => {\n window.removeEventListener(\"resize\", handleResize);\n };\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [wrapperRef.current]);\n\n // Announcements\n useEffect(() => {\n const timeElapsed = initialSeconds - remainingSeconds;\n if (timeElapsed === 0) return;\n // Polite notification\n if (timeElapsed % reminderInterval === 0) {\n if (\n typeof notifyTimer !== \"number\" ||\n remainingSeconds > notifyTimer\n ) {\n setAnnouncement(getAccessibleTimeText(remainingSeconds));\n }\n }\n\n // Assertive notification\n if (\n typeof notifyTimer === \"number\" &&\n remainingSeconds === notifyTimer\n ) {\n setAnnouncement(getAccessibleTimeText(remainingSeconds));\n }\n }, [remainingSeconds, initialSeconds, reminderInterval, notifyTimer]);\n\n useEffect(() => {\n // reset\n isNotified.current = false;\n }, [initialSeconds, show]);\n\n // =============================================================================\n // EVENT HANDLERS\n // =============================================================================\n const handleWindowResize = () => {\n if (!wrapperRef.current) return;\n\n const clientRect = wrapperRef.current.getBoundingClientRect();\n\n setClientRectX(clientRect.x);\n setClientRectRight(clientRect.right);\n };\n\n const handleOnFocus = () => {\n const focusMessage = getAccessibleTimeText(remainingSeconds);\n setAnnouncement(focusMessage);\n };\n\n // =============================================================================\n // HELPER FUNCTIONS\n // =============================================================================\n\n const performOnTickHandler = () => {\n if (onTick) {\n onTick(remainingSeconds);\n }\n };\n\n const performOnNotifyHandler = () => {\n if (onNotify && !isNotified.current) {\n isNotified.current = true;\n onNotify();\n }\n };\n\n const performOnFinishHandler = () => {\n setIsPlaying(false);\n\n if (onFinish) onFinish();\n };\n\n function getOffsetY() {\n const desktopTop = offset?.top ?? 168;\n const mobileTop = mobileOffset?.top ?? 80;\n const offsetY = isMobile ? mobileTop : desktopTop;\n\n return offsetY;\n }\n\n function getOffsetLeft() {\n if (!isMounted || align !== \"left\") return undefined;\n return offset?.left ?? clientRectX;\n }\n\n function getOffsetRight() {\n if (!isMounted || align !== \"right\") return undefined;\n return (\n offset?.right ??\n Math.floor(document.body.clientWidth - clientRectRight)\n );\n }\n\n const getAccessibleTimeText = (seconds: number): string => {\n const { minutes, seconds: secs } = TimeHelper.toMinutesSeconds(seconds);\n\n const minuteText = minutes === 1 ? \"minute\" : \"minutes\";\n const secondText = secs === 1 ? \"second\" : \"seconds\";\n\n if (minutes > 0 && secs > 0) {\n return `Time left: ${minutes} ${minuteText} and ${secs} ${secondText}`;\n } else if (minutes > 0) {\n return `Time left: ${minutes} ${minuteText}`;\n } else {\n return `Time left: ${secs} ${secondText}`;\n }\n };\n // =============================================================================\n // RENDER FUNCTION\n // =============================================================================\n\n const renderPeriodicAndWarningAnnouncement = () => (\n <VisuallyHidden\n aria-live={\n typeof notifyTimer === \"number\" &&\n remainingSeconds <= notifyTimer\n ? \"assertive\"\n : \"polite\"\n }\n aria-atomic=\"true\"\n >\n {announcement}\n </VisuallyHidden>\n );\n\n const renderTimer = () => {\n const { minutes, seconds } =\n TimeHelper.toMinutesSeconds(remainingSeconds);\n const m = minutes !== 1 ? \"mins\" : \"min\";\n const s = seconds !== 1 ? \"secs\" : \"sec\";\n return (\n <>\n <ClockIcon className={styles.timerIcon} data-warn={!!warn} />\n <div className={styles.timeLeft}>Time left:</div>\n <div\n className={styles.timer}\n aria-label={getAccessibleTimeText(remainingSeconds)}\n >\n {minutes} {m} {String(seconds).padStart(2, \"0\")} {s}\n </div>\n {renderPeriodicAndWarningAnnouncement()}\n </>\n );\n };\n\n const renderInlineCountdown = () => {\n return (\n <div\n data-testid={testId}\n data-id=\"countdown-wrapper\"\n data-warn={!!warn}\n ref={wrapperRef}\n inert={inertValue(!isVisible)}\n className={clsx(\n styles.countdown,\n styles.countdownInline,\n !isVisible && styles.countdownHidden\n )}\n tabIndex={0}\n role=\"timer\"\n aria-label=\"Countdown timer\"\n onFocus={handleOnFocus}\n >\n {renderTimer()}\n </div>\n );\n };\n\n const renderFixedCountdown = () => {\n return (\n <div\n ref={fixedCountdownRef}\n data-testid={testId}\n data-id=\"fixed-countdown-wrapper\"\n data-warn={!!warn}\n className={clsx(styles.countdown, styles.countdownFixed)}\n tabIndex={0}\n role=\"timer\"\n aria-label=\"Countdown timer\"\n onFocus={handleOnFocus}\n >\n {renderTimer()}\n </div>\n );\n };\n\n if (!isPlaying && remainingSeconds !== 0) return <></>;\n\n return (\n <div className={clsx(styles.wrapper, className)} {...otherProps}>\n <div ref={stickyRef}></div>\n {renderInlineCountdown()}\n {wrapperRef.current && !isVisible && renderFixedCountdown()}\n </div>\n );\n};\n"],"names":["CountdownTimer","_a","className","align","timer","timestamp","notifyTimer","offset","mobileOffset","show","fixed","reminderInterval","testId","onFinish","onNotify","onTick","otherProps","__rest","wrapperRef","useRef","fixedCountdownRef","isNotified","offsetY","setOffsetY","useState","clientRectRight","setClientRectRight","clientRectX","setClientRectX","isPlaying","setIsPlaying","announcement","setAnnouncement","isMounted","useIsMounted","remainingSeconds","initialSeconds","useTimer","ref","stickyRef","inView","useInView","threshold","rootMargin","initialInView","isVisible","warn","isMobile","useMaxWidthMediaQuery","useApplyStyle","styles.tokens","fixedCountdown","top","formatUnitValue","left","getOffsetLeft","right","Math","floor","document","body","clientWidth","getOffsetRight","useEffect","performOnFinishHandler","performOnTickHandler","performOnNotifyHandler","y","desktopTop","mobileTop","_b","getOffsetY","current","handleResize","throttle","handleWindowResize","window","addEventListener","removeEventListener","timeElapsed","getAccessibleTimeText","clientRect","getBoundingClientRect","x","handleOnFocus","focusMessage","seconds","minutes","secs","TimeHelper","toMinutesSeconds","minuteText","secondText","renderTimer","m","s","_jsxs","_Fragment","children","_jsx","ClockIcon","styles.timerIcon","styles.timeLeft","styles.timer","String","padStart","VisuallyHidden","Object","assign","clsx","styles.wrapper","inert","inertValue","styles.countdown","styles.countdownInline","styles.countdownHidden","tabIndex","role","onFocus","styles.countdownFixed"],"mappings":"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"}
|
package/data-table/data-table.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{__rest as e}from"../_virtual/_tslib.js";import{jsxs as t,jsx as i,Fragment as n}from"react/jsx-runtime";import{ArrowUpIcon as r,ArrowDownIcon as l}from"@lifesg/react-icons";import o from"clsx";import{useRef as a,useState as s,useEffect as d}from"react";import{useResizeDetector as c}from"../external/react-resize-detector/build/index.esm.js";import"../animations/loading-dots/loading-dots.js";import{LoadingDotsSpinner as m}from"../animations/loading-dots-spinner/loading-dots-spinner.js";import"../animations/loading-spinner/loading-spinner.js";import"../theme/theme-provider/context.js";import{Checkbox as h}from"../checkbox/checkbox.js";import{ErrorDisplay as p}from"../error-display/error-display.js";import{VisuallyHidden as u,concatIds as g}from"../shared/accessibility/index.js";import{BasicButton as b}from"../shared/input-wrapper/input-wrapper.js";import"../theme/theme-provider/index.js";import"../theme/tokens/border.js";import"../theme/tokens/colour.js";import"../theme/tokens/font.js";import"../theme/tokens/media-query.js";import{useApplyStyle as f}from"../theme/utils/use-apply-styles.js";import"../theme/utils/use-media-query.js";import{Typography as y}from"../typography/index.js";import"../util/calendar-helper.js";import"../external/dayjs/dayjs.min.js";import{useId as w}from"../util/simple-id-generator.js";import"../util/string-helper.js";import"@react-aria/live-announcer";import{useEvent as v}from"../util/use-event.js";import{useEventListener as j}from"../util/use-event-listener.js";import{useThrottle as N}from"../util/use-throttle.js";import{tokens as S,tableContainer as k,table as $,tableWrapper as C,tableWrapperScrollable as x,stickyHeader as H,headerRow as B,bodyRow as A,emptyStateCell as R,bodyRowWithBottomBorder as I,bodyRowAlternating as W,bodyRowSelectable as M,bodyRowSelected as K,loadingStateWrapper as L,actionBarWrapper as V,actionBarButton as q,actionBarGap as z,actionBar as D,actionBarFloating as E,actionBarSpacer as _,selectionCheckboxWrapper as O,headerCell as P,headerCellCheckbox as T,headerCellContent as F,headerCellClickable as G,emptyStateTitle as J,bodyCell as Q,bodyCellCheckbox as U,bodyCellText as X}from"./data-table.styles.js";const Y=Y=>{var{id:Z,headers:ee,rows:te,className:ie,sortIndicators:ne,alternatingRows:re,loadState:le="success",enableMultiSelect:oe,selectedIds:ae,disabledIds:se,enableActionBar:de,enableActionBarWithoutSelection:ce,enableSelectAll:me,enableStickyHeader:he,emptyView:pe,actionBarContent:ue,renderCustomEmptyView:ge,onHeaderClick:be,onSelect:fe,onSelectAll:ye,onClearSelectionClick:we}=Y,ve=e(Y,["id","headers","rows","className","sortIndicators","alternatingRows","loadState","enableMultiSelect","selectedIds","disabledIds","enableActionBar","enableActionBarWithoutSelection","enableSelectAll","enableStickyHeader","emptyView","actionBarContent","renderCustomEmptyView","onHeaderClick","onSelect","onSelectAll","onClearSelectionClick"]);const je=a(null),Ne=a(null),Se=a(null),ke=a(null),$e=w(),Ce=ee.filter((e=>"string"!=typeof e&&!!e.keyColumn)),[xe,He]=s("inline"),[Be,Ae]=s(!1),[Re,Ie]=s(!1);f(Se,{[S.actionBarWrapperLeft]:Re&&je.current?`${je.current.getBoundingClientRect().left}px`:void 0,[S.actionBarWrapperWidth]:je.current?`${je.current.clientWidth}px`:void 0});const We=v((()=>{Fe()}));d((()=>{We()}),[We]);const Me=N((()=>Fe()),100);c({targetRef:ke,onResize:()=>Me()}),j("scroll",Me),j("scroll",Me,ke.current);const Ke=()=>!!(te&&te.length&&ae)&&ae.length===te.length,Le=e=>!!(null==ae?void 0:ae.includes(e)),Ve=e=>!!re&&e%2==1,qe=e=>!!(null==se?void 0:se.includes(e)),ze=e=>{if(ve["data-testid"])return`${ve["data-testid"]}-${e}`},De=()=>ee.length+(oe?1:0),Ee=e=>null==ne?void 0:ne[e],_e=e=>{const t=Ee(e);if(t)return"asc"===t?"ascending":"descending"},Oe=e=>` by ${"asc"===Ee(e)?"descending":"ascending"} order`,Pe=(e,t)=>`${$e}-row-${e}-${t}-key-column`,Te=e=>{const t=Ce.map((t=>Pe(e,t.fieldKey)));return g(...t)},Fe=()=>{if(!ke.current||!Ne.current||!Se.current)return;const e=ke.current.getBoundingClientRect(),t=Ne.current.getBoundingClientRect();let i="inline";const n=ke.current.scrollHeight>ke.current.clientHeight;if(e.top>window.innerHeight||e.bottom<0?i="inline":n?e.top>0&&e.top<window.innerHeight&&e.bottom>0&&e.bottom<window.innerHeight||e.top<0&&e.bottom>0&&e.bottom<window.innerHeight?i="sticky":e.bottom>window.innerHeight&&Math.max(e.top,t.bottom)+t.height<window.innerHeight&&(i="fixed"):e.bottom>window.innerHeight&&t.bottom+t.height<window.innerHeight&&(i="fixed"),Ae(n),He(i),"fixed"===i)Ie(!0);else if("sticky"===i){const e=ke.current.scrollTop+ke.current.clientHeight<ke.current.scrollHeight;Ie(e)}else Ie(!1)},Ge=e=>{const{fieldKey:n,label:r,clickable:l=!1,style:a}="string"==typeof e?{fieldKey:e,label:e,style:void 0}:e,s=!!Ee(n),d=(e=>`${$e}-header-${e}`)(n);return t("th",{"data-testid":ze(`header-${n}`),className:o(P,l&&G),scope:"col","aria-sort":_e(n),style:a,onClick:()=>(e=>{null==be||be(e)})(n),children:[t("div",{className:F,id:d,children:["string"==typeof r?i(y.BodyBL,{weight:"bold",children:r}):r,Je(n)]}),(l||s)&&i(u,{children:t("button",{onClick:e=>((e,t)=>{e.stopPropagation(),null==be||be(t)})(e,n),children:[s&&"Sort ",i("span",{"aria-labelledby":d}),s&&Oe(n)]})})]},n)},Je=e=>{const t=null==ne?void 0:ne[e];return t?"asc"===t?i(r,{"aria-hidden":!0,"data-testid":ze(`header-${e}-arrowup`)}):i(l,{"aria-hidden":!0,"data-testid":ze(`header-${e}-arrowdown`)}):i(n,{})},Qe=()=>i("th",{"data-testid":ze("header-selection"),className:o(P,T),scope:"col",children:t("div",{className:O,children:[i(u,{children:"Row selection"}),me&&i(h,{checked:Ke(),indeterminate:!!ae&&0!==ae.length&&!Ke(),disabled:!te||!te.length
|
|
1
|
+
import{__rest as e}from"../_virtual/_tslib.js";import{jsxs as t,jsx as i,Fragment as n}from"react/jsx-runtime";import{ArrowUpIcon as r,ArrowDownIcon as l}from"@lifesg/react-icons";import o from"clsx";import{useRef as a,useState as s,useEffect as d}from"react";import{useResizeDetector as c}from"../external/react-resize-detector/build/index.esm.js";import"../animations/loading-dots/loading-dots.js";import{LoadingDotsSpinner as m}from"../animations/loading-dots-spinner/loading-dots-spinner.js";import"../animations/loading-spinner/loading-spinner.js";import"../theme/theme-provider/context.js";import{Checkbox as h}from"../checkbox/checkbox.js";import{ErrorDisplay as p}from"../error-display/error-display.js";import{VisuallyHidden as u,concatIds as g}from"../shared/accessibility/index.js";import{BasicButton as b}from"../shared/input-wrapper/input-wrapper.js";import"../theme/theme-provider/index.js";import"../theme/tokens/border.js";import"../theme/tokens/colour.js";import"../theme/tokens/font.js";import"../theme/tokens/media-query.js";import{useApplyStyle as f}from"../theme/utils/use-apply-styles.js";import"../theme/utils/use-media-query.js";import{Typography as y}from"../typography/index.js";import"../util/calendar-helper.js";import"../external/dayjs/dayjs.min.js";import{useId as w}from"../util/simple-id-generator.js";import"../util/string-helper.js";import"@react-aria/live-announcer";import{useEvent as v}from"../util/use-event.js";import{useEventListener as j}from"../util/use-event-listener.js";import{useThrottle as N}from"../util/use-throttle.js";import{tokens as S,tableContainer as k,table as $,tableWrapper as C,tableWrapperScrollable as x,stickyHeader as H,headerRow as B,bodyRow as A,emptyStateCell as R,bodyRowWithBottomBorder as I,bodyRowAlternating as W,bodyRowSelectable as M,bodyRowSelected as K,loadingStateWrapper as L,actionBarWrapper as V,actionBarButton as q,actionBarGap as z,actionBar as D,actionBarFloating as E,actionBarSpacer as _,selectionCheckboxWrapper as O,headerCell as P,headerCellCheckbox as T,headerCellContent as F,headerCellClickable as G,emptyStateTitle as J,bodyCell as Q,bodyCellCheckbox as U,bodyCellText as X}from"./data-table.styles.js";const Y=Y=>{var{id:Z,headers:ee,rows:te,className:ie,sortIndicators:ne,alternatingRows:re,loadState:le="success",enableMultiSelect:oe,selectedIds:ae,disabledIds:se,enableActionBar:de,enableActionBarWithoutSelection:ce,enableSelectAll:me,enableStickyHeader:he,emptyView:pe,actionBarContent:ue,renderCustomEmptyView:ge,onHeaderClick:be,onSelect:fe,onSelectAll:ye,onClearSelectionClick:we}=Y,ve=e(Y,["id","headers","rows","className","sortIndicators","alternatingRows","loadState","enableMultiSelect","selectedIds","disabledIds","enableActionBar","enableActionBarWithoutSelection","enableSelectAll","enableStickyHeader","emptyView","actionBarContent","renderCustomEmptyView","onHeaderClick","onSelect","onSelectAll","onClearSelectionClick"]);const je=a(null),Ne=a(null),Se=a(null),ke=a(null),$e=w(),Ce=ee.filter((e=>"string"!=typeof e&&!!e.keyColumn)),[xe,He]=s("inline"),[Be,Ae]=s(!1),[Re,Ie]=s(!1);f(Se,{[S.actionBarWrapperLeft]:Re&&je.current?`${je.current.getBoundingClientRect().left}px`:void 0,[S.actionBarWrapperWidth]:je.current?`${je.current.clientWidth}px`:void 0});const We=v((()=>{Fe()}));d((()=>{We()}),[We]);const Me=N((()=>Fe()),100);c({targetRef:ke,onResize:()=>Me()}),j("scroll",Me),j("scroll",Me,ke.current);const Ke=()=>!!(te&&te.length&&ae)&&ae.length===te.length,Le=e=>!!(null==ae?void 0:ae.includes(e)),Ve=e=>!!re&&e%2==1,qe=e=>!!(null==se?void 0:se.includes(e)),ze=e=>{if(ve["data-testid"])return`${ve["data-testid"]}-${e}`},De=()=>ee.length+(oe?1:0),Ee=e=>null==ne?void 0:ne[e],_e=e=>{const t=Ee(e);if(t)return"asc"===t?"ascending":"descending"},Oe=e=>` by ${"asc"===Ee(e)?"descending":"ascending"} order`,Pe=(e,t)=>`${$e}-row-${e}-${t}-key-column`,Te=e=>{const t=Ce.map((t=>Pe(e,t.fieldKey)));return g(...t)},Fe=()=>{if(!ke.current||!Ne.current||!Se.current)return;const e=ke.current.getBoundingClientRect(),t=Ne.current.getBoundingClientRect();let i="inline";const n=ke.current.scrollHeight>ke.current.clientHeight;if(e.top>window.innerHeight||e.bottom<0?i="inline":n?e.top>0&&e.top<window.innerHeight&&e.bottom>0&&e.bottom<window.innerHeight||e.top<0&&e.bottom>0&&e.bottom<window.innerHeight?i="sticky":e.bottom>window.innerHeight&&Math.max(e.top,t.bottom)+t.height<window.innerHeight&&(i="fixed"):e.bottom>window.innerHeight&&t.bottom+t.height<window.innerHeight&&(i="fixed"),Ae(n),He(i),"fixed"===i)Ie(!0);else if("sticky"===i){const e=ke.current.scrollTop+ke.current.clientHeight<ke.current.scrollHeight;Ie(e)}else Ie(!1)},Ge=e=>{const{fieldKey:n,label:r,clickable:l=!1,style:a}="string"==typeof e?{fieldKey:e,label:e,style:void 0}:e,s=!!Ee(n),d=(e=>`${$e}-header-${e}`)(n);return t("th",{"data-testid":ze(`header-${n}`),className:o(P,l&&G),scope:"col","aria-sort":_e(n),style:a,onClick:()=>(e=>{null==be||be(e)})(n),children:[t("div",{className:F,id:d,children:["string"==typeof r?i(y.BodyBL,{weight:"bold",children:r}):r,Je(n)]}),(l||s)&&i(u,{children:t("button",{onClick:e=>((e,t)=>{e.stopPropagation(),null==be||be(t)})(e,n),children:[s&&"Sort ",i("span",{"aria-labelledby":d}),s&&Oe(n)]})})]},n)},Je=e=>{const t=null==ne?void 0:ne[e];return t?"asc"===t?i(r,{"aria-hidden":!0,"data-testid":ze(`header-${e}-arrowup`)}):i(l,{"aria-hidden":!0,"data-testid":ze(`header-${e}-arrowdown`)}):i(n,{})},Qe=()=>i("th",{"data-testid":ze("header-selection"),className:o(P,T),scope:"col",children:t("div",{className:O,children:[i(u,{children:"Row selection"}),me&&i(h,{checked:Ke(),indeterminate:!!ae&&0!==ae.length&&!Ke(),disabled:!te||!te.length,"aria-label":"Select all rows",onClick:()=>{ye&&ye(Ke())}})]})}),Ue=(e,t)=>{const n="string"==typeof e?void 0:e.style,r="string"==typeof e?e:e.fieldKey,l=t.id.toString(),o=t[r],a=`${l}-${r}`;let s;return s="string"==typeof o||"number"==typeof o?i(y.BodyBL,{className:X,children:o}):"function"==typeof o?o(t,{isSelected:Le(l)}):o,i("td",{"data-testid":ze(`row-${a}`),id:Pe(l,r),className:Q,style:n,children:s},a)},Xe=e=>{const t=e.id.toString();return i("td",{"data-testid":ze(`row-${t}-selection`),className:o(Q,U),children:i("div",{className:O,children:i(h,{checked:Le(t),"aria-labelledby":Te(t),onClick:()=>{fe&&fe(t,!Le(t))},disabled:qe(t),focusableWhenDisabled:qe(t)})})})},Ye=()=>{let e;return e=(null==pe?void 0:pe.title)?"string"==typeof pe.title?i(y.HeadingSM,{className:J,weight:"bold",children:pe.title}):pe.title:i(y.HeadingSM,{className:J,weight:"bold",children:"No <items> found"}),i(p,Object.assign({type:"no-item-found"},pe,{title:e,description:(null==pe?void 0:pe.description)?pe.description:"No matching rows"}))};return t("div",{id:Z||"table-wrapper","data-testid":ve["data-testid"]||"table",className:o(C,Be&&x,ie),ref:ke,tabIndex:0,children:[i("div",{className:k,children:t("table",{ref:je,className:$,children:[i("thead",{ref:Ne,className:he?H:void 0,children:t("tr",{className:B,children:[oe&&Qe(),ee.map(Ge)]})}),i("tbody",{children:"success"===le?!te||te.length<1?i("tr",{className:A,children:i("td",{className:R,colSpan:De(),children:ge?ge():Ye()})}):i(n,{children:null==te?void 0:te.map(((e,i)=>t("tr",{"data-testid":ze(`row-${e.id.toString()}`),className:o(A,Re&&I,Ve(i)&&W,oe&&M,Le(e.id.toString())&&K),children:[oe&&Xe(e),ee.map((t=>Ue(t,e)))]},e.id.toString())))}):i("tr",{className:A,children:i("td",{colSpan:De(),children:i("div",{className:L,role:"status","aria-live":"polite","aria-label":"Loading table",children:"loading"===le&&i(m,{})})})})})]})}),de&&(ae&&ae.length>0||ce)&&(()=>{var e;const r=null!==(e=null==ae?void 0:ae.length)&&void 0!==e?e:0,l="item"+(1===r?"":"s");return t(n,{children:[i("div",{ref:Se,className:V,"data-strategy":xe,children:t("div",{"data-testid":"data-table-action-bar",className:o(D,Re&&E),children:[i(y.BodyMD,{weight:"semibold",children:`${r} ${l} selected`}),r>0?i(b,{className:q,type:"button","aria-label":`Clear selection of ${r} ${l}`,onClick:we,children:"Clear selection"}):i("div",{className:z}),ue]})}),"sticky"!==xe&&i("div",{className:_})]})})()]})};export{Y as DataTable};
|
|
2
2
|
//# sourceMappingURL=data-table.js.map
|