@dsn-starter-kit/components-react 3.4.0 → 4.0.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/dist/Card/Card.d.mts +86 -21
- package/dist/Card/Card.d.mts.map +1 -1
- package/dist/Card/Card2.mjs +100 -17
- package/dist/Card/Card2.mjs.map +1 -1
- package/dist/Card/index.d.mts +2 -2
- package/dist/DateInput/DateInput.d.mts +1 -1
- package/dist/DateInput/DateInput.d.mts.map +1 -1
- package/dist/DateInput/DateInput2.mjs +1 -2
- package/dist/DateInput/DateInput2.mjs.map +1 -1
- package/dist/DateInputGroup/DateInputGroup2.mjs +3 -3
- package/dist/DateInputGroup/DateInputGroup2.mjs.map +1 -1
- package/dist/Drawer/Drawer.d.mts +11 -3
- package/dist/Drawer/Drawer.d.mts.map +1 -1
- package/dist/Drawer/Drawer2.mjs +28 -4
- package/dist/Drawer/Drawer2.mjs.map +1 -1
- package/dist/EmailInput/EmailInput.d.mts +10 -5
- package/dist/EmailInput/EmailInput.d.mts.map +1 -1
- package/dist/EmailInput/EmailInput2.mjs +2 -2
- package/dist/EmailInput/EmailInput2.mjs.map +1 -1
- package/dist/ModalDialog/ModalDialog.d.mts +12 -2
- package/dist/ModalDialog/ModalDialog.d.mts.map +1 -1
- package/dist/ModalDialog/ModalDialog2.mjs +35 -8
- package/dist/ModalDialog/ModalDialog2.mjs.map +1 -1
- package/dist/NumberInput/NumberInput.d.mts +4 -4
- package/dist/NumberInput/NumberInput.d.mts.map +1 -1
- package/dist/NumberInput/NumberInput2.mjs +2 -2
- package/dist/NumberInput/NumberInput2.mjs.map +1 -1
- package/dist/PageHeader/PageHeader.d.mts.map +1 -1
- package/dist/PageHeader/PageHeader2.mjs +20 -2
- package/dist/PageHeader/PageHeader2.mjs.map +1 -1
- package/dist/PasswordInput/PasswordInput.d.mts +10 -5
- package/dist/PasswordInput/PasswordInput.d.mts.map +1 -1
- package/dist/PasswordInput/PasswordInput2.mjs +2 -2
- package/dist/PasswordInput/PasswordInput2.mjs.map +1 -1
- package/dist/SearchInput/SearchInput.d.mts +10 -5
- package/dist/SearchInput/SearchInput.d.mts.map +1 -1
- package/dist/SearchInput/SearchInput2.mjs +2 -3
- package/dist/SearchInput/SearchInput2.mjs.map +1 -1
- package/dist/Select/Select.d.mts +6 -6
- package/dist/Select/Select.d.mts.map +1 -1
- package/dist/Select/Select2.mjs +4 -5
- package/dist/Select/Select2.mjs.map +1 -1
- package/dist/TelephoneInput/TelephoneInput.d.mts +10 -5
- package/dist/TelephoneInput/TelephoneInput.d.mts.map +1 -1
- package/dist/TelephoneInput/TelephoneInput2.mjs +2 -2
- package/dist/TelephoneInput/TelephoneInput2.mjs.map +1 -1
- package/dist/TextArea/TextArea.d.mts +10 -5
- package/dist/TextArea/TextArea.d.mts.map +1 -1
- package/dist/TextArea/TextArea2.mjs +2 -2
- package/dist/TextArea/TextArea2.mjs.map +1 -1
- package/dist/TextInput/TextInput.d.mts +4 -4
- package/dist/TextInput/TextInput.d.mts.map +1 -1
- package/dist/TextInput/TextInput2.mjs +2 -2
- package/dist/TextInput/TextInput2.mjs.map +1 -1
- package/dist/TimeInput/TimeInput.d.mts +1 -1
- package/dist/TimeInput/TimeInput.d.mts.map +1 -1
- package/dist/TimeInput/TimeInput2.mjs +1 -2
- package/dist/TimeInput/TimeInput2.mjs.map +1 -1
- package/dist/index.cjs +303 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +5439 -3838
- package/dist/index.d.cts +163 -55
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/utils/focusTrap.mjs +99 -0
- package/dist/utils/focusTrap.mjs.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
//#region src/utils/focusTrap.ts
|
|
3
|
+
/**
|
|
4
|
+
* Focus-trap hulpfuncties voor dialoogachtige componenten.
|
|
5
|
+
*
|
|
6
|
+
* Gebaseerd op de techniek van Hidde de Vries:
|
|
7
|
+
* https://hidde.blog/using-javascript-to-trap-focus-in-an-element/
|
|
8
|
+
*
|
|
9
|
+
* Het native `<dialog>` element trapt de focus zelf zodra het via `.showModal()`
|
|
10
|
+
* geopend wordt. Die native trap is echter niet overal betrouwbaar: zodra het
|
|
11
|
+
* dialoogvenster in een `<iframe>` staat (Storybook, embeds, previews) laat
|
|
12
|
+
* Safari de focus alsnog over de iframe-grens ontsnappen naar de pagina
|
|
13
|
+
* eromheen. Deze expliciete trap staat daarbovenop en maakt het gedrag
|
|
14
|
+
* browseronafhankelijk.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Selector voor elementen die standaard in de tabvolgorde staan.
|
|
18
|
+
* Uitgeschakelde controls en `tabindex="-1"` vallen er bewust buiten.
|
|
19
|
+
*/
|
|
20
|
+
const FOCUSABLE_SELECTOR = [
|
|
21
|
+
"a[href]",
|
|
22
|
+
"area[href]",
|
|
23
|
+
"button:not(:disabled)",
|
|
24
|
+
"input:not(:disabled):not([type=\"hidden\"])",
|
|
25
|
+
"select:not(:disabled)",
|
|
26
|
+
"textarea:not(:disabled)",
|
|
27
|
+
"details > summary:first-of-type",
|
|
28
|
+
"iframe",
|
|
29
|
+
"audio[controls]",
|
|
30
|
+
"video[controls]",
|
|
31
|
+
"[contenteditable]:not([contenteditable=\"false\"])",
|
|
32
|
+
"[tabindex]:not([tabindex^=\"-\"])"
|
|
33
|
+
].join(", ");
|
|
34
|
+
/**
|
|
35
|
+
* Elementen die de focus niet kunnen krijgen omdat ze (of een voorouder)
|
|
36
|
+
* verborgen of inert zijn. `[hidden]` en `[inert]` halen een element uit de
|
|
37
|
+
* tabvolgorde, `[aria-hidden="true"]` hoort geen focusbare inhoud te bevatten.
|
|
38
|
+
*/
|
|
39
|
+
function isReachable(element) {
|
|
40
|
+
return !element.closest("[hidden], [inert], [aria-hidden=\"true\"]");
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Alle bereikbare focusbare elementen binnen een container, in DOM-volgorde.
|
|
44
|
+
*
|
|
45
|
+
* De trap staat of valt met die volgorde: het eerste en het laatste element
|
|
46
|
+
* bepalen waar Tab en Shift+Tab omslaan. Daarom lopen we alle elementen langs
|
|
47
|
+
* en toetsen we ze met `matches`, in plaats van `querySelectorAll` met de hele
|
|
48
|
+
* selectorlijst. Browsers geven daar documentvolgorde bij terug, maar jsdom
|
|
49
|
+
* groepeert het resultaat per losse selector, en dan zou de trap in tests
|
|
50
|
+
* ander gedrag vertonen dan in het echt.
|
|
51
|
+
*/
|
|
52
|
+
function getFocusableElements(container) {
|
|
53
|
+
return Array.from(container.querySelectorAll("*")).filter((element) => element.matches(FOCUSABLE_SELECTOR) && isReachable(element));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Houdt de toetsenbordfocus binnen `containerRef` zolang `active` waar is.
|
|
57
|
+
*
|
|
58
|
+
* De listener hangt aan de container zelf, niet aan het document: hij grijpt
|
|
59
|
+
* alleen in wanneer de focus al binnen de container zit, en laat de rest van de
|
|
60
|
+
* pagina met rust.
|
|
61
|
+
*
|
|
62
|
+
* Elementen met `tabindex="-1"` (zoals de heading die bij openen focus krijgt)
|
|
63
|
+
* staan niet in de tabvolgorde. Vanaf zo'n element wrapt Shift+Tab naar het
|
|
64
|
+
* laatste focusbare element, zodat de focus ook daar niet naar buiten valt.
|
|
65
|
+
*/
|
|
66
|
+
function useFocusTrap(containerRef, active) {
|
|
67
|
+
React.useEffect(() => {
|
|
68
|
+
if (!active) return;
|
|
69
|
+
const container = containerRef.current;
|
|
70
|
+
if (!container) return;
|
|
71
|
+
const handleKeyDown = (event) => {
|
|
72
|
+
if (event.key !== "Tab") return;
|
|
73
|
+
const focusable = getFocusableElements(container);
|
|
74
|
+
if (focusable.length === 0) {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const first = focusable[0];
|
|
79
|
+
const last = focusable[focusable.length - 1];
|
|
80
|
+
const current = document.activeElement;
|
|
81
|
+
const isInTabOrder = current ? focusable.includes(current) : false;
|
|
82
|
+
if (event.shiftKey) {
|
|
83
|
+
if (current === first || !isInTabOrder) {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
last.focus();
|
|
86
|
+
}
|
|
87
|
+
} else if (current === last) {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
first.focus();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
container.addEventListener("keydown", handleKeyDown);
|
|
93
|
+
return () => container.removeEventListener("keydown", handleKeyDown);
|
|
94
|
+
}, [active, containerRef]);
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { FOCUSABLE_SELECTOR, getFocusableElements, useFocusTrap };
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=focusTrap.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focusTrap.mjs","names":[],"sources":["../../src/utils/focusTrap.ts"],"sourcesContent":["import React from 'react';\n\n/**\n * Focus-trap hulpfuncties voor dialoogachtige componenten.\n *\n * Gebaseerd op de techniek van Hidde de Vries:\n * https://hidde.blog/using-javascript-to-trap-focus-in-an-element/\n *\n * Het native `<dialog>` element trapt de focus zelf zodra het via `.showModal()`\n * geopend wordt. Die native trap is echter niet overal betrouwbaar: zodra het\n * dialoogvenster in een `<iframe>` staat (Storybook, embeds, previews) laat\n * Safari de focus alsnog over de iframe-grens ontsnappen naar de pagina\n * eromheen. Deze expliciete trap staat daarbovenop en maakt het gedrag\n * browseronafhankelijk.\n */\n\n/**\n * Selector voor elementen die standaard in de tabvolgorde staan.\n * Uitgeschakelde controls en `tabindex=\"-1\"` vallen er bewust buiten.\n */\nexport const FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'area[href]',\n 'button:not(:disabled)',\n 'input:not(:disabled):not([type=\"hidden\"])',\n 'select:not(:disabled)',\n 'textarea:not(:disabled)',\n 'details > summary:first-of-type',\n 'iframe',\n 'audio[controls]',\n 'video[controls]',\n '[contenteditable]:not([contenteditable=\"false\"])',\n '[tabindex]:not([tabindex^=\"-\"])',\n].join(', ');\n\n/**\n * Elementen die de focus niet kunnen krijgen omdat ze (of een voorouder)\n * verborgen of inert zijn. `[hidden]` en `[inert]` halen een element uit de\n * tabvolgorde, `[aria-hidden=\"true\"]` hoort geen focusbare inhoud te bevatten.\n */\nfunction isReachable(element: HTMLElement): boolean {\n return !element.closest('[hidden], [inert], [aria-hidden=\"true\"]');\n}\n\n/**\n * Alle bereikbare focusbare elementen binnen een container, in DOM-volgorde.\n *\n * De trap staat of valt met die volgorde: het eerste en het laatste element\n * bepalen waar Tab en Shift+Tab omslaan. Daarom lopen we alle elementen langs\n * en toetsen we ze met `matches`, in plaats van `querySelectorAll` met de hele\n * selectorlijst. Browsers geven daar documentvolgorde bij terug, maar jsdom\n * groepeert het resultaat per losse selector, en dan zou de trap in tests\n * ander gedrag vertonen dan in het echt.\n */\nexport function getFocusableElements(container: HTMLElement): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>('*')).filter(\n (element) => element.matches(FOCUSABLE_SELECTOR) && isReachable(element)\n );\n}\n\n/**\n * Houdt de toetsenbordfocus binnen `containerRef` zolang `active` waar is.\n *\n * De listener hangt aan de container zelf, niet aan het document: hij grijpt\n * alleen in wanneer de focus al binnen de container zit, en laat de rest van de\n * pagina met rust.\n *\n * Elementen met `tabindex=\"-1\"` (zoals de heading die bij openen focus krijgt)\n * staan niet in de tabvolgorde. Vanaf zo'n element wrapt Shift+Tab naar het\n * laatste focusbare element, zodat de focus ook daar niet naar buiten valt.\n */\nexport function useFocusTrap(\n containerRef: React.RefObject<HTMLElement | null>,\n active: boolean\n): void {\n React.useEffect(() => {\n if (!active) return;\n const container = containerRef.current;\n if (!container) return;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Tab') return;\n\n const focusable = getFocusableElements(container);\n\n // Geen focusbare inhoud: houd de focus waar hij is\n if (focusable.length === 0) {\n event.preventDefault();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const current = document.activeElement as HTMLElement | null;\n const isInTabOrder = current ? focusable.includes(current) : false;\n\n if (event.shiftKey) {\n // Terug vanaf het eerste element, of vanaf een element buiten de\n // tabvolgorde zoals de heading, wrapt naar het laatste element\n if (current === first || !isInTabOrder) {\n event.preventDefault();\n last.focus();\n }\n } else if (current === last) {\n event.preventDefault();\n first.focus();\n }\n };\n\n container.addEventListener('keydown', handleKeyDown);\n return () => container.removeEventListener('keydown', handleKeyDown);\n }, [active, containerRef]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoBA,MAAa,qBAAqB;CAChC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,IAAI;;;;;;AAOX,SAAS,YAAY,SAA+B;CAClD,OAAO,CAAC,QAAQ,QAAQ,2CAAyC;AACnE;;;;;;;;;;;AAYA,SAAgB,qBAAqB,WAAuC;CAC1E,OAAO,MAAM,KAAK,UAAU,iBAA8B,GAAG,CAAC,CAAC,CAAC,QAC7D,YAAY,QAAQ,QAAQ,kBAAkB,KAAK,YAAY,OAAO,CACzE;AACF;;;;;;;;;;;;AAaA,SAAgB,aACd,cACA,QACM;CACN,MAAM,gBAAgB;EACpB,IAAI,CAAC,QAAQ;EACb,MAAM,YAAY,aAAa;EAC/B,IAAI,CAAC,WAAW;EAEhB,MAAM,iBAAiB,UAAyB;GAC9C,IAAI,MAAM,QAAQ,OAAO;GAEzB,MAAM,YAAY,qBAAqB,SAAS;GAGhD,IAAI,UAAU,WAAW,GAAG;IAC1B,MAAM,eAAe;IACrB;GACF;GAEA,MAAM,QAAQ,UAAU;GACxB,MAAM,OAAO,UAAU,UAAU,SAAS;GAC1C,MAAM,UAAU,SAAS;GACzB,MAAM,eAAe,UAAU,UAAU,SAAS,OAAO,IAAI;GAE7D,IAAI,MAAM,UAGJ;QAAA,YAAY,SAAS,CAAC,cAAc;KACtC,MAAM,eAAe;KACrB,KAAK,MAAM;IACb;UACK,IAAI,YAAY,MAAM;IAC3B,MAAM,eAAe;IACrB,MAAM,MAAM;GACd;EACF;EAEA,UAAU,iBAAiB,WAAW,aAAa;EACnD,aAAa,UAAU,oBAAoB,WAAW,aAAa;CACrE,GAAG,CAAC,QAAQ,YAAY,CAAC;AAC3B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsn-starter-kit/components-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "React components for the design system",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@dsn-starter-kit/
|
|
45
|
-
"@dsn-starter-kit/
|
|
44
|
+
"@dsn-starter-kit/design-tokens": "4.0.0",
|
|
45
|
+
"@dsn-starter-kit/core": "4.0.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"react": ">=18.0.0",
|