@salt-ds/core 1.59.0 → 1.60.0
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/CHANGELOG.md +33 -0
- package/css/salt-core.css +1 -1
- package/dist-cjs/accordion/Accordion.js.map +1 -1
- package/dist-cjs/accordion/AccordionContext.js.map +1 -1
- package/dist-cjs/aria-announcer/AriaAnnounce.js +15 -3
- package/dist-cjs/aria-announcer/AriaAnnounce.js.map +1 -1
- package/dist-cjs/aria-announcer/AriaAnnouncerContext.js.map +1 -1
- package/dist-cjs/aria-announcer/AriaAnnouncerProvider.js +65 -43
- package/dist-cjs/aria-announcer/AriaAnnouncerProvider.js.map +1 -1
- package/dist-cjs/aria-announcer/announcementRegistry.js +31 -0
- package/dist-cjs/aria-announcer/announcementRegistry.js.map +1 -0
- package/dist-cjs/aria-announcer/useAriaAnnouncer.js +44 -16
- package/dist-cjs/aria-announcer/useAriaAnnouncer.js.map +1 -1
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/pagination/Pagination.js +1 -0
- package/dist-cjs/pagination/Pagination.js.map +1 -1
- package/dist-cjs/salt-provider/SaltProvider.js +3 -1
- package/dist-cjs/salt-provider/SaltProvider.js.map +1 -1
- package/dist-cjs/spinner/Spinner.js +1 -0
- package/dist-cjs/spinner/Spinner.js.map +1 -1
- package/dist-cjs/tooltip/useAriaAnnounce.js +1 -0
- package/dist-cjs/tooltip/useAriaAnnounce.js.map +1 -1
- package/dist-cjs/utils/useEventCallback.js +8 -2
- package/dist-cjs/utils/useEventCallback.js.map +1 -1
- package/dist-es/accordion/Accordion.js.map +1 -1
- package/dist-es/accordion/AccordionContext.js.map +1 -1
- package/dist-es/aria-announcer/AriaAnnounce.js +15 -3
- package/dist-es/aria-announcer/AriaAnnounce.js.map +1 -1
- package/dist-es/aria-announcer/AriaAnnouncerContext.js.map +1 -1
- package/dist-es/aria-announcer/AriaAnnouncerProvider.js +67 -45
- package/dist-es/aria-announcer/AriaAnnouncerProvider.js.map +1 -1
- package/dist-es/aria-announcer/announcementRegistry.js +28 -0
- package/dist-es/aria-announcer/announcementRegistry.js.map +1 -0
- package/dist-es/aria-announcer/useAriaAnnouncer.js +45 -17
- package/dist-es/aria-announcer/useAriaAnnouncer.js.map +1 -1
- package/dist-es/index.js +1 -1
- package/dist-es/pagination/Pagination.js +1 -0
- package/dist-es/pagination/Pagination.js.map +1 -1
- package/dist-es/salt-provider/SaltProvider.js +3 -1
- package/dist-es/salt-provider/SaltProvider.js.map +1 -1
- package/dist-es/spinner/Spinner.js +1 -0
- package/dist-es/spinner/Spinner.js.map +1 -1
- package/dist-es/tooltip/useAriaAnnounce.js +1 -0
- package/dist-es/tooltip/useAriaAnnounce.js.map +1 -1
- package/dist-es/utils/useEventCallback.js +8 -2
- package/dist-es/utils/useEventCallback.js.map +1 -1
- package/dist-types/accordion/Accordion.d.ts +2 -2
- package/dist-types/accordion/AccordionContext.d.ts +1 -1
- package/dist-types/aria-announcer/AriaAnnounce.d.ts +9 -2
- package/dist-types/aria-announcer/AriaAnnouncerContext.d.ts +26 -2
- package/dist-types/aria-announcer/AriaAnnouncerProvider.d.ts +6 -7
- package/dist-types/aria-announcer/announcementRegistry.d.ts +5 -0
- package/dist-types/utils/useEventCallback.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Spinner.js","sources":["../src/spinner/Spinner.tsx"],"sourcesContent":["import { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport { forwardRef, type HTMLAttributes, useEffect } from \"react\";\nimport { useAriaAnnouncer } from \"../aria-announcer\";\nimport { useDensity } from \"../salt-provider\";\nimport { makePrefixer, useId } from \"../utils\";\nimport spinnerCss from \"./Spinner.css\";\nimport { SpinnerSVG } from \"./svgSpinners/SpinnerSVG\";\n\n/**\n * Spinner component, provides an indeterminate loading indicator\n *\n * @example\n * <Spinner size=\"small\" | \"medium\" | \"large\" />\n */\n\nexport const SpinnerSizeValues = [\n \"default\",\n \"large\",\n \"small\",\n \"medium\",\n] as const;\n\ntype SpinnerSize = (typeof SpinnerSizeValues)[number];\n\nexport type SpinnerSVGSize = Exclude<SpinnerSize, \"default\">;\n\nconst handleSize = (size: SpinnerSize): SpinnerSVGSize =>\n size === \"default\" ? \"medium\" : size;\n\nconst withBaseName = makePrefixer(\"saltSpinner\");\n\nexport interface SpinnerProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Determines the interval on which the component will continue to announce the aria-label. Defaults to 5000ms (5s)\n */\n announcerInterval?: number;\n /**\n * * Determines the interval after which the component will stop announcing the aria-label. Defaults to 20000ms (20s)\n */\n announcerTimeout?: number;\n /**\n * The className(s) of the component\n */\n className?: string;\n /**\n * Determines the message to be announced by the component when it unmounts. Set to null if not needed.\n */\n completionAnnouncement?: string | null;\n /**\n * If true, built in aria announcer will be inactive\n */\n disableAnnouncer?: boolean;\n /**\n * The prop for the role attribute of the component\n */\n role?: string;\n /**\n * Determines the size of the spinner. Must be one of: 'default', 'large', 'small', 'medium'.\n */\n size?: SpinnerSize;\n /**\n * The ids of the SvgSpinner components\n */\n id?: string;\n}\n\nexport const Spinner = forwardRef<HTMLDivElement, SpinnerProps>(\n function Spinner(\n {\n \"aria-label\": ariaLabel = \"loading\",\n announcerInterval = 5000,\n announcerTimeout = 20000,\n completionAnnouncement = `finished ${ariaLabel}`,\n disableAnnouncer,\n className,\n size = \"medium\",\n id: idProp,\n ...rest\n },\n ref,\n ) {\n const id = useId(idProp);\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-spinner\",\n css: spinnerCss,\n window: targetWindow,\n });\n\n const { announce } = useAriaAnnouncer();\n\n const density = useDensity();\n size = handleSize(size);\n\n useEffect(() => {\n if (disableAnnouncer) return;\n\n announce(ariaLabel);\n\n const startTime = Date.now();\n\n const interval =\n announcerInterval > 0 &&\n setInterval(() => {\n if (Date.now() - startTime > announcerTimeout) {\n // The announcer will stop after `announcerTimeout` time\n announce(\n `${ariaLabel} is still in progress, but will no longer announce.`,\n );\n interval && clearInterval(interval);\n return;\n }\n announce(ariaLabel);\n }, announcerInterval);\n\n return () => {\n if (disableAnnouncer) return;\n\n interval && clearInterval(interval);\n if (completionAnnouncement) {\n announce(completionAnnouncement);\n }\n };\n }, [\n announce,\n announcerInterval,\n announcerTimeout,\n ariaLabel,\n completionAnnouncement,\n disableAnnouncer,\n ]);\n\n return (\n <div\n aria-label={ariaLabel}\n className={clsx(withBaseName(), withBaseName(size), className)}\n ref={ref}\n role=\"img\"\n {...rest}\n >\n <SpinnerSVG size={size} density={density} id={id} />\n </div>\n );\n },\n);\n"],"names":["makePrefixer","forwardRef","Spinner","useId","useWindow","useComponentCssInjection","spinnerCss","useAriaAnnouncer","useDensity","useEffect","jsx","clsx","SpinnerSVG"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Spinner.js","sources":["../src/spinner/Spinner.tsx"],"sourcesContent":["import { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport { forwardRef, type HTMLAttributes, useEffect } from \"react\";\nimport { useAriaAnnouncer } from \"../aria-announcer\";\nimport { useDensity } from \"../salt-provider\";\nimport { makePrefixer, useId } from \"../utils\";\nimport spinnerCss from \"./Spinner.css\";\nimport { SpinnerSVG } from \"./svgSpinners/SpinnerSVG\";\n\n/**\n * Spinner component, provides an indeterminate loading indicator\n *\n * @example\n * <Spinner size=\"small\" | \"medium\" | \"large\" />\n */\n\nexport const SpinnerSizeValues = [\n \"default\",\n \"large\",\n \"small\",\n \"medium\",\n] as const;\n\ntype SpinnerSize = (typeof SpinnerSizeValues)[number];\n\nexport type SpinnerSVGSize = Exclude<SpinnerSize, \"default\">;\n\nconst handleSize = (size: SpinnerSize): SpinnerSVGSize =>\n size === \"default\" ? \"medium\" : size;\n\nconst withBaseName = makePrefixer(\"saltSpinner\");\n\nexport interface SpinnerProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Determines the interval on which the component will continue to announce the aria-label. Defaults to 5000ms (5s)\n */\n announcerInterval?: number;\n /**\n * * Determines the interval after which the component will stop announcing the aria-label. Defaults to 20000ms (20s)\n */\n announcerTimeout?: number;\n /**\n * The className(s) of the component\n */\n className?: string;\n /**\n * Determines the message to be announced by the component when it unmounts. Set to null if not needed.\n */\n completionAnnouncement?: string | null;\n /**\n * If true, built in aria announcer will be inactive\n */\n disableAnnouncer?: boolean;\n /**\n * The prop for the role attribute of the component\n */\n role?: string;\n /**\n * Determines the size of the spinner. Must be one of: 'default', 'large', 'small', 'medium'.\n */\n size?: SpinnerSize;\n /**\n * The ids of the SvgSpinner components\n */\n id?: string;\n}\n\nexport const Spinner = forwardRef<HTMLDivElement, SpinnerProps>(\n function Spinner(\n {\n \"aria-label\": ariaLabel = \"loading\",\n announcerInterval = 5000,\n announcerTimeout = 20000,\n completionAnnouncement = `finished ${ariaLabel}`,\n disableAnnouncer,\n className,\n size = \"medium\",\n id: idProp,\n ...rest\n },\n ref,\n ) {\n const id = useId(idProp);\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-spinner\",\n css: spinnerCss,\n window: targetWindow,\n });\n\n const { announce } = useAriaAnnouncer();\n\n const density = useDensity();\n size = handleSize(size);\n\n useEffect(() => {\n if (disableAnnouncer) return;\n\n announce(ariaLabel);\n\n const startTime = Date.now();\n\n const interval =\n announcerInterval > 0 &&\n setInterval(() => {\n if (Date.now() - startTime > announcerTimeout) {\n // The announcer will stop after `announcerTimeout` time\n announce(\n `${ariaLabel} is still in progress, but will no longer announce.`,\n );\n interval && clearInterval(interval);\n return;\n }\n announce(ariaLabel);\n }, announcerInterval);\n\n return () => {\n if (disableAnnouncer) return;\n\n interval && clearInterval(interval);\n if (completionAnnouncement) {\n announce(completionAnnouncement);\n }\n };\n }, [\n announce,\n announcerInterval,\n announcerTimeout,\n ariaLabel,\n completionAnnouncement,\n disableAnnouncer,\n ]);\n\n return (\n <div\n aria-label={ariaLabel}\n className={clsx(withBaseName(), withBaseName(size), className)}\n ref={ref}\n role=\"img\"\n {...rest}\n >\n <SpinnerSVG size={size} density={density} id={id} />\n </div>\n );\n },\n);\n"],"names":["makePrefixer","forwardRef","Spinner","useId","useWindow","useComponentCssInjection","spinnerCss","useAriaAnnouncer","useDensity","useEffect","jsx","clsx","SpinnerSVG"],"mappings":";;;;;;;;;;;;;;;;;;AAiBO,MAAM,iBAAA,GAAoB;AAAA,EAC/B,SAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF;AAMA,MAAM,UAAA,GAAa,CAAC,IAAA,KAClB,IAAA,KAAS,YAAY,QAAA,GAAW,IAAA;AAElC,MAAM,YAAA,GAAeA,0BAAa,aAAa,CAAA;AAqCxC,MAAM,OAAA,GAAUC,gBAAA;AAAA,EACrB,SAASC,QAAAA,CACP;AAAA,IACE,cAAc,SAAA,GAAY,SAAA;AAAA,IAC1B,iBAAA,GAAoB,GAAA;AAAA,IACpB,gBAAA,GAAmB,GAAA;AAAA,IACnB,sBAAA,GAAyB,YAAY,SAAS,CAAA,CAAA;AAAA,IAC9C,gBAAA;AAAA,IACA,SAAA;AAAA,IACA,IAAA,GAAO,QAAA;AAAA,IACP,EAAA,EAAI,MAAA;AAAA,IACJ,GAAG;AAAA,KAEL,GAAA,EACA;AACA,IAAA,MAAM,EAAA,GAAKC,YAAM,MAAM,CAAA;AACvB,IAAA,MAAM,eAAeC,gBAAA,EAAU;AAC/B,IAAAC,+BAAA,CAAyB;AAAA,MACvB,MAAA,EAAQ,cAAA;AAAA,MACR,GAAA,EAAKC,SAAA;AAAA,MACL,MAAA,EAAQ;AAAA,KACT,CAAA;AAED,IAAA,MAAM,EAAE,QAAA,EAAS,GAAIC,iCAAA,EAAiB;AAEtC,IAAA,MAAM,UAAUC,uBAAA,EAAW;AAC3B,IAAA,IAAA,GAAO,WAAW,IAAI,CAAA;AAEtB,IAAAC,eAAA,CAAU,MAAM;AACd,MAAA,IAAI,gBAAA,EAAkB;AAEtB,MAAA,QAAA,CAAS,SAAS,CAAA;AAElB,MAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAE3B,MAAA,MAAM,QAAA,GACJ,iBAAA,GAAoB,CAAA,IACpB,WAAA,CAAY,MAAM;AAChB,QAAA,IAAI,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA,GAAY,gBAAA,EAAkB;AAE7C,UAAA,QAAA;AAAA,YACE,GAAG,SAAS,CAAA,mDAAA;AAAA,WACd;AACA,UAAA,QAAA,IAAY,cAAc,QAAQ,CAAA;AAClC,UAAA;AAAA,QACF;AACA,QAAA,QAAA,CAAS,SAAS,CAAA;AAAA,MACpB,GAAG,iBAAiB,CAAA;AAEtB,MAAA,OAAO,MAAM;AACX,QAAA,IAAI,gBAAA,EAAkB;AAEtB,QAAA,QAAA,IAAY,cAAc,QAAQ,CAAA;AAClC,QAAA,IAAI,sBAAA,EAAwB;AAC1B,UAAA,QAAA,CAAS,sBAAsB,CAAA;AAAA,QACjC;AAAA,MACF,CAAA;AAAA,IACF,CAAA,EAAG;AAAA,MACD,QAAA;AAAA,MACA,iBAAA;AAAA,MACA,gBAAA;AAAA,MACA,SAAA;AAAA,MACA,sBAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,uBACEC,cAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,YAAA,EAAY,SAAA;AAAA,QACZ,WAAWC,SAAA,CAAK,YAAA,IAAgB,YAAA,CAAa,IAAI,GAAG,SAAS,CAAA;AAAA,QAC7D,GAAA;AAAA,QACA,IAAA,EAAK,KAAA;AAAA,QACJ,GAAG,IAAA;AAAA,QAEJ,QAAA,kBAAAD,cAAA,CAACE,qBAAA,EAAA,EAAW,IAAA,EAAY,OAAA,EAAkB,EAAA,EAAQ;AAAA;AAAA,KACpD;AAAA,EAEJ;AACF;;;;;"}
|
|
@@ -4,6 +4,7 @@ var React = require('react');
|
|
|
4
4
|
require('react/jsx-runtime');
|
|
5
5
|
var useAriaAnnouncer = require('../aria-announcer/useAriaAnnouncer.js');
|
|
6
6
|
require('../aria-announcer/AriaAnnouncerContext.js');
|
|
7
|
+
require('../aria-announcer/AriaAnnouncerProvider.js');
|
|
7
8
|
require('clsx');
|
|
8
9
|
var useIsomorphicLayoutEffect = require('../utils/useIsomorphicLayoutEffect.js');
|
|
9
10
|
require('../utils/useFloatingUI/useFloatingUI.js');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAriaAnnounce.js","sources":["../src/tooltip/useAriaAnnounce.ts"],"sourcesContent":["import type { ElementProps, FloatingContext } from \"@floating-ui/react\";\nimport { type PointerEvent, useEffect, useRef } from \"react\";\nimport { useAriaAnnouncer } from \"../aria-announcer\";\nimport { useIsomorphicLayoutEffect } from \"../utils\";\n\nfunction getDocument(floating: HTMLElement | null) {\n return floating?.ownerDocument ?? document;\n}\n\n// TODO: Check whether can be anything more restrictive than `any`\n// biome-ignore lint/suspicious/noExplicitAny: see comment above\nfunction getWindow(value: any) {\n return getDocument(value).defaultView ?? window;\n}\n\nfunction isElement(value: unknown): value is HTMLElement {\n return value ? value instanceof getWindow(value).Element : false;\n}\n\nfunction getDelay(\n value: Props[\"delay\"],\n prop: \"open\" | \"close\",\n pointerType?: PointerEvent[\"pointerType\"],\n) {\n if (pointerType && pointerType !== \"mouse\") {\n return 0;\n }\n\n if (typeof value === \"number\") {\n return value;\n }\n\n return value?.[prop];\n}\n\ntype Props = {\n delay?: number | Partial<{ open: number; close: number }>;\n};\n\nexport const useAriaAnnounce = (\n context: FloatingContext,\n { delay = 0 }: Props,\n): ElementProps => {\n const { open, dataRef, refs } = context;\n\n const pointerTypeRef = useRef<PointerEvent[\"pointerType\"]>();\n const timeoutRef = useRef<number>();\n const blockMouseMoveRef = useRef(true);\n const { announce } = useAriaAnnouncer();\n\n useIsomorphicLayoutEffect(() => {\n if (!open) {\n pointerTypeRef.current = undefined;\n }\n });\n\n useEffect(() => {\n const reference = refs.reference.current;\n function announceFloating() {\n const tooltipContent = refs.floating.current?.innerText;\n\n if (tooltipContent) {\n announce(tooltipContent);\n }\n }\n\n function onMouseEnter(event: MouseEvent) {\n clearTimeout(timeoutRef.current);\n\n if (open) {\n return;\n }\n\n blockMouseMoveRef.current = false;\n dataRef.current.openEvent = event;\n\n if (delay) {\n timeoutRef.current = window.setTimeout(\n () => {\n announceFloating();\n },\n getDelay(delay, \"open\", pointerTypeRef.current),\n );\n } else {\n announceFloating();\n }\n }\n\n if (isElement(reference)) {\n reference.addEventListener(\"mouseenter\", onMouseEnter);\n return () => {\n reference.removeEventListener(\"mouseenter\", onMouseEnter);\n };\n }\n }, [dataRef, delay, open, refs.reference, refs.floating, announce]);\n\n function setPointerRef(event: PointerEvent) {\n pointerTypeRef.current = event.pointerType;\n }\n\n return {\n reference: {\n onPointerDown: setPointerRef,\n onPointerEnter: setPointerRef,\n },\n floating: {\n onMouseEnter() {\n clearTimeout(timeoutRef.current);\n },\n },\n };\n};\n"],"names":["useRef","useAriaAnnouncer","useIsomorphicLayoutEffect","useEffect"],"mappings":"
|
|
1
|
+
{"version":3,"file":"useAriaAnnounce.js","sources":["../src/tooltip/useAriaAnnounce.ts"],"sourcesContent":["import type { ElementProps, FloatingContext } from \"@floating-ui/react\";\nimport { type PointerEvent, useEffect, useRef } from \"react\";\nimport { useAriaAnnouncer } from \"../aria-announcer\";\nimport { useIsomorphicLayoutEffect } from \"../utils\";\n\nfunction getDocument(floating: HTMLElement | null) {\n return floating?.ownerDocument ?? document;\n}\n\n// TODO: Check whether can be anything more restrictive than `any`\n// biome-ignore lint/suspicious/noExplicitAny: see comment above\nfunction getWindow(value: any) {\n return getDocument(value).defaultView ?? window;\n}\n\nfunction isElement(value: unknown): value is HTMLElement {\n return value ? value instanceof getWindow(value).Element : false;\n}\n\nfunction getDelay(\n value: Props[\"delay\"],\n prop: \"open\" | \"close\",\n pointerType?: PointerEvent[\"pointerType\"],\n) {\n if (pointerType && pointerType !== \"mouse\") {\n return 0;\n }\n\n if (typeof value === \"number\") {\n return value;\n }\n\n return value?.[prop];\n}\n\ntype Props = {\n delay?: number | Partial<{ open: number; close: number }>;\n};\n\nexport const useAriaAnnounce = (\n context: FloatingContext,\n { delay = 0 }: Props,\n): ElementProps => {\n const { open, dataRef, refs } = context;\n\n const pointerTypeRef = useRef<PointerEvent[\"pointerType\"]>();\n const timeoutRef = useRef<number>();\n const blockMouseMoveRef = useRef(true);\n const { announce } = useAriaAnnouncer();\n\n useIsomorphicLayoutEffect(() => {\n if (!open) {\n pointerTypeRef.current = undefined;\n }\n });\n\n useEffect(() => {\n const reference = refs.reference.current;\n function announceFloating() {\n const tooltipContent = refs.floating.current?.innerText;\n\n if (tooltipContent) {\n announce(tooltipContent);\n }\n }\n\n function onMouseEnter(event: MouseEvent) {\n clearTimeout(timeoutRef.current);\n\n if (open) {\n return;\n }\n\n blockMouseMoveRef.current = false;\n dataRef.current.openEvent = event;\n\n if (delay) {\n timeoutRef.current = window.setTimeout(\n () => {\n announceFloating();\n },\n getDelay(delay, \"open\", pointerTypeRef.current),\n );\n } else {\n announceFloating();\n }\n }\n\n if (isElement(reference)) {\n reference.addEventListener(\"mouseenter\", onMouseEnter);\n return () => {\n reference.removeEventListener(\"mouseenter\", onMouseEnter);\n };\n }\n }, [dataRef, delay, open, refs.reference, refs.floating, announce]);\n\n function setPointerRef(event: PointerEvent) {\n pointerTypeRef.current = event.pointerType;\n }\n\n return {\n reference: {\n onPointerDown: setPointerRef,\n onPointerEnter: setPointerRef,\n },\n floating: {\n onMouseEnter() {\n clearTimeout(timeoutRef.current);\n },\n },\n };\n};\n"],"names":["useRef","useAriaAnnouncer","useIsomorphicLayoutEffect","useEffect"],"mappings":"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"}
|
|
@@ -7,8 +7,14 @@ function useEventCallback(fn) {
|
|
|
7
7
|
const ref = React.useRef(fn);
|
|
8
8
|
useIsomorphicLayoutEffect.useIsomorphicLayoutEffect(() => {
|
|
9
9
|
ref.current = fn;
|
|
10
|
-
});
|
|
11
|
-
return React.useCallback(
|
|
10
|
+
}, [fn]);
|
|
11
|
+
return React.useCallback(
|
|
12
|
+
((...args) => {
|
|
13
|
+
const latestFn = ref.current;
|
|
14
|
+
return latestFn(...args);
|
|
15
|
+
}),
|
|
16
|
+
[]
|
|
17
|
+
);
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
exports.useEventCallback = useEventCallback;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEventCallback.js","sources":["../src/utils/useEventCallback.ts"],"sourcesContent":["import { useCallback, useRef } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect\";\n\n/**\n * https://github.com/facebook/react/issues/14099#issuecomment-440013892\n */\nexport function useEventCallback<
|
|
1
|
+
{"version":3,"file":"useEventCallback.js","sources":["../src/utils/useEventCallback.ts"],"sourcesContent":["import { useCallback, useRef } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect\";\n\n/**\n * https://github.com/facebook/react/issues/14099#issuecomment-440013892\n */\nexport function useEventCallback<const T extends (...args: any[]) => void>(\n fn: T,\n): T {\n const ref = useRef<T>(fn);\n\n useIsomorphicLayoutEffect(() => {\n ref.current = fn;\n }, [fn]);\n\n return useCallback(\n ((...args: any[]) => {\n const latestFn = ref.current;\n return latestFn(...args);\n }) as T,\n [],\n );\n}\n"],"names":["useRef","useIsomorphicLayoutEffect","useCallback"],"mappings":";;;;;AAMO,SAAS,iBACd,EAAA,EACG;AACH,EAAA,MAAM,GAAA,GAAMA,aAAU,EAAE,CAAA;AAExB,EAAAC,mDAAA,CAA0B,MAAM;AAC9B,IAAA,GAAA,CAAI,OAAA,GAAU,EAAA;AAAA,EAChB,CAAA,EAAG,CAAC,EAAE,CAAC,CAAA;AAEP,EAAA,OAAOC,iBAAA;AAAA,KACJ,IAAI,IAAA,KAAgB;AACnB,MAAA,MAAM,WAAW,GAAA,CAAI,OAAA;AACrB,MAAA,OAAO,QAAA,CAAS,GAAG,IAAI,CAAA;AAAA,IACzB,CAAA;AAAA,IACA;AAAC,GACH;AACF;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Accordion.js","sources":["../src/accordion/Accordion.tsx"],"sourcesContent":["import { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport {\n type ComponentPropsWithoutRef,\n forwardRef,\n type SyntheticEvent,\n useState,\n} from \"react\";\nimport { makePrefixer, useControlled, useId } from \"../utils\";\nimport accordionCss from \"./Accordion.css\";\nimport { AccordionContext } from \"./AccordionContext\";\nexport interface AccordionProps\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onToggle\"> {\n /**\n *
|
|
1
|
+
{"version":3,"file":"Accordion.js","sources":["../src/accordion/Accordion.tsx"],"sourcesContent":["import { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport {\n type ComponentPropsWithoutRef,\n forwardRef,\n type SyntheticEvent,\n useState,\n} from \"react\";\nimport { makePrefixer, useControlled, useId } from \"../utils\";\nimport accordionCss from \"./Accordion.css\";\nimport { AccordionContext } from \"./AccordionContext\";\nexport interface AccordionProps\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onToggle\"> {\n /**\n * Optional value used by controlled group patterns.\n */\n value?: string;\n /**\n * Whether the accordion is expanded.\n */\n expanded?: boolean;\n /**\n * Whether the accordion is expanded by default.\n */\n defaultExpanded?: boolean;\n /**\n * Side to align the Accordion's indicator. Defaults to `left`.\n */\n indicatorSide?: \"left\" | \"right\";\n /**\n * Callback fired when the accordion is toggled.\n */\n onToggle?: (event: SyntheticEvent<HTMLButtonElement>) => void;\n /**\n * Whether the accordion is disabled.\n */\n disabled?: boolean;\n /**\n * The status of the accordion.\n */\n status?: \"error\" | \"warning\" | \"success\";\n}\n\nconst withBaseName = makePrefixer(\"saltAccordion\");\n\nexport const Accordion = forwardRef<HTMLDivElement, AccordionProps>(\n function Accordion(props, ref) {\n const {\n className,\n defaultExpanded,\n expanded: expandedProp,\n disabled,\n indicatorSide = \"left\",\n id: idProp,\n onToggle,\n status,\n value,\n ...rest\n } = props;\n\n const id = useId(idProp);\n const [headerId, setHeaderId] = useState(`${id}-header`);\n const [panelId, setPanelId] = useState(`${id}-panel`);\n\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-accordion\",\n css: accordionCss,\n window: targetWindow,\n });\n\n const [expanded, setExpanded] = useControlled({\n controlled: expandedProp,\n default: Boolean(defaultExpanded),\n name: \"Accordion\",\n state: \"expanded\",\n });\n\n const toggle = (event: SyntheticEvent<HTMLButtonElement>) => {\n setExpanded((prev) => !prev);\n onToggle?.(event);\n };\n\n return (\n <AccordionContext.Provider\n value={{\n value,\n toggle,\n expanded,\n indicatorSide,\n disabled: Boolean(disabled),\n headerId,\n setHeaderId,\n panelId,\n setPanelId,\n status,\n }}\n >\n <div\n ref={ref}\n className={clsx(\n withBaseName(),\n {\n [withBaseName(status ?? \"\")]: status,\n [withBaseName(\"disabled\")]: disabled,\n },\n className,\n )}\n {...rest}\n />\n </AccordionContext.Provider>\n );\n },\n);\n"],"names":["Accordion","accordionCss"],"mappings":";;;;;;;;;;;;;;AA4CA,MAAM,YAAA,GAAe,aAAa,eAAe,CAAA;AAE1C,MAAM,SAAA,GAAY,UAAA;AAAA,EACvB,SAASA,UAAAA,CAAU,KAAA,EAAO,GAAA,EAAK;AAC7B,IAAA,MAAM;AAAA,MACJ,SAAA;AAAA,MACA,eAAA;AAAA,MACA,QAAA,EAAU,YAAA;AAAA,MACV,QAAA;AAAA,MACA,aAAA,GAAgB,MAAA;AAAA,MAChB,EAAA,EAAI,MAAA;AAAA,MACJ,QAAA;AAAA,MACA,MAAA;AAAA,MACA,KAAA;AAAA,MACA,GAAG;AAAA,KACL,GAAI,KAAA;AAEJ,IAAA,MAAM,EAAA,GAAK,MAAM,MAAM,CAAA;AACvB,IAAA,MAAM,CAAC,QAAA,EAAU,WAAW,IAAI,QAAA,CAAS,CAAA,EAAG,EAAE,CAAA,OAAA,CAAS,CAAA;AACvD,IAAA,MAAM,CAAC,OAAA,EAAS,UAAU,IAAI,QAAA,CAAS,CAAA,EAAG,EAAE,CAAA,MAAA,CAAQ,CAAA;AAEpD,IAAA,MAAM,eAAe,SAAA,EAAU;AAC/B,IAAA,wBAAA,CAAyB;AAAA,MACvB,MAAA,EAAQ,gBAAA;AAAA,MACR,GAAA,EAAKC,QAAA;AAAA,MACL,MAAA,EAAQ;AAAA,KACT,CAAA;AAED,IAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,aAAA,CAAc;AAAA,MAC5C,UAAA,EAAY,YAAA;AAAA,MACZ,OAAA,EAAS,QAAQ,eAAe,CAAA;AAAA,MAChC,IAAA,EAAM,WAAA;AAAA,MACN,KAAA,EAAO;AAAA,KACR,CAAA;AAED,IAAA,MAAM,MAAA,GAAS,CAAC,KAAA,KAA6C;AAC3D,MAAA,WAAA,CAAY,CAAC,IAAA,KAAS,CAAC,IAAI,CAAA;AAC3B,MAAA,QAAA,IAAA,IAAA,GAAA,MAAA,GAAA,QAAA,CAAW,KAAA,CAAA;AAAA,IACb,CAAA;AAEA,IAAA,uBACE,GAAA;AAAA,MAAC,gBAAA,CAAiB,QAAA;AAAA,MAAjB;AAAA,QACC,KAAA,EAAO;AAAA,UACL,KAAA;AAAA,UACA,MAAA;AAAA,UACA,QAAA;AAAA,UACA,aAAA;AAAA,UACA,QAAA,EAAU,QAAQ,QAAQ,CAAA;AAAA,UAC1B,QAAA;AAAA,UACA,WAAA;AAAA,UACA,OAAA;AAAA,UACA,UAAA;AAAA,UACA;AAAA,SACF;AAAA,QAEA,QAAA,kBAAA,GAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,GAAA;AAAA,YACA,SAAA,EAAW,IAAA;AAAA,cACT,YAAA,EAAa;AAAA,cACb;AAAA,gBACE,CAAC,YAAA,CAAa,MAAA,IAAU,EAAE,CAAC,GAAG,MAAA;AAAA,gBAC9B,CAAC,YAAA,CAAa,UAAU,CAAC,GAAG;AAAA,eAC9B;AAAA,cACA;AAAA,aACF;AAAA,YACC,GAAG;AAAA;AAAA;AACN;AAAA,KACF;AAAA,EAEJ;AACF;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccordionContext.js","sources":["../src/accordion/AccordionContext.ts"],"sourcesContent":["import { type SyntheticEvent, useContext } from \"react\";\nimport { createContext } from \"../utils\";\n\nexport interface AccordionContextValue {\n value
|
|
1
|
+
{"version":3,"file":"AccordionContext.js","sources":["../src/accordion/AccordionContext.ts"],"sourcesContent":["import { type SyntheticEvent, useContext } from \"react\";\nimport { createContext } from \"../utils\";\n\nexport interface AccordionContextValue {\n value?: string;\n expanded: boolean;\n toggle: (event: SyntheticEvent<HTMLButtonElement>) => void;\n disabled: boolean;\n indicatorSide: \"left\" | \"right\";\n headerId: string;\n setHeaderId: (id: string) => void;\n panelId: string;\n setPanelId: (id: string) => void;\n status?: \"error\" | \"warning\" | \"success\";\n}\n\nexport const AccordionContext = createContext<AccordionContextValue>(\n \"AccordionContext\",\n {\n value: \"\",\n expanded: false,\n toggle: () => undefined,\n disabled: false,\n indicatorSide: \"left\",\n headerId: \"\",\n setHeaderId: () => undefined,\n panelId: \"\",\n setPanelId: () => undefined,\n },\n);\n\nexport function useAccordion() {\n return useContext(AccordionContext);\n}\n"],"names":[],"mappings":";;;;;;;;;AAgBO,MAAM,gBAAA,GAAmB,aAAA;AAAA,EAC9B,kBAAA;AAAA,EACA;AAAA,IACE,KAAA,EAAO,EAAA;AAAA,IACP,QAAA,EAAU,KAAA;AAAA,IACV,QAAQ,MAAM,MAAA;AAAA,IACd,QAAA,EAAU,KAAA;AAAA,IACV,aAAA,EAAe,MAAA;AAAA,IACf,QAAA,EAAU,EAAA;AAAA,IACV,aAAa,MAAM,MAAA;AAAA,IACnB,OAAA,EAAS,EAAA;AAAA,IACT,YAAY,MAAM;AAAA;AAEtB;AAEO,SAAS,YAAA,GAAe;AAC7B,EAAA,OAAO,WAAW,gBAAgB,CAAA;AACpC;;;;"}
|
|
@@ -3,14 +3,26 @@ import { useEffect } from 'react';
|
|
|
3
3
|
import { useAriaAnnouncer } from './useAriaAnnouncer.js';
|
|
4
4
|
|
|
5
5
|
const AriaAnnounce = ({
|
|
6
|
-
announcement
|
|
6
|
+
announcement,
|
|
7
|
+
delay,
|
|
8
|
+
ariaLive,
|
|
9
|
+
duration,
|
|
10
|
+
target
|
|
7
11
|
}) => {
|
|
8
12
|
const { announce } = useAriaAnnouncer();
|
|
9
13
|
useEffect(() => {
|
|
10
14
|
if (announcement) {
|
|
11
|
-
|
|
15
|
+
if (delay !== void 0) {
|
|
16
|
+
announce(announcement, delay);
|
|
17
|
+
} else {
|
|
18
|
+
announce(announcement, {
|
|
19
|
+
...ariaLive ? { ariaLive } : {},
|
|
20
|
+
...duration !== void 0 ? { duration } : {},
|
|
21
|
+
...target ? { target } : {}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
12
24
|
}
|
|
13
|
-
}, [announce, announcement]);
|
|
25
|
+
}, [announce, announcement, ariaLive, delay, duration, target]);
|
|
14
26
|
return /* @__PURE__ */ jsx(Fragment, {});
|
|
15
27
|
};
|
|
16
28
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AriaAnnounce.js","sources":["../src/aria-announcer/AriaAnnounce.tsx"],"sourcesContent":["import { type ComponentType, useEffect } from \"react\";\
|
|
1
|
+
{"version":3,"file":"AriaAnnounce.js","sources":["../src/aria-announcer/AriaAnnounce.tsx"],"sourcesContent":["import { type ComponentType, useEffect } from \"react\";\nimport type { AnnounceFnOptions } from \"./AriaAnnouncerContext\";\nimport { useAriaAnnouncer } from \"./useAriaAnnouncer\";\n\nexport interface AriaAnnounceProps extends AnnounceFnOptions {\n /**\n * String to be announced by screenreader.\n */\n announcement?: string;\n /**\n * Legacy option, precede the announcement with a delay.\n * @deprecated\n * useAriaAnnouncer `delay` arg is deprecated, use your own `setTimeout` or consider using `duration` through `AnnounceFnOptions` instead.\n */\n delay?: number;\n}\n\nexport const AriaAnnounce: ComponentType<AriaAnnounceProps> = ({\n announcement,\n delay,\n ariaLive,\n duration,\n target,\n}) => {\n const { announce } = useAriaAnnouncer();\n\n useEffect(() => {\n if (announcement) {\n if (delay !== undefined) {\n announce(announcement, delay);\n } else {\n announce(announcement, {\n ...(ariaLive ? { ariaLive } : {}),\n ...(duration !== undefined ? { duration } : {}),\n ...(target ? { target } : {}),\n });\n }\n }\n }, [announce, announcement, ariaLive, delay, duration, target]);\n\n // biome-ignore lint/complexity/noUselessFragments: If we return null here, react-docgen wouldn't be able to locate the component.\n return <></>;\n};\n"],"names":[],"mappings":";;;;AAiBO,MAAM,eAAiD,CAAC;AAAA,EAC7D,YAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,KAAM;AACJ,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,gBAAA,EAAiB;AAEtC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,IAAI,UAAU,MAAA,EAAW;AACvB,QAAA,QAAA,CAAS,cAAc,KAAK,CAAA;AAAA,MAC9B,CAAA,MAAO;AACL,QAAA,QAAA,CAAS,YAAA,EAAc;AAAA,UACrB,GAAI,QAAA,GAAW,EAAE,QAAA,KAAa,EAAC;AAAA,UAC/B,GAAI,QAAA,KAAa,MAAA,GAAY,EAAE,QAAA,KAAa,EAAC;AAAA,UAC7C,GAAI,MAAA,GAAS,EAAE,MAAA,KAAW;AAAC,SAC5B,CAAA;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAA,EAAG,CAAC,QAAA,EAAU,YAAA,EAAc,UAAU,KAAA,EAAO,QAAA,EAAU,MAAM,CAAC,CAAA;AAG9D,EAAA,uBAAO,GAAA,CAAA,QAAA,EAAA,EAAE,CAAA;AACX;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AriaAnnouncerContext.js","sources":["../src/aria-announcer/AriaAnnouncerContext.ts"],"sourcesContent":["import { createContext } from \"react\";\n\nexport type
|
|
1
|
+
{"version":3,"file":"AriaAnnouncerContext.js","sources":["../src/aria-announcer/AriaAnnouncerContext.ts"],"sourcesContent":["import { type AriaAttributes, createContext } from \"react\";\n\nexport type AnnounceFnOptions = {\n /**\n * Assertiveness\n */\n ariaLive?: Exclude<AriaAttributes[\"aria-live\"], \"off\">;\n /**\n * How long (ms) the announcement should remain in the DOM before being removed.\n * Defaults to the provider's ANNOUNCEMENT_TIME_IN_DOM.\n */\n duration?: number;\n /**\n * Optional named target live region.\n * When set, announcements can be routed to a provider registered with the same target.\n */\n target?: string;\n};\n\nexport type AriaAnnouncer = {\n /**\n * TODO remove legacy `delay` arg (number) in favour of `options` (AnnounceFnOptions) as a breaking change\n */\n /**\n * Announcer function\n * @param announcement - announcement to queue for screenreader.\n * @param legacyDelayOrOptions, deprecated `delay` or `options` for announcement\n */\n announce: (\n announcement: string,\n legacyDelayOrOptions?: number | AnnounceFnOptions,\n ) => void;\n};\n\nexport const AriaAnnouncerContext = createContext<AriaAnnouncer | undefined>(\n undefined,\n);\n"],"names":[],"mappings":";;AAkCO,MAAM,oBAAA,GAAuB,aAAA;AAAA,EAClC;AACF;;;;"}
|
|
@@ -1,56 +1,73 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useState, useRef, useCallback,
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { forwardRef, useState, useRef, useCallback, useMemo } from 'react';
|
|
3
|
+
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect.js';
|
|
3
4
|
import { AriaAnnouncerContext } from './AriaAnnouncerContext.js';
|
|
5
|
+
import { registerAnnouncementTarget } from './announcementRegistry.js';
|
|
4
6
|
|
|
5
|
-
const
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const mountedRef = useRef(true);
|
|
14
|
-
const announceAll = useCallback(() => {
|
|
15
|
-
isAnnouncingRef.current = true;
|
|
16
|
-
if (mountedRef.current) {
|
|
17
|
-
setCurrentAnnouncement("");
|
|
18
|
-
requestAnimationFrame(() => {
|
|
19
|
-
if (mountedRef.current && announcementsRef.current.length) {
|
|
20
|
-
const announcement = announcementsRef.current.shift();
|
|
21
|
-
setCurrentAnnouncement(announcement);
|
|
22
|
-
setTimeout(() => {
|
|
23
|
-
announceAll();
|
|
24
|
-
}, ARIA_ANNOUNCE_DELAY);
|
|
25
|
-
} else {
|
|
26
|
-
isAnnouncingRef.current = false;
|
|
27
|
-
}
|
|
28
|
-
});
|
|
7
|
+
const ANNOUNCEMENT_TIME_IN_DOM = 300;
|
|
8
|
+
const AnnouncementRegion = forwardRef(function AnnouncementRegion2(props, ref) {
|
|
9
|
+
return /* @__PURE__ */ jsx(
|
|
10
|
+
"div",
|
|
11
|
+
{
|
|
12
|
+
"aria-atomic": false,
|
|
13
|
+
ref,
|
|
14
|
+
...props
|
|
29
15
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
const AriaAnnouncerProvider = forwardRef(function AriaAnnouncerProvider2({ children, style, target, ...rest }, ref) {
|
|
19
|
+
const [politeAnnouncements, setPoliteAnnouncements] = useState([]);
|
|
20
|
+
const [assertiveAnnouncements, setAssertiveAnnouncements] = useState([]);
|
|
21
|
+
const idCounterRef = useRef(0);
|
|
22
|
+
const makeAnnouncement = useCallback(
|
|
23
|
+
(message, assertiveness = "polite", duration = ANNOUNCEMENT_TIME_IN_DOM) => {
|
|
24
|
+
idCounterRef.current += 1;
|
|
25
|
+
const id = `announce-${assertiveness}-${Date.now()}-${idCounterRef.current}`;
|
|
26
|
+
if (assertiveness === "polite") {
|
|
27
|
+
setPoliteAnnouncements((previous) => {
|
|
28
|
+
return previous.concat({ id, message });
|
|
29
|
+
});
|
|
30
|
+
setTimeout(() => {
|
|
31
|
+
setPoliteAnnouncements(
|
|
32
|
+
(previous) => previous.filter((announcement) => announcement.id !== id)
|
|
33
|
+
);
|
|
34
|
+
}, duration);
|
|
35
|
+
} else {
|
|
36
|
+
setAssertiveAnnouncements((previous) => {
|
|
37
|
+
return previous.concat({ id, message });
|
|
38
|
+
});
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
setAssertiveAnnouncements(
|
|
41
|
+
(previous) => previous.filter((announcement) => announcement.id !== id)
|
|
42
|
+
);
|
|
43
|
+
}, duration);
|
|
36
44
|
}
|
|
37
45
|
},
|
|
38
|
-
[
|
|
46
|
+
[]
|
|
47
|
+
);
|
|
48
|
+
const announce = useCallback(
|
|
49
|
+
(announcement, legacyDelayOrOptions = {}) => {
|
|
50
|
+
const options = typeof legacyDelayOrOptions === "object" && legacyDelayOrOptions ? legacyDelayOrOptions : {};
|
|
51
|
+
makeAnnouncement(
|
|
52
|
+
announcement,
|
|
53
|
+
options.ariaLive,
|
|
54
|
+
options.duration ?? ANNOUNCEMENT_TIME_IN_DOM
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
[makeAnnouncement]
|
|
39
58
|
);
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
mountedRef.current = true;
|
|
42
|
-
return () => {
|
|
43
|
-
mountedRef.current = false;
|
|
44
|
-
};
|
|
45
|
-
}, []);
|
|
46
59
|
const value = useMemo(() => ({ announce }), [announce]);
|
|
60
|
+
useIsomorphicLayoutEffect(() => {
|
|
61
|
+
if (!target) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
return registerAnnouncementTarget(target, announce);
|
|
65
|
+
}, [announce, target]);
|
|
47
66
|
return /* @__PURE__ */ jsxs(AriaAnnouncerContext.Provider, { value, children: [
|
|
48
67
|
children,
|
|
49
|
-
/* @__PURE__ */
|
|
68
|
+
/* @__PURE__ */ jsxs(
|
|
50
69
|
"div",
|
|
51
70
|
{
|
|
52
|
-
"aria-atomic": "true",
|
|
53
|
-
"aria-live": "assertive",
|
|
54
71
|
style: {
|
|
55
72
|
position: "absolute",
|
|
56
73
|
height: 1,
|
|
@@ -63,11 +80,16 @@ function AriaAnnouncerProvider({
|
|
|
63
80
|
borderWidth: 0,
|
|
64
81
|
...style
|
|
65
82
|
},
|
|
66
|
-
|
|
83
|
+
...rest,
|
|
84
|
+
ref,
|
|
85
|
+
children: [
|
|
86
|
+
/* @__PURE__ */ jsx(AnnouncementRegion, { "aria-live": "polite", children: politeAnnouncements.map((announcement) => /* @__PURE__ */ jsx("div", { children: announcement.message }, `polite-${announcement.id}`)) }),
|
|
87
|
+
/* @__PURE__ */ jsx(AnnouncementRegion, { "aria-live": "assertive", children: assertiveAnnouncements.map((announcement) => /* @__PURE__ */ jsx("div", { children: announcement.message }, `assertive-${announcement.id}`)) })
|
|
88
|
+
]
|
|
67
89
|
}
|
|
68
90
|
)
|
|
69
91
|
] });
|
|
70
|
-
}
|
|
92
|
+
});
|
|
71
93
|
|
|
72
|
-
export {
|
|
94
|
+
export { ANNOUNCEMENT_TIME_IN_DOM, AriaAnnouncerProvider };
|
|
73
95
|
//# sourceMappingURL=AriaAnnouncerProvider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AriaAnnouncerProvider.js","sources":["../src/aria-announcer/AriaAnnouncerProvider.tsx"],"sourcesContent":["import {\n type
|
|
1
|
+
{"version":3,"file":"AriaAnnouncerProvider.js","sources":["../src/aria-announcer/AriaAnnouncerProvider.tsx"],"sourcesContent":["import {\n type ComponentPropsWithRef,\n forwardRef,\n useCallback,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { useIsomorphicLayoutEffect } from \"../utils/useIsomorphicLayoutEffect\";\nimport {\n type AnnounceFnOptions,\n AriaAnnouncerContext,\n} from \"./AriaAnnouncerContext\";\nimport { registerAnnouncementTarget } from \"./announcementRegistry\";\n\nexport const ANNOUNCEMENT_TIME_IN_DOM = 300; // time between DOM updates\n\nexport interface AriaAnnouncerProviderProps\n extends ComponentPropsWithRef<\"div\"> {\n /**\n * Optional target key used to route announcements to this provider.\n */\n target?: string;\n}\n\nconst AnnouncementRegion = forwardRef<\n HTMLDivElement,\n ComponentPropsWithRef<\"div\">\n>(function AnnouncementRegion(props, ref) {\n return (\n <div\n // Keep the region simple for maximum assistive-tech compatibility.\n // aria-live is applied by the caller (polite/assertive).\n aria-atomic={false}\n ref={ref}\n {...props}\n />\n );\n});\n\ntype AnnouncementMessage = {\n id: string;\n message: string;\n};\n\nexport const AriaAnnouncerProvider = forwardRef<\n HTMLDivElement,\n AriaAnnouncerProviderProps\n>(function AriaAnnouncerProvider({ children, style, target, ...rest }, ref) {\n const [politeAnnouncements, setPoliteAnnouncements] = useState<\n AnnouncementMessage[]\n >([]);\n const [assertiveAnnouncements, setAssertiveAnnouncements] = useState<\n AnnouncementMessage[]\n >([]);\n\n const idCounterRef = useRef(0);\n\n const makeAnnouncement = useCallback(\n (\n message: string,\n assertiveness: \"polite\" | \"assertive\" = \"polite\",\n duration: number = ANNOUNCEMENT_TIME_IN_DOM,\n ) => {\n idCounterRef.current += 1;\n // Date.now() can collide when multiple announcements are created in the same millisecond\n // (e.g. tests with cy.clock, batching, or multiple announces during one tick).\n // Add a monotonic counter suffix to guarantee uniqueness.\n const id = `announce-${assertiveness}-${Date.now()}-${idCounterRef.current}`;\n if (assertiveness === \"polite\") {\n setPoliteAnnouncements((previous) => {\n return previous.concat({ id, message });\n });\n\n setTimeout(() => {\n setPoliteAnnouncements((previous) =>\n previous.filter((announcement) => announcement.id !== id),\n );\n }, duration);\n } else {\n setAssertiveAnnouncements((previous) => {\n return previous.concat({ id, message });\n });\n\n setTimeout(() => {\n setAssertiveAnnouncements((previous) =>\n previous.filter((announcement) => announcement.id !== id),\n );\n }, duration);\n }\n },\n [],\n );\n\n const announce = useCallback(\n (\n announcement: string,\n legacyDelayOrOptions: number | AnnounceFnOptions | undefined = {},\n ) => {\n // Legacy delay (number arg) is handled by useAriaAnnouncer; if we also delayed\n // here we'd apply it twice. Keep supporting the signature but ignore the delay.\n const options: AnnounceFnOptions =\n typeof legacyDelayOrOptions === \"object\" && legacyDelayOrOptions\n ? legacyDelayOrOptions\n : {};\n\n makeAnnouncement(\n announcement,\n options.ariaLive,\n options.duration ?? ANNOUNCEMENT_TIME_IN_DOM,\n );\n },\n [makeAnnouncement],\n );\n\n const value = useMemo(() => ({ announce }), [announce]);\n\n useIsomorphicLayoutEffect(() => {\n if (!target) {\n return;\n }\n return registerAnnouncementTarget(target, announce);\n }, [announce, target]);\n\n return (\n <AriaAnnouncerContext.Provider value={value}>\n {children}\n <div\n // hidden styling based on https://tailwindcss.com/docs/screen-readers\n style={{\n position: \"absolute\",\n height: 1,\n width: 1,\n padding: 0,\n margin: -1,\n overflow: \"hidden\",\n clip: \"rect(0, 0, 0, 0)\",\n whiteSpace: \"nowrap\",\n borderWidth: 0,\n ...style,\n }}\n {...rest}\n ref={ref}\n >\n <AnnouncementRegion aria-live=\"polite\">\n {politeAnnouncements.map((announcement) => (\n <div key={`polite-${announcement.id}`}>{announcement.message}</div>\n ))}\n </AnnouncementRegion>\n <AnnouncementRegion aria-live=\"assertive\">\n {assertiveAnnouncements.map((announcement) => (\n <div key={`assertive-${announcement.id}`}>\n {announcement.message}\n </div>\n ))}\n </AnnouncementRegion>\n </div>\n </AriaAnnouncerContext.Provider>\n );\n});\n"],"names":["AnnouncementRegion","AriaAnnouncerProvider"],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const registry = /* @__PURE__ */ new Map();
|
|
2
|
+
function registerAnnouncementTarget(target, announce) {
|
|
3
|
+
const stack = registry.get(target) ?? [];
|
|
4
|
+
stack.push(announce);
|
|
5
|
+
registry.set(target, stack);
|
|
6
|
+
return () => {
|
|
7
|
+
const currentStack = registry.get(target);
|
|
8
|
+
if (!currentStack) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const index = currentStack.lastIndexOf(announce);
|
|
12
|
+
if (index !== -1) {
|
|
13
|
+
currentStack.splice(index, 1);
|
|
14
|
+
}
|
|
15
|
+
if (currentStack.length === 0) {
|
|
16
|
+
registry.delete(target);
|
|
17
|
+
} else {
|
|
18
|
+
registry.set(target, currentStack);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function getAnnouncementTarget(target) {
|
|
23
|
+
const stack = registry.get(target);
|
|
24
|
+
return stack == null ? void 0 : stack[stack.length - 1];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { getAnnouncementTarget, registerAnnouncementTarget };
|
|
28
|
+
//# sourceMappingURL=announcementRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"announcementRegistry.js","sources":["../src/aria-announcer/announcementRegistry.ts"],"sourcesContent":["import type { AnnounceFnOptions } from \"./AriaAnnouncerContext\";\n\ntype Announcer = (announcement: string, options?: AnnounceFnOptions) => void;\n\nconst registry = new Map<string, Announcer[]>();\n\nexport function registerAnnouncementTarget(\n target: string,\n announce: Announcer,\n): () => void {\n const stack = registry.get(target) ?? [];\n stack.push(announce);\n registry.set(target, stack);\n\n return () => {\n const currentStack = registry.get(target);\n if (!currentStack) {\n return;\n }\n\n // Remove the most recent matching announcer first (LIFO nested providers).\n const index = currentStack.lastIndexOf(announce);\n if (index !== -1) {\n currentStack.splice(index, 1);\n }\n\n if (currentStack.length === 0) {\n registry.delete(target);\n } else {\n registry.set(target, currentStack);\n }\n };\n}\n\nexport function getAnnouncementTarget(target: string): Announcer | undefined {\n const stack = registry.get(target);\n return stack?.[stack.length - 1];\n}\n"],"names":[],"mappings":"AAIA,MAAM,QAAA,uBAAe,GAAA,EAAyB;AAEvC,SAAS,0BAAA,CACd,QACA,QAAA,EACY;AACZ,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,GAAA,CAAI,MAAM,KAAK,EAAC;AACvC,EAAA,KAAA,CAAM,KAAK,QAAQ,CAAA;AACnB,EAAA,QAAA,CAAS,GAAA,CAAI,QAAQ,KAAK,CAAA;AAE1B,EAAA,OAAO,MAAM;AACX,IAAA,MAAM,YAAA,GAAe,QAAA,CAAS,GAAA,CAAI,MAAM,CAAA;AACxC,IAAA,IAAI,CAAC,YAAA,EAAc;AACjB,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,WAAA,CAAY,QAAQ,CAAA;AAC/C,IAAA,IAAI,UAAU,EAAA,EAAI;AAChB,MAAA,YAAA,CAAa,MAAA,CAAO,OAAO,CAAC,CAAA;AAAA,IAC9B;AAEA,IAAA,IAAI,YAAA,CAAa,WAAW,CAAA,EAAG;AAC7B,MAAA,QAAA,CAAS,OAAO,MAAM,CAAA;AAAA,IACxB,CAAA,MAAO;AACL,MAAA,QAAA,CAAS,GAAA,CAAI,QAAQ,YAAY,CAAA;AAAA,IACnC;AAAA,EACF,CAAA;AACF;AAEO,SAAS,sBAAsB,MAAA,EAAuC;AAC3E,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,GAAA,CAAI,MAAM,CAAA;AACjC,EAAA,OAAO,KAAA,IAAA,IAAA,GAAA,MAAA,GAAA,KAAA,CAAQ,MAAM,MAAA,GAAS,CAAA,CAAA;AAChC;;;;"}
|
|
@@ -1,25 +1,60 @@
|
|
|
1
|
-
import { useContext,
|
|
1
|
+
import { useContext, useCallback, useMemo } from 'react';
|
|
2
2
|
import { debounce } from '../utils/debounce.js';
|
|
3
|
+
import 'clsx';
|
|
4
|
+
import 'react/jsx-runtime';
|
|
5
|
+
import '../utils/useFloatingUI/useFloatingUI.js';
|
|
6
|
+
import '../utils/useId.js';
|
|
7
|
+
import '../salt-provider/SaltProvider.js';
|
|
8
|
+
import '../viewport/ViewportProvider.js';
|
|
3
9
|
import { AriaAnnouncerContext } from './AriaAnnouncerContext.js';
|
|
10
|
+
import { getAnnouncementTarget } from './announcementRegistry.js';
|
|
4
11
|
|
|
12
|
+
let warnedOnce = false;
|
|
13
|
+
let warnedLegacyOnce = false;
|
|
5
14
|
const useAriaAnnouncer = ({
|
|
6
15
|
debounce: debounceInterval = 0
|
|
7
16
|
} = {}) => {
|
|
8
17
|
const context = useContext(AriaAnnouncerContext);
|
|
9
|
-
const mountedRef = useRef(true);
|
|
10
18
|
const baseAnnounce = useCallback(
|
|
11
|
-
(announcement,
|
|
19
|
+
(announcement, delayOrOptions = {}) => {
|
|
20
|
+
const isLegacy = typeof delayOrOptions === "number";
|
|
21
|
+
let legacyDelay;
|
|
22
|
+
let options = {};
|
|
23
|
+
if (isLegacy) {
|
|
24
|
+
legacyDelay = delayOrOptions;
|
|
25
|
+
} else {
|
|
26
|
+
options = delayOrOptions;
|
|
27
|
+
}
|
|
12
28
|
const isReactAnnouncerInstalled = context == null ? void 0 : context.announce;
|
|
13
|
-
if (process.env.NODE_ENV !== "production")
|
|
29
|
+
if (process.env.NODE_ENV !== "production") {
|
|
30
|
+
if (legacyDelay !== void 0 && !warnedLegacyOnce) {
|
|
31
|
+
console.warn(
|
|
32
|
+
"useAriaAnnouncer `delay` prop is deprecated, use `duration` through `AnnounceFnOptions` instead."
|
|
33
|
+
);
|
|
34
|
+
warnedLegacyOnce = true;
|
|
35
|
+
}
|
|
36
|
+
if (!isReactAnnouncerInstalled && !warnedOnce) {
|
|
37
|
+
console.warn(
|
|
38
|
+
"useAriaAnnouncer is being used without an AriaAnnouncerProvider. Your application should be wrapped in an AriaAnnouncerProvider"
|
|
39
|
+
);
|
|
40
|
+
warnedOnce = true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
14
43
|
function makeAnnouncement() {
|
|
15
|
-
if (
|
|
16
|
-
|
|
17
|
-
|
|
44
|
+
if (options.target) {
|
|
45
|
+
const targetAnnouncer = getAnnouncementTarget(options.target);
|
|
46
|
+
if (targetAnnouncer) {
|
|
47
|
+
const { target: _target, ...targetOptions } = options;
|
|
48
|
+
targetAnnouncer(announcement, targetOptions);
|
|
49
|
+
return;
|
|
18
50
|
}
|
|
19
51
|
}
|
|
52
|
+
if (isReactAnnouncerInstalled) {
|
|
53
|
+
context.announce(announcement, isLegacy ? legacyDelay : options);
|
|
54
|
+
}
|
|
20
55
|
}
|
|
21
|
-
if (
|
|
22
|
-
setTimeout(makeAnnouncement,
|
|
56
|
+
if (legacyDelay) {
|
|
57
|
+
setTimeout(makeAnnouncement, legacyDelay);
|
|
23
58
|
} else {
|
|
24
59
|
makeAnnouncement();
|
|
25
60
|
}
|
|
@@ -30,20 +65,13 @@ const useAriaAnnouncer = ({
|
|
|
30
65
|
() => debounceInterval > 0 ? debounce(baseAnnounce, debounceInterval) : baseAnnounce,
|
|
31
66
|
[baseAnnounce, debounceInterval]
|
|
32
67
|
);
|
|
33
|
-
|
|
68
|
+
return useMemo(
|
|
34
69
|
() => ({
|
|
35
70
|
...context,
|
|
36
71
|
announce
|
|
37
72
|
}),
|
|
38
73
|
[context, announce]
|
|
39
74
|
);
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
mountedRef.current = true;
|
|
42
|
-
return () => {
|
|
43
|
-
mountedRef.current = false;
|
|
44
|
-
};
|
|
45
|
-
}, []);
|
|
46
|
-
return ariaAnnouncer;
|
|
47
75
|
};
|
|
48
76
|
|
|
49
77
|
export { useAriaAnnouncer };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAriaAnnouncer.js","sources":["../src/aria-announcer/useAriaAnnouncer.ts"],"sourcesContent":["import { useCallback, useContext,
|
|
1
|
+
{"version":3,"file":"useAriaAnnouncer.js","sources":["../src/aria-announcer/useAriaAnnouncer.ts"],"sourcesContent":["import { useCallback, useContext, useMemo } from \"react\";\nimport { debounce } from \"../utils\";\nimport {\n type AnnounceFnOptions,\n type AriaAnnouncer,\n AriaAnnouncerContext,\n} from \"./AriaAnnouncerContext\";\nimport { getAnnouncementTarget } from \"./announcementRegistry\";\n\nexport type useAnnouncerOptions = {\n debounce?: number;\n};\nexport type useAriaAnnouncerHook = (\n options?: useAnnouncerOptions,\n) => AriaAnnouncer;\n\nlet warnedOnce = false;\nlet warnedLegacyOnce = false;\n\nexport const useAriaAnnouncer: useAriaAnnouncerHook = ({\n debounce: debounceInterval = 0,\n} = {}) => {\n const context = useContext(AriaAnnouncerContext);\n const baseAnnounce = useCallback(\n (announcement: string, delayOrOptions: number | AnnounceFnOptions = {}) => {\n const isLegacy = typeof delayOrOptions === \"number\";\n let legacyDelay: number | undefined;\n let options: AnnounceFnOptions = {};\n /** TODO remove legacy `delay` arg (number) in favour of `options` (AnnounceFnOptions) as a breaking change */\n if (isLegacy) {\n legacyDelay = delayOrOptions as number;\n } else {\n options = delayOrOptions;\n }\n const isReactAnnouncerInstalled = context?.announce;\n\n if (process.env.NODE_ENV !== \"production\") {\n if (legacyDelay !== undefined && !warnedLegacyOnce) {\n console.warn(\n \"useAriaAnnouncer `delay` prop is deprecated, use `duration` through `AnnounceFnOptions` instead.\",\n );\n warnedLegacyOnce = true;\n }\n if (!isReactAnnouncerInstalled && !warnedOnce) {\n console.warn(\n \"useAriaAnnouncer is being used without an AriaAnnouncerProvider. Your application should be wrapped in an AriaAnnouncerProvider\",\n );\n warnedOnce = true;\n }\n }\n\n function makeAnnouncement() {\n if (options.target) {\n const targetAnnouncer = getAnnouncementTarget(options.target);\n if (targetAnnouncer) {\n const { target: _target, ...targetOptions } = options;\n targetAnnouncer(announcement, targetOptions);\n return;\n }\n }\n\n // Allow announcements from component cleanup.\n // React runs effect cleanups in parent->child ordering, so gating announcements on a\n // hook-level mounted flag can incorrectly block announcements that occur during unmount\n // (e.g. Spinner completionAnnouncement).\n if (isReactAnnouncerInstalled) {\n context.announce(announcement, isLegacy ? legacyDelay : options);\n }\n }\n\n if (legacyDelay) {\n setTimeout(makeAnnouncement, legacyDelay);\n } else {\n makeAnnouncement();\n }\n },\n [context],\n );\n\n const announce = useMemo(\n () =>\n debounceInterval > 0\n ? debounce(baseAnnounce, debounceInterval)\n : baseAnnounce,\n [baseAnnounce, debounceInterval],\n );\n\n return useMemo(\n () => ({\n ...context,\n announce,\n }),\n [context, announce],\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;AAgBA,IAAI,UAAA,GAAa,KAAA;AACjB,IAAI,gBAAA,GAAmB,KAAA;AAEhB,MAAM,mBAAyC,CAAC;AAAA,EACrD,UAAU,gBAAA,GAAmB;AAC/B,CAAA,GAAI,EAAC,KAAM;AACT,EAAA,MAAM,OAAA,GAAU,WAAW,oBAAoB,CAAA;AAC/C,EAAA,MAAM,YAAA,GAAe,WAAA;AAAA,IACnB,CAAC,YAAA,EAAsB,cAAA,GAA6C,EAAC,KAAM;AACzE,MAAA,MAAM,QAAA,GAAW,OAAO,cAAA,KAAmB,QAAA;AAC3C,MAAA,IAAI,WAAA;AACJ,MAAA,IAAI,UAA6B,EAAC;AAElC,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,WAAA,GAAc,cAAA;AAAA,MAChB,CAAA,MAAO;AACL,QAAA,OAAA,GAAU,cAAA;AAAA,MACZ;AACA,MAAA,MAAM,4BAA4B,OAAA,IAAA,IAAA,GAAA,MAAA,GAAA,OAAA,CAAS,QAAA;AAE3C,MAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,YAAA,EAAc;AACzC,QAAA,IAAI,WAAA,KAAgB,MAAA,IAAa,CAAC,gBAAA,EAAkB;AAClD,UAAA,OAAA,CAAQ,IAAA;AAAA,YACN;AAAA,WACF;AACA,UAAA,gBAAA,GAAmB,IAAA;AAAA,QACrB;AACA,QAAA,IAAI,CAAC,yBAAA,IAA6B,CAAC,UAAA,EAAY;AAC7C,UAAA,OAAA,CAAQ,IAAA;AAAA,YACN;AAAA,WACF;AACA,UAAA,UAAA,GAAa,IAAA;AAAA,QACf;AAAA,MACF;AAEA,MAAA,SAAS,gBAAA,GAAmB;AAC1B,QAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,UAAA,MAAM,eAAA,GAAkB,qBAAA,CAAsB,OAAA,CAAQ,MAAM,CAAA;AAC5D,UAAA,IAAI,eAAA,EAAiB;AACnB,YAAA,MAAM,EAAE,MAAA,EAAQ,OAAA,EAAS,GAAG,eAAc,GAAI,OAAA;AAC9C,YAAA,eAAA,CAAgB,cAAc,aAAa,CAAA;AAC3C,YAAA;AAAA,UACF;AAAA,QACF;AAMA,QAAA,IAAI,yBAAA,EAA2B;AAC7B,UAAA,OAAA,CAAQ,QAAA,CAAS,YAAA,EAAc,QAAA,GAAW,WAAA,GAAc,OAAO,CAAA;AAAA,QACjE;AAAA,MACF;AAEA,MAAA,IAAI,WAAA,EAAa;AACf,QAAA,UAAA,CAAW,kBAAkB,WAAW,CAAA;AAAA,MAC1C,CAAA,MAAO;AACL,QAAA,gBAAA,EAAiB;AAAA,MACnB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,OAAO;AAAA,GACV;AAEA,EAAA,MAAM,QAAA,GAAW,OAAA;AAAA,IACf,MACE,gBAAA,GAAmB,CAAA,GACf,QAAA,CAAS,YAAA,EAAc,gBAAgB,CAAA,GACvC,YAAA;AAAA,IACN,CAAC,cAAc,gBAAgB;AAAA,GACjC;AAEA,EAAA,OAAO,OAAA;AAAA,IACL,OAAO;AAAA,MACL,GAAG,OAAA;AAAA,MACH;AAAA,KACF,CAAA;AAAA,IACA,CAAC,SAAS,QAAQ;AAAA,GACpB;AACF;;;;"}
|
package/dist-es/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { AccordionHeader } from './accordion/AccordionHeader.js';
|
|
|
4
4
|
export { AccordionPanel } from './accordion/AccordionPanel.js';
|
|
5
5
|
export { AriaAnnounce } from './aria-announcer/AriaAnnounce.js';
|
|
6
6
|
export { AriaAnnouncerContext } from './aria-announcer/AriaAnnouncerContext.js';
|
|
7
|
-
export {
|
|
7
|
+
export { ANNOUNCEMENT_TIME_IN_DOM, AriaAnnouncerProvider } from './aria-announcer/AriaAnnouncerProvider.js';
|
|
8
8
|
export { useAriaAnnouncer } from './aria-announcer/useAriaAnnouncer.js';
|
|
9
9
|
export { Avatar } from './avatar/Avatar.js';
|
|
10
10
|
export { useAvatarImage } from './avatar/useAvatarImage.js';
|
|
@@ -5,6 +5,7 @@ import { clsx } from 'clsx';
|
|
|
5
5
|
import { forwardRef, useCallback, useMemo, useRef, useEffect } from 'react';
|
|
6
6
|
import { useAriaAnnouncer } from '../aria-announcer/useAriaAnnouncer.js';
|
|
7
7
|
import '../aria-announcer/AriaAnnouncerContext.js';
|
|
8
|
+
import '../aria-announcer/AriaAnnouncerProvider.js';
|
|
8
9
|
import { makePrefixer } from '../utils/makePrefixer.js';
|
|
9
10
|
import { useControlled } from '../utils/useControlled.js';
|
|
10
11
|
import '../utils/useFloatingUI/useFloatingUI.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pagination.js","sources":["../src/pagination/Pagination.tsx"],"sourcesContent":["import { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport {\n forwardRef,\n type HTMLAttributes,\n type SyntheticEvent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n} from \"react\";\nimport { useAriaAnnouncer } from \"../aria-announcer\";\nimport { makePrefixer, useControlled } from \"../utils\";\nimport paginationCss from \"./Pagination.css\";\nimport { type PaginationContext, paginationContext } from \"./PaginationContext\";\n\nconst withBaseName = makePrefixer(\"saltPagination\");\n\nconst { Provider } = paginationContext;\n\nexport interface PaginationProps extends HTMLAttributes<HTMLElement> {\n /**\n * Number of pages.\n */\n count: number;\n /**\n * Current/active page.\n */\n page?: number;\n /**\n * Default current/active page.\n */\n defaultPage?: number;\n /**\n * Callback function triggered when current page changed.\n */\n onPageChange?: (event: SyntheticEvent, page: number) => void;\n}\n\nexport const Pagination = forwardRef<HTMLElement, PaginationProps>(\n function Pagination(\n {\n className,\n count,\n children,\n defaultPage = 1,\n page: pageProp,\n onPageChange: onPageChangeProp,\n ...restProps\n },\n ref,\n ) {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-pagination\",\n css: paginationCss,\n window: targetWindow,\n });\n\n const [pageState, setPageState] = useControlled({\n controlled: pageProp,\n default: defaultPage,\n name: \"Pagination\",\n state: \"page\",\n });\n\n const onPageChange = useCallback(\n (event: SyntheticEvent, page: number) => {\n setPageState(page);\n onPageChangeProp?.(event, page);\n },\n [onPageChangeProp],\n );\n\n const contextValue: PaginationContext = useMemo(\n () => ({\n page: pageState,\n count,\n onPageChange,\n }),\n [pageState, count, onPageChange],\n );\n\n const { announce } = useAriaAnnouncer();\n const mounted = useRef<boolean>(false);\n\n useEffect(() => {\n if (mounted.current) {\n announce(`Page ${pageState}`);\n } else {\n mounted.current = true;\n }\n }, [announce, pageState]);\n\n if (count < 2) {\n return null;\n }\n\n return (\n <Provider value={contextValue}>\n <nav\n className={clsx(withBaseName(), className)}\n ref={ref}\n {...restProps}\n >\n {children}\n </nav>\n </Provider>\n );\n },\n);\n"],"names":["Pagination","paginationCss"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Pagination.js","sources":["../src/pagination/Pagination.tsx"],"sourcesContent":["import { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { clsx } from \"clsx\";\nimport {\n forwardRef,\n type HTMLAttributes,\n type SyntheticEvent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n} from \"react\";\nimport { useAriaAnnouncer } from \"../aria-announcer\";\nimport { makePrefixer, useControlled } from \"../utils\";\nimport paginationCss from \"./Pagination.css\";\nimport { type PaginationContext, paginationContext } from \"./PaginationContext\";\n\nconst withBaseName = makePrefixer(\"saltPagination\");\n\nconst { Provider } = paginationContext;\n\nexport interface PaginationProps extends HTMLAttributes<HTMLElement> {\n /**\n * Number of pages.\n */\n count: number;\n /**\n * Current/active page.\n */\n page?: number;\n /**\n * Default current/active page.\n */\n defaultPage?: number;\n /**\n * Callback function triggered when current page changed.\n */\n onPageChange?: (event: SyntheticEvent, page: number) => void;\n}\n\nexport const Pagination = forwardRef<HTMLElement, PaginationProps>(\n function Pagination(\n {\n className,\n count,\n children,\n defaultPage = 1,\n page: pageProp,\n onPageChange: onPageChangeProp,\n ...restProps\n },\n ref,\n ) {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"salt-pagination\",\n css: paginationCss,\n window: targetWindow,\n });\n\n const [pageState, setPageState] = useControlled({\n controlled: pageProp,\n default: defaultPage,\n name: \"Pagination\",\n state: \"page\",\n });\n\n const onPageChange = useCallback(\n (event: SyntheticEvent, page: number) => {\n setPageState(page);\n onPageChangeProp?.(event, page);\n },\n [onPageChangeProp],\n );\n\n const contextValue: PaginationContext = useMemo(\n () => ({\n page: pageState,\n count,\n onPageChange,\n }),\n [pageState, count, onPageChange],\n );\n\n const { announce } = useAriaAnnouncer();\n const mounted = useRef<boolean>(false);\n\n useEffect(() => {\n if (mounted.current) {\n announce(`Page ${pageState}`);\n } else {\n mounted.current = true;\n }\n }, [announce, pageState]);\n\n if (count < 2) {\n return null;\n }\n\n return (\n <Provider value={contextValue}>\n <nav\n className={clsx(withBaseName(), className)}\n ref={ref}\n {...restProps}\n >\n {children}\n </nav>\n </Provider>\n );\n },\n);\n"],"names":["Pagination","paginationCss"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,MAAM,YAAA,GAAe,aAAa,gBAAgB,CAAA;AAElD,MAAM,EAAE,UAAS,GAAI,iBAAA;AAqBd,MAAM,UAAA,GAAa,UAAA;AAAA,EACxB,SAASA,WAAAA,CACP;AAAA,IACE,SAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA,WAAA,GAAc,CAAA;AAAA,IACd,IAAA,EAAM,QAAA;AAAA,IACN,YAAA,EAAc,gBAAA;AAAA,IACd,GAAG;AAAA,KAEL,GAAA,EACA;AACA,IAAA,MAAM,eAAe,SAAA,EAAU;AAC/B,IAAA,wBAAA,CAAyB;AAAA,MACvB,MAAA,EAAQ,iBAAA;AAAA,MACR,GAAA,EAAKC,QAAA;AAAA,MACL,MAAA,EAAQ;AAAA,KACT,CAAA;AAED,IAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,aAAA,CAAc;AAAA,MAC9C,UAAA,EAAY,QAAA;AAAA,MACZ,OAAA,EAAS,WAAA;AAAA,MACT,IAAA,EAAM,YAAA;AAAA,MACN,KAAA,EAAO;AAAA,KACR,CAAA;AAED,IAAA,MAAM,YAAA,GAAe,WAAA;AAAA,MACnB,CAAC,OAAuB,IAAA,KAAiB;AACvC,QAAA,YAAA,CAAa,IAAI,CAAA;AACjB,QAAA,gBAAA,IAAA,IAAA,GAAA,MAAA,GAAA,gBAAA,CAAmB,KAAA,EAAO,IAAA,CAAA;AAAA,MAC5B,CAAA;AAAA,MACA,CAAC,gBAAgB;AAAA,KACnB;AAEA,IAAA,MAAM,YAAA,GAAkC,OAAA;AAAA,MACtC,OAAO;AAAA,QACL,IAAA,EAAM,SAAA;AAAA,QACN,KAAA;AAAA,QACA;AAAA,OACF,CAAA;AAAA,MACA,CAAC,SAAA,EAAW,KAAA,EAAO,YAAY;AAAA,KACjC;AAEA,IAAA,MAAM,EAAE,QAAA,EAAS,GAAI,gBAAA,EAAiB;AACtC,IAAA,MAAM,OAAA,GAAU,OAAgB,KAAK,CAAA;AAErC,IAAA,SAAA,CAAU,MAAM;AACd,MAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,QAAA,QAAA,CAAS,CAAA,KAAA,EAAQ,SAAS,CAAA,CAAE,CAAA;AAAA,MAC9B,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAAA,MACpB;AAAA,IACF,CAAA,EAAG,CAAC,QAAA,EAAU,SAAS,CAAC,CAAA;AAExB,IAAA,IAAI,QAAQ,CAAA,EAAG;AACb,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,uBACE,GAAA,CAAC,QAAA,EAAA,EAAS,KAAA,EAAO,YAAA,EACf,QAAA,kBAAA,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAW,IAAA,CAAK,YAAA,EAAa,EAAG,SAAS,CAAA;AAAA,QACzC,GAAA;AAAA,QACC,GAAG,SAAA;AAAA,QAEH;AAAA;AAAA,KACH,EACF,CAAA;AAAA,EAEJ;AACF;;;;"}
|
|
@@ -3,11 +3,13 @@ import { StyleInjectionProvider, useComponentCssInjection } from '@salt-ds/style
|
|
|
3
3
|
import { useWindow } from '@salt-ds/window';
|
|
4
4
|
import { clsx } from 'clsx';
|
|
5
5
|
import { createContext, useContext, useMemo } from 'react';
|
|
6
|
+
import '../utils/useFloatingUI/useFloatingUI.js';
|
|
7
|
+
import '../utils/useId.js';
|
|
8
|
+
import { ViewportProvider } from '../viewport/ViewportProvider.js';
|
|
6
9
|
import '../aria-announcer/AriaAnnouncerContext.js';
|
|
7
10
|
import { AriaAnnouncerProvider } from '../aria-announcer/AriaAnnouncerProvider.js';
|
|
8
11
|
import { useMatchedBreakpoints, BreakpointProvider } from '../breakpoints/BreakpointProvider.js';
|
|
9
12
|
import { DEFAULT_BREAKPOINTS } from '../breakpoints/Breakpoints.js';
|
|
10
|
-
import { ViewportProvider } from '../viewport/ViewportProvider.js';
|
|
11
13
|
import { ProviderContext } from './ProviderContext.js';
|
|
12
14
|
import css_248z from './SaltProvider.css.js';
|
|
13
15
|
import { ThemeApplicator } from './ThemeApplicator.js';
|