@pnx-mixtape/mxds 0.1.0-rc.4 → 0.1.0-rc.6
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/.claude/skills/create-mxds-component/SKILL.md +77 -0
- package/DESIGN.md +595 -0
- package/README.md +2 -3
- package/dist/build/accordion.entry.js +2 -1
- package/dist/build/autocomplete.entry.js +1 -1
- package/dist/build/back-to-top.css +1 -0
- package/dist/build/back-to-top.entry.js +1 -0
- package/dist/build/base.css +1 -1
- package/dist/build/button.css +1 -1
- package/dist/build/card.css +1 -1
- package/dist/build/chunks/Accordion-Bqe8vTNR.js +44 -0
- package/dist/build/chunks/AccordionGroup-BYtoAFxg.js +47 -0
- package/dist/build/chunks/AccordionMobile-D517oXYZ.js +26 -0
- package/dist/build/chunks/Autocomplete-kRo8RNbC.js +135 -0
- package/dist/build/chunks/BackToTop-4M-Wl7z3.js +64 -0
- package/dist/build/chunks/ClosableAlert-CZ114JsL.js +55 -0
- package/dist/build/chunks/CookieConsent-wd8gQ2Oe.js +18 -0
- package/dist/build/chunks/Dialog-DoYcGqhW.js +48 -0
- package/dist/build/chunks/DropMenu-CSTZq0Zt.js +68 -0
- package/dist/build/chunks/FeedbackForm-BsL7fv8s.js +64 -0
- package/dist/build/chunks/Gallery-DqJPujX5.js +165 -0
- package/dist/build/chunks/GlobalToggle-7H3F1Wvc.js +82 -0
- package/dist/build/chunks/InPageNavigation-D7m3IN89.js +86 -0
- package/dist/build/chunks/Navigation-CRModT40.js +142 -0
- package/dist/build/chunks/Popover-CiTAftNU.js +30 -0
- package/dist/build/chunks/Sticky-C1H8MNqK.js +51 -0
- package/dist/build/chunks/Tabs-BJD3kABL.js +160 -0
- package/dist/build/chunks/Translate-yAgySwet.js +46 -0
- package/dist/build/chunks/cookie-compliance-CG6x-TcV.js +119 -0
- package/dist/build/chunks/enums-BrHxPZZ_.js +36 -0
- package/dist/build/chunks/polyfills-D-qh1a_2.js +33 -0
- package/dist/build/chunks/utilities-B9mNRs7V.js +255 -0
- package/dist/build/content-block.css +1 -1
- package/dist/build/cookie-consent.entry.js +1 -1
- package/dist/build/dialog.entry.js +1 -1
- package/dist/build/drop-menu.entry.js +1 -1
- package/dist/build/elements.js +147 -1
- package/dist/build/feedback-form.css +1 -0
- package/dist/build/feedback-form.entry.js +1 -0
- package/dist/build/filters.entry.js +145 -1
- package/dist/build/form.css +1 -1
- package/dist/build/gallery.entry.js +1 -1
- package/dist/build/global-alert.entry.js +1 -1
- package/dist/build/header.css +1 -1
- package/dist/build/header.entry.js +1 -1
- package/dist/build/icon.css +1 -1
- package/dist/build/in-page-navigation.entry.js +1 -1
- package/dist/build/navigation.css +1 -1
- package/dist/build/navigation.entry.js +1 -1
- package/dist/build/pagination.css +1 -1
- package/dist/build/popover.entry.js +1 -1
- package/dist/build/react.js +2291 -1
- package/dist/build/side-navigation.css +1 -1
- package/dist/build/sidebar.css +1 -1
- package/dist/build/sticky.entry.js +1 -1
- package/dist/build/tabs.entry.js +1 -1
- package/dist/build/translate.css +1 -0
- package/dist/build/translate.entry.js +1 -0
- package/dist/build/utility-list.entry.js +53 -1
- package/dist/types/Component/BackToTop/BackToTop.d.ts +20 -0
- package/dist/types/Component/BackToTop/BackToTop.d.ts.map +1 -0
- package/dist/types/Component/BackToTop/Elements/BackToTop.d.ts +27 -0
- package/dist/types/Component/BackToTop/Elements/BackToTop.d.ts.map +1 -0
- package/dist/types/Component/BackToTop/back-to-top.entry.d.ts +2 -0
- package/dist/types/Component/BackToTop/back-to-top.entry.d.ts.map +1 -0
- package/dist/types/Component/DropMenu/DropMenu.d.ts +2 -1
- package/dist/types/Component/DropMenu/DropMenu.d.ts.map +1 -1
- package/dist/types/Component/Navigation/Elements/Navigation.d.ts +2 -3
- package/dist/types/Component/Navigation/Elements/Navigation.d.ts.map +1 -1
- package/dist/types/Component/SideNavigation/Components/SideNavigationTree.d.ts +3 -3
- package/dist/types/Component/SideNavigation/Components/SideNavigationTree.d.ts.map +1 -1
- package/dist/types/Component/SideNavigation/SideNavigation.d.ts +3 -1
- package/dist/types/Component/SideNavigation/SideNavigation.d.ts.map +1 -1
- package/dist/types/Component/Tabs/Components/TabDropMenu.d.ts.map +1 -1
- package/dist/types/Component/Tabs/Elements/Tabs.d.ts.map +1 -1
- package/dist/types/Component/Translate/Elements/Translate.d.ts +23 -0
- package/dist/types/Component/Translate/Elements/Translate.d.ts.map +1 -0
- package/dist/types/Component/Translate/Translate.d.ts +37 -0
- package/dist/types/Component/Translate/Translate.d.ts.map +1 -0
- package/dist/types/Component/Translate/languages.d.ts +11 -0
- package/dist/types/Component/Translate/languages.d.ts.map +1 -0
- package/dist/types/Component/Translate/translate.entry.d.ts +2 -0
- package/dist/types/Component/Translate/translate.entry.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/Elements/FeedbackForm.d.ts +37 -0
- package/dist/types/Form/FeedbackForm/Elements/FeedbackForm.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/FeedbackForm.d.ts +36 -0
- package/dist/types/Form/FeedbackForm/FeedbackForm.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/feedback-form.entry.d.ts +2 -0
- package/dist/types/Form/FeedbackForm/feedback-form.entry.d.ts.map +1 -0
- package/dist/types/Utility/Elements/google-translate-widget.d.ts +91 -0
- package/dist/types/Utility/Elements/google-translate-widget.d.ts.map +1 -0
- package/dist/types/Utility/utilities.d.ts +2 -0
- package/dist/types/Utility/utilities.d.ts.map +1 -1
- package/dist/types/elements.d.ts +3 -0
- package/dist/types/elements.d.ts.map +1 -1
- package/dist/types/enums.d.ts +3 -1
- package/dist/types/enums.d.ts.map +1 -1
- package/dist/types/react.d.ts +3 -0
- package/dist/types/react.d.ts.map +1 -1
- package/dist/types/tokens.d.ts +1 -0
- package/dist/types/tokens.d.ts.map +1 -1
- package/docs/API.md +1215 -0
- package/docs/CSSGuidelines.md +893 -0
- package/docs/CodeQuality.md +940 -0
- package/docs/ComponentArchitecture.md +714 -0
- package/docs/Contributing.md +626 -0
- package/docs/MemoryManagement.md +598 -0
- package/docs/Migrations.md +6 -0
- package/docs/README.md +100 -0
- package/docs/Security.md +444 -0
- package/docs/Testing.md +739 -0
- package/docs/Troubleshooting.md +674 -0
- package/package.json +9 -6
- package/src/Atom/Button/Button.tsx +1 -1
- package/src/Atom/Button/__snapshots__/Button.stories.tsx.snap +2 -2
- package/src/Atom/Button/_buttons.css +1 -0
- package/src/Atom/Icon/README.md +1 -1
- package/src/Atom/Icon/_icon.css +1 -0
- package/src/Atom/Icon/images/thumb.svg +1 -0
- package/src/Atom/Link/_links.css +1 -0
- package/src/Atom/Media/__snapshots__/Media.stories.ts.snap +4 -9
- package/src/Atom/Media/media.twig +4 -5
- package/src/Component/BackToTop/BackToTop.stories.ts +127 -0
- package/src/Component/BackToTop/BackToTop.stories.tsx +53 -0
- package/src/Component/BackToTop/BackToTop.tsx +120 -0
- package/src/Component/BackToTop/Elements/BackToTop.ts +119 -0
- package/src/Component/BackToTop/README.md +164 -0
- package/src/Component/BackToTop/__snapshots__/BackToTop.stories.ts.snap +24 -0
- package/src/Component/BackToTop/__snapshots__/BackToTop.stories.tsx.snap +3 -0
- package/src/Component/BackToTop/back-to-top.css +70 -0
- package/src/Component/BackToTop/back-to-top.entry.js +1 -0
- package/src/Component/BackToTop/back-to-top.twig +26 -0
- package/src/Component/Card/Components/CardContent.tsx +1 -1
- package/src/Component/Card/Components/CardMedia.tsx +1 -1
- package/src/Component/Card/__snapshots__/Card.stories.tsx.snap +3 -3
- package/src/Component/Card/card.css +1 -1
- package/src/Component/Carousel/Carousel.stories.ts +66 -36
- package/src/Component/Carousel/__snapshots__/Carousel.stories.tsx.snap +5 -5
- package/src/Component/ContentBlock/Components/ContentBlockMedia.tsx +1 -1
- package/src/Component/ContentBlock/content-block.css +1 -1
- package/src/Component/DropMenu/DropMenu.stories.ts +21 -2
- package/src/Component/DropMenu/DropMenu.tsx +14 -4
- package/src/Component/DropMenu/__snapshots__/DropMenu.stories.ts.snap +3 -3
- package/src/Component/DropMenu/__snapshots__/DropMenu.stories.tsx.snap +2 -2
- package/src/Component/DropMenu/drop-menu.twig +7 -1
- package/src/Component/HeroBanner/__snapshots__/HeroBanner.stories.tsx.snap +1 -1
- package/src/Component/InPageNavigation/__snapshots__/InPageNavigation.stories.ts.snap +48 -13
- package/src/Component/InPageNavigation/in-page-navigation.twig +4 -6
- package/src/Component/ListItem/Components/ListItemMedia.tsx +1 -1
- package/src/Component/Navigation/Elements/Navigation.ts +25 -29
- package/src/Component/Navigation/README.md +11 -10
- package/src/Component/Navigation/__snapshots__/Navigation.stories.ts.snap +8 -8
- package/src/Component/Navigation/_navigation-dropdown.css +9 -11
- package/src/Component/Navigation/_navigation-mega.css +5 -11
- package/src/Component/Navigation/_navigation.css +5 -3
- package/src/Component/Navigation/menu.twig +1 -1
- package/src/Component/Navigation/navigation.twig +0 -1
- package/src/Component/Pagination/Pagination.stories.ts +57 -27
- package/src/Component/Pagination/pagination.css +16 -7
- package/src/Component/Popover/__snapshots__/Popover.stories.tsx.snap +3 -3
- package/src/Component/SideNavigation/Components/SideNavigationTree.tsx +68 -24
- package/src/Component/SideNavigation/README.md +78 -32
- package/src/Component/SideNavigation/SideNavigation.stories.ts +41 -2
- package/src/Component/SideNavigation/SideNavigation.stories.tsx +31 -0
- package/src/Component/SideNavigation/SideNavigation.tsx +13 -3
- package/src/Component/SideNavigation/__snapshots__/SideNavigation.stories.ts.snap +87 -3
- package/src/Component/SideNavigation/__snapshots__/SideNavigation.stories.tsx.snap +2 -0
- package/src/Component/SideNavigation/side-navigation.css +3 -3
- package/src/Component/SideNavigation/side-navigation.twig +10 -3
- package/src/Component/Tabs/Components/TabDropMenu.tsx +7 -1
- package/src/Component/Tabs/Elements/Tabs.ts +13 -3
- package/src/Component/Tabs/Tabs.stories.ts +33 -12
- package/src/Component/Tabs/Tabs.stories.tsx +28 -14
- package/src/Component/Tabs/__snapshots__/Tabs.stories.ts.snap +3 -3
- package/src/Component/Tabs/__snapshots__/Tabs.stories.tsx.snap +2 -2
- package/src/Component/Translate/Elements/Translate.ts +76 -0
- package/src/Component/Translate/README.md +285 -0
- package/src/Component/Translate/Translate.stories.ts +87 -0
- package/src/Component/Translate/Translate.stories.tsx +77 -0
- package/src/Component/Translate/Translate.tsx +213 -0
- package/src/Component/Translate/__snapshots__/Translate.stories.ts.snap +18 -0
- package/src/Component/Translate/__snapshots__/Translate.stories.tsx.snap +3 -0
- package/src/Component/Translate/languages.ts +39 -0
- package/src/Component/Translate/translate.css +62 -0
- package/src/Component/Translate/translate.entry.js +1 -0
- package/src/Component/Translate/translate.twig +44 -0
- package/src/Form/Autocomplete/Autocomplete.stories.tsx +2 -1
- package/src/Form/FeedbackForm/Elements/FeedbackForm.ts +122 -0
- package/src/Form/FeedbackForm/FeedbackForm.stories.ts +99 -0
- package/src/Form/FeedbackForm/FeedbackForm.stories.tsx +73 -0
- package/src/Form/FeedbackForm/FeedbackForm.tsx +196 -0
- package/src/Form/FeedbackForm/README.md +233 -0
- package/src/Form/FeedbackForm/__snapshots__/FeedbackForm.stories.ts.snap +125 -0
- package/src/Form/FeedbackForm/__snapshots__/FeedbackForm.stories.tsx.snap +5 -0
- package/src/Form/FeedbackForm/feedback-form.css +74 -0
- package/src/Form/FeedbackForm/feedback-form.entry.js +1 -0
- package/src/Form/FeedbackForm/feedback-form.twig +80 -0
- package/src/Form/form.css +17 -19
- package/src/Introduction.mdx +1 -1
- package/src/Layout/Footer/__snapshots__/Footer.stories.ts.snap +3 -3
- package/src/Layout/Header/Components/GlobalToggle.tsx +1 -1
- package/src/Layout/Header/Header.stories.ts +53 -48
- package/src/Layout/Header/Header.stories.tsx +35 -22
- package/src/Layout/Header/Header.tsx +2 -2
- package/src/Layout/Header/__snapshots__/Header.stories.ts.snap +18 -14
- package/src/Layout/Header/__snapshots__/Header.stories.tsx.snap +2 -2
- package/src/Layout/Header/_toggles.css +7 -7
- package/src/Layout/Page/__snapshots__/Page.stories.tsx.snap +1 -1
- package/src/Layout/Sidebar/sidebar.css +10 -3
- package/src/Utility/Elements/google-translate-widget.ts +293 -0
- package/src/Utility/global.d.ts +4 -0
- package/src/Utility/utilities.ts +2 -0
- package/src/_custom-media.css +2 -0
- package/src/elements.ts +3 -0
- package/src/enums.ts +2 -0
- package/src/react.ts +3 -0
- package/src/tokens.js +1 -0
- package/dist/build/chunks/Accordion-GN0sME4W.js +0 -1
- package/dist/build/chunks/AccordionGroup-Bzqp5__q.js +0 -1
- package/dist/build/chunks/AccordionMobile-DG5jSe8Q.js +0 -1
- package/dist/build/chunks/Autocomplete-B4RtkAyh.js +0 -1
- package/dist/build/chunks/ClosableAlert-DEhj8Nxm.js +0 -1
- package/dist/build/chunks/CookieConsent-BkCMRVST.js +0 -1
- package/dist/build/chunks/Dialog-Haahn-Yn.js +0 -1
- package/dist/build/chunks/DropMenu-1baQc8Yc.js +0 -1
- package/dist/build/chunks/Gallery-D8NGXy2Q.js +0 -1
- package/dist/build/chunks/GlobalToggle-B52fdBQu.js +0 -1
- package/dist/build/chunks/InPageNavigation-Dyn2TR-N.js +0 -1
- package/dist/build/chunks/Navigation-hOrfLq7h.js +0 -1
- package/dist/build/chunks/Popover-Cxg-s5Jn.js +0 -1
- package/dist/build/chunks/Sticky-BGCuuEJK.js +0 -1
- package/dist/build/chunks/Tabs-DYy1YTdU.js +0 -15
- package/dist/build/chunks/cookie-compliance-Bbm-0vpj.js +0 -1
- package/dist/build/chunks/enums-DLV_1NAf.js +0 -1
- package/dist/build/chunks/polyfills-C7pRt54M.js +0 -1
- package/dist/build/chunks/utilities-_Bq3TxAQ.js +0 -1
package/docs/API.md
ADDED
|
@@ -0,0 +1,1215 @@
|
|
|
1
|
+
# API Documentation
|
|
2
|
+
|
|
3
|
+
Reference documentation for utilities and helper functions in the Mixtape Design System.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Validators](#validators)
|
|
8
|
+
- [Utilities](#utilities)
|
|
9
|
+
- [Helper Classes](#helper-classes)
|
|
10
|
+
- [Hooks](#hooks)
|
|
11
|
+
- [Elements](#elements)
|
|
12
|
+
- [React Components](#react-components)
|
|
13
|
+
- [Design Tokens](#design-tokens)
|
|
14
|
+
- [Enums](#enums)
|
|
15
|
+
- [Global Variables](#global-variables)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Validators
|
|
20
|
+
|
|
21
|
+
> Located in: `src/Utility/utilities.ts`
|
|
22
|
+
|
|
23
|
+
The `validators` object provides type-safe input validation without external dependencies.
|
|
24
|
+
|
|
25
|
+
### Import
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { validators } from "@pnx-mixtape/mxds/src/Utility/utilities"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### validators.url()
|
|
32
|
+
|
|
33
|
+
Validates a URL string and checks protocol.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
validators.url(value: string, allowedProtocols?: string[]): boolean
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Parameters:**
|
|
40
|
+
|
|
41
|
+
- `value` - URL string to validate
|
|
42
|
+
- `allowedProtocols` - Array of allowed protocols (default: `['http:', 'https:']`)
|
|
43
|
+
|
|
44
|
+
**Returns:** `true` if valid URL with allowed protocol
|
|
45
|
+
|
|
46
|
+
**Examples:**
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
validators.url("https://example.com") // true
|
|
50
|
+
validators.url("http://example.com") // true
|
|
51
|
+
validators.url("javascript:alert(1)") // false
|
|
52
|
+
validators.url("ftp://example.com") // false
|
|
53
|
+
validators.url("ftp://example.com", ["ftp:", "ftps:"]) // true
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
### validators.email()
|
|
59
|
+
|
|
60
|
+
Validates email address format.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
validators.email(value: string): boolean
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Parameters:**
|
|
67
|
+
|
|
68
|
+
- `value` - Email string to validate
|
|
69
|
+
|
|
70
|
+
**Returns:** `true` if valid email format
|
|
71
|
+
|
|
72
|
+
**Examples:**
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
validators.email("user@example.com") // true
|
|
76
|
+
validators.email("invalid-email") // false
|
|
77
|
+
validators.email("user@domain") // false
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
### validators.number()
|
|
83
|
+
|
|
84
|
+
Validates a number with optional min/max bounds.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
validators.number(value: any, min?: number, max?: number): boolean
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Parameters:**
|
|
91
|
+
|
|
92
|
+
- `value` - Value to validate as number
|
|
93
|
+
- `min` - Optional minimum value (inclusive)
|
|
94
|
+
- `max` - Optional maximum value (inclusive)
|
|
95
|
+
|
|
96
|
+
**Returns:** `true` if valid number within bounds
|
|
97
|
+
|
|
98
|
+
**Examples:**
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
validators.number(5) // true
|
|
102
|
+
validators.number("5") // true (converts)
|
|
103
|
+
validators.number(5, 1, 10) // true
|
|
104
|
+
validators.number(15, 1, 10) // false
|
|
105
|
+
validators.number("abc") // false
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### validators.integer()
|
|
111
|
+
|
|
112
|
+
Validates an integer with optional min/max bounds.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
validators.integer(value: any, min?: number, max?: number): boolean
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Parameters:**
|
|
119
|
+
|
|
120
|
+
- `value` - Value to validate as integer
|
|
121
|
+
- `min` - Optional minimum value (inclusive)
|
|
122
|
+
- `max` - Optional maximum value (inclusive)
|
|
123
|
+
|
|
124
|
+
**Returns:** `true` if valid integer within bounds
|
|
125
|
+
|
|
126
|
+
**Examples:**
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
validators.integer(5, 1, 10) // true
|
|
130
|
+
validators.integer(5.5, 1, 10) // false (not integer)
|
|
131
|
+
validators.integer(-1, 1, 10) // false (below min)
|
|
132
|
+
validators.integer("5", 1, 10) // true (converts)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
### validators.string()
|
|
138
|
+
|
|
139
|
+
Validates a string with optional length constraints.
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
validators.string(value: any, minLength?: number, maxLength?: number): boolean
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Parameters:**
|
|
146
|
+
|
|
147
|
+
- `value` - Value to validate as string
|
|
148
|
+
- `minLength` - Optional minimum length
|
|
149
|
+
- `maxLength` - Optional maximum length
|
|
150
|
+
|
|
151
|
+
**Returns:** `true` if valid string within length bounds
|
|
152
|
+
|
|
153
|
+
**Examples:**
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
validators.string("hello", 1, 10) // true
|
|
157
|
+
validators.string("", 1, 10) // false
|
|
158
|
+
validators.string("toolongstring", 1, 5) // false
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### validators.notEmpty()
|
|
164
|
+
|
|
165
|
+
Validates a string is not empty (after trimming).
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
validators.notEmpty(value: string): boolean
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Parameters:**
|
|
172
|
+
|
|
173
|
+
- `value` - String to validate
|
|
174
|
+
|
|
175
|
+
**Returns:** `true` if string has content after trimming
|
|
176
|
+
|
|
177
|
+
**Examples:**
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
validators.notEmpty("hello") // true
|
|
181
|
+
validators.notEmpty(" ") // false
|
|
182
|
+
validators.notEmpty("") // false
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
### validators.pattern()
|
|
188
|
+
|
|
189
|
+
Validates a value matches a regex pattern.
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
validators.pattern(value: string, pattern: RegExp): boolean
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Parameters:**
|
|
196
|
+
|
|
197
|
+
- `value` - String to validate
|
|
198
|
+
- `pattern` - Regex pattern to match
|
|
199
|
+
|
|
200
|
+
**Returns:** `true` if value matches pattern
|
|
201
|
+
|
|
202
|
+
**Examples:**
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
validators.pattern("abc123", /^[a-z0-9]+$/) // true
|
|
206
|
+
validators.pattern("ABC", /^[a-z]+$/) // false
|
|
207
|
+
validators.pattern("test@example.com", /^[\w.]+@[\w.]+$/) // true
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
### validators.oneOf()
|
|
213
|
+
|
|
214
|
+
Validates a value is one of the allowed values.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
validators.oneOf<T>(value: T, allowedValues: T[]): boolean
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Parameters:**
|
|
221
|
+
|
|
222
|
+
- `value` - Value to check
|
|
223
|
+
- `allowedValues` - Array of allowed values
|
|
224
|
+
|
|
225
|
+
**Returns:** `true` if value is in allowed list
|
|
226
|
+
|
|
227
|
+
**Examples:**
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
validators.oneOf("red", ["red", "green", "blue"]) // true
|
|
231
|
+
validators.oneOf("yellow", ["red", "green", "blue"]) // false
|
|
232
|
+
validators.oneOf(5, [1, 5, 10]) // true
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### validators.array()
|
|
238
|
+
|
|
239
|
+
Validates an array with optional length constraints.
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
validators.array(value: any, minLength?: number, maxLength?: number): boolean
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Parameters:**
|
|
246
|
+
|
|
247
|
+
- `value` - Value to validate as array
|
|
248
|
+
- `minLength` - Optional minimum array length
|
|
249
|
+
- `maxLength` - Optional maximum array length
|
|
250
|
+
|
|
251
|
+
**Returns:** `true` if valid array within length bounds
|
|
252
|
+
|
|
253
|
+
**Examples:**
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
validators.array([1, 2, 3], 1, 5) // true
|
|
257
|
+
validators.array([], 1, 5) // false
|
|
258
|
+
validators.array([1, 2, 3], 5, 10) // false
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
### validators.json()
|
|
264
|
+
|
|
265
|
+
Validates a value is a valid JSON string.
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
validators.json(value: string): boolean
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Parameters:**
|
|
272
|
+
|
|
273
|
+
- `value` - String to validate as JSON
|
|
274
|
+
|
|
275
|
+
**Returns:** `true` if valid JSON string
|
|
276
|
+
|
|
277
|
+
**Examples:**
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
validators.json('{"key": "value"}') // true
|
|
281
|
+
validators.json("invalid{json") // false
|
|
282
|
+
validators.json("[1, 2, 3]") // true
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### validators.date()
|
|
288
|
+
|
|
289
|
+
Validates a date string or Date object.
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
validators.date(value: string | Date, minDate?: Date, maxDate?: Date): boolean
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Parameters:**
|
|
296
|
+
|
|
297
|
+
- `value` - Date string or Date object to validate
|
|
298
|
+
- `minDate` - Optional minimum date
|
|
299
|
+
- `maxDate` - Optional maximum date
|
|
300
|
+
|
|
301
|
+
**Returns:** `true` if valid date within bounds
|
|
302
|
+
|
|
303
|
+
**Examples:**
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
validators.date("2024-01-01") // true
|
|
307
|
+
validators.date("invalid-date") // false
|
|
308
|
+
validators.date(new Date()) // true
|
|
309
|
+
validators.date("2024-06-15", new Date("2024-01-01"), new Date("2024-12-31")) // true
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
### validators.hexColor()
|
|
315
|
+
|
|
316
|
+
Validates a hex color code.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
validators.hexColor(value: string): boolean
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Parameters:**
|
|
323
|
+
|
|
324
|
+
- `value` - Color string to validate
|
|
325
|
+
|
|
326
|
+
**Returns:** `true` if valid hex color (3 or 6 digits)
|
|
327
|
+
|
|
328
|
+
**Examples:**
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
validators.hexColor("#fff") // true
|
|
332
|
+
validators.hexColor("#ffffff") // true
|
|
333
|
+
validators.hexColor("#FF0000") // true
|
|
334
|
+
validators.hexColor("fff") // false (missing #)
|
|
335
|
+
validators.hexColor("#gggggg") // false (invalid hex)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Utilities
|
|
341
|
+
|
|
342
|
+
### makeAnchor()
|
|
343
|
+
|
|
344
|
+
Converts a string to a valid HTML ID.
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
makeAnchor(string: string, length?: number): string
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**Parameters:**
|
|
351
|
+
|
|
352
|
+
- `string` - String to convert
|
|
353
|
+
- `length` - Maximum length (default: 20)
|
|
354
|
+
|
|
355
|
+
**Returns:** Valid HTML ID string
|
|
356
|
+
|
|
357
|
+
**Examples:**
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
makeAnchor("Hello World") // 'hello-world'
|
|
361
|
+
makeAnchor("Hello World", 5) // 'hello'
|
|
362
|
+
makeAnchor("Test 123!") // 'test-123'
|
|
363
|
+
makeAnchor(" spaces ") // 'spaces'
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
### makeCamelCase()
|
|
369
|
+
|
|
370
|
+
Converts a string to camelCase.
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
makeCamelCase(string: string): string
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Parameters:**
|
|
377
|
+
|
|
378
|
+
- `string` - String to convert
|
|
379
|
+
|
|
380
|
+
**Returns:** camelCase string
|
|
381
|
+
|
|
382
|
+
**Examples:**
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
makeCamelCase("hello world") // 'helloWorld'
|
|
386
|
+
makeCamelCase("Hello World") // 'helloWorld'
|
|
387
|
+
makeCamelCase("hello-world") // 'helloWorld'
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
### isInternalLink()
|
|
393
|
+
|
|
394
|
+
Checks if a URL is internal to the current site.
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
isInternalLink(href: string, internalHostnames: string[]): boolean
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Parameters:**
|
|
401
|
+
|
|
402
|
+
- `href` - URL to check
|
|
403
|
+
- `internalHostnames` - Array of internal hostnames
|
|
404
|
+
|
|
405
|
+
**Returns:** `true` if URL is internal
|
|
406
|
+
|
|
407
|
+
**Examples:**
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
isInternalLink("https://example.com/page", ["example.com"]) // true
|
|
411
|
+
isInternalLink("https://other.com/page", ["example.com"]) // false
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
### createElement()
|
|
417
|
+
|
|
418
|
+
Safely creates an HTML element from a string.
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
createElement(string: string): Element | null
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**Parameters:**
|
|
425
|
+
|
|
426
|
+
- `string` - HTML string to parse
|
|
427
|
+
|
|
428
|
+
**Returns:** Parsed Element or null
|
|
429
|
+
|
|
430
|
+
**Examples:**
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
const el = createElement('<div class="test">Content</div>')
|
|
434
|
+
// Returns: <div class="test">Content</div>
|
|
435
|
+
|
|
436
|
+
const unsafe = createElement("<script>alert(1)</script>")
|
|
437
|
+
// Returns: <script> element (but script doesn't execute)
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
### handleOutsideClick()
|
|
443
|
+
|
|
444
|
+
Handles clicks outside a container.
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
handleOutsideClick(
|
|
448
|
+
event: MouseEvent,
|
|
449
|
+
container: HTMLElement,
|
|
450
|
+
callback: () => void,
|
|
451
|
+
ignored?: HTMLElement[]
|
|
452
|
+
): void
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**Parameters:**
|
|
456
|
+
|
|
457
|
+
- `event` - Mouse event
|
|
458
|
+
- `container` - Container element
|
|
459
|
+
- `callback` - Function to call if outside
|
|
460
|
+
- `ignored` - Elements to ignore (optional)
|
|
461
|
+
|
|
462
|
+
**Examples:**
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
document.addEventListener('click', (e) => {
|
|
466
|
+
handleOutsideClick(e, dialog, () => {
|
|
467
|
+
dialog.close()
|
|
468
|
+
}, [triggerButton])
|
|
469
|
+
})
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
### handleEscape()
|
|
475
|
+
|
|
476
|
+
Handles Escape key press.
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
handleEscape(event: KeyboardEvent, callback: () => void): void
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**Parameters:**
|
|
483
|
+
|
|
484
|
+
- `event` - Keyboard event
|
|
485
|
+
- `callback` - Function to call on Escape
|
|
486
|
+
|
|
487
|
+
**Examples:**
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
document.addEventListener("keydown", e => {
|
|
491
|
+
handleEscape(e, () => {
|
|
492
|
+
modal.close()
|
|
493
|
+
})
|
|
494
|
+
})
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
### getElementBox()
|
|
500
|
+
|
|
501
|
+
Observes an element's dimensions via `ResizeObserver` and writes them as CSS custom properties on the element itself. Useful for container-query-style layouts without native container queries.
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
getElementBox(element: HTMLElement, once?: boolean, content?: boolean): void
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
**Parameters:**
|
|
508
|
+
|
|
509
|
+
- `element` - Element to observe
|
|
510
|
+
- `once` - If `true`, stops observing after the first measurement (default: `false`)
|
|
511
|
+
- `content` - If `true`, measures `contentBoxSize` instead of `borderBoxSize` (default: `false`)
|
|
512
|
+
|
|
513
|
+
**Sets on the element:**
|
|
514
|
+
|
|
515
|
+
- `--inline-size` — current inline (horizontal) size in `px`
|
|
516
|
+
- `--block-size` — current block (vertical) size in `px`
|
|
517
|
+
|
|
518
|
+
**Examples:**
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
// Continuously track size
|
|
522
|
+
getElementBox(containerEl)
|
|
523
|
+
|
|
524
|
+
// Measure once (e.g. for initial layout)
|
|
525
|
+
getElementBox(containerEl, true)
|
|
526
|
+
|
|
527
|
+
// Use in CSS
|
|
528
|
+
// .mx-component { width: calc(var(--inline-size) / 2); }
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
### generatedId()
|
|
534
|
+
|
|
535
|
+
Generates a stable HTML-safe ID from a Web Component's text content. Used internally by Web Components when an element has no existing `id`.
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
private generatedId(): string
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
**Returns:** A kebab-case HTML ID string derived from the component's text content, via `makeAnchor()`.
|
|
542
|
+
|
|
543
|
+
**Examples:**
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
// Inside a Web Component:
|
|
547
|
+
details.id = details.id || this.generatedId()
|
|
548
|
+
// If textContent is "My Accordion Section", produces: "my-accordion-section"
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**Note:** This is a private method on Web Component classes. It is not exported from the utilities module. Use `makeAnchor()` directly if you need the same behaviour outside a component.
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## Helper Classes
|
|
556
|
+
|
|
557
|
+
### DisclosureWidget
|
|
558
|
+
|
|
559
|
+
Manages disclosure patterns (show/hide content) using an `inert` attribute toggle and `aria-expanded` state.
|
|
560
|
+
|
|
561
|
+
```typescript
|
|
562
|
+
import { DisclosureWidget } from "@pnx-mixtape/mxds/src/Utility/utilities"
|
|
563
|
+
|
|
564
|
+
const widget = new DisclosureWidget(trigger, target, options?, context?)
|
|
565
|
+
widget.init() // Attach and set initial state (idempotent — safe to call multiple times)
|
|
566
|
+
widget.attach() // Add event listeners
|
|
567
|
+
widget.detach() // Remove event listeners and close if open
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
**Constructor:**
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
new DisclosureWidget(
|
|
574
|
+
trigger: Element, // The button/trigger element
|
|
575
|
+
element: Element, // The content element to show/hide
|
|
576
|
+
options?: DisclosureWidgetOptions, // Optional overrides (see below)
|
|
577
|
+
context?: Element | Document, // Default: document
|
|
578
|
+
)
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
**Options (`DisclosureWidgetOptions`):**
|
|
582
|
+
|
|
583
|
+
| Option | Type | Default | Description |
|
|
584
|
+
|---|---|---|---|
|
|
585
|
+
| `attribute` | `string` | `"inert"` | HTML attribute toggled on the target element |
|
|
586
|
+
| `shouldPreventDefault` | `boolean` | `true` | Call `preventDefault()` on trigger events |
|
|
587
|
+
| `setInitialAttribute` | `boolean` | `true` | Set the attribute on init (starts closed) |
|
|
588
|
+
| `clickEvent` | `"click" \| "mouseover"` | `"click"` | Event type to trigger toggle |
|
|
589
|
+
|
|
590
|
+
**Custom Event — `disclosure-toggle`:**
|
|
591
|
+
|
|
592
|
+
Dispatched on the trigger element after each toggle:
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
trigger.addEventListener("disclosure-toggle", (e: CustomEvent<DisclosureWidgetEvent>) => {
|
|
596
|
+
const { isOpen, id, options, target } = e.detail
|
|
597
|
+
})
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
type DisclosureWidgetEvent = {
|
|
602
|
+
isOpen: boolean // Whether the widget is now open
|
|
603
|
+
id: string // ID of the target element
|
|
604
|
+
options: DisclosureWidgetOptions
|
|
605
|
+
target: Element // The content element
|
|
606
|
+
}
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
**Used in:** Accordion, Tabs, Dialog
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
613
|
+
### Keyboard
|
|
614
|
+
|
|
615
|
+
Manages keyboard navigation (arrow keys, Escape) for menus and popup overlays. Uses `window.Mixtape` to coordinate state across components — see [Global Variables → Mixtape](#global-variables).
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
import { Keyboard } from "@pnx-mixtape/mxds/src/Utility/utilities"
|
|
619
|
+
|
|
620
|
+
const keyboard = new Keyboard()
|
|
621
|
+
|
|
622
|
+
// Register a popup so Escape closes it
|
|
623
|
+
keyboard.attachPopup(id, closefn)
|
|
624
|
+
keyboard.detachPopup(id)
|
|
625
|
+
|
|
626
|
+
// Register a menu for arrow-key navigation
|
|
627
|
+
keyboard.attachMenu(container, items)
|
|
628
|
+
keyboard.detachMenu()
|
|
629
|
+
|
|
630
|
+
// Attach global click + keydown listeners (pass AbortSignal for cleanup)
|
|
631
|
+
keyboard.attachEventListeners(signal)
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
**Key bindings handled by `attachEventListeners`:**
|
|
635
|
+
|
|
636
|
+
| Key | Behaviour |
|
|
637
|
+
|---|---|
|
|
638
|
+
| `Escape` | Calls `closefn` on the most recently attached popup |
|
|
639
|
+
| `ArrowDown` / `ArrowRight` | Moves focus forward in attached menu (wraps) |
|
|
640
|
+
| `ArrowUp` / `ArrowLeft` | Moves focus backward in attached menu (wraps) |
|
|
641
|
+
|
|
642
|
+
**Note:** `attachEventListeners(signal)` must be called after `attachMenu`/`attachPopup`. Pass an `AbortSignal` so listeners are cleaned up automatically.
|
|
643
|
+
|
|
644
|
+
**Used in:** Navigation, DropMenu, Tabs
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
|
|
648
|
+
### CookieCompliance
|
|
649
|
+
|
|
650
|
+
Manages cookie consent banner. Initialised via the static `create()` factory which uses `window.once` to prevent double-initialisation.
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
import { CookieCompliance } from "@pnx-mixtape/mxds/src/Utility/utilities"
|
|
654
|
+
|
|
655
|
+
CookieCompliance.create({ context: document })
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
After creation, the instance is available at `window.CookieCompliance`. Other scripts can check consent before initialising tracking:
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
if (window.CookieCompliance?.hasConsent()) {
|
|
662
|
+
// safe to init analytics
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
**Required HTML attributes on child elements:**
|
|
667
|
+
|
|
668
|
+
| Attribute | Element | Purpose |
|
|
669
|
+
|---|---|---|
|
|
670
|
+
| `data-cookie-accept` | `<button>` | Accept button |
|
|
671
|
+
| `data-cookie-reject` | `<button>` | Reject button |
|
|
672
|
+
| `data-cookie-more` | `<button>` | "More info" button |
|
|
673
|
+
| `data-wait-remove` | Banner root | ms delay before banner is removed from DOM |
|
|
674
|
+
|
|
675
|
+
---
|
|
676
|
+
|
|
677
|
+
### IntersectionLoader
|
|
678
|
+
|
|
679
|
+
Lazy-loads a component when its placeholder element scrolls into the viewport.
|
|
680
|
+
|
|
681
|
+
```typescript
|
|
682
|
+
import { IntersectionLoader } from "@pnx-mixtape/mxds/src/Utility/utilities"
|
|
683
|
+
|
|
684
|
+
IntersectionLoader.create("[data-lazy]", async () => {
|
|
685
|
+
const { default: Component } = await import("./Component")
|
|
686
|
+
// initialise Component
|
|
687
|
+
})
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
**Required HTML attributes on target elements:**
|
|
691
|
+
|
|
692
|
+
| Attribute | Purpose |
|
|
693
|
+
|---|---|
|
|
694
|
+
| `data-load-fallback` | Optional `<button>` shown if JS lazy load fails |
|
|
695
|
+
| `data-load-error` | Optional element revealed on load error |
|
|
696
|
+
|
|
697
|
+
**Note:** `create()` requires `window.once` to be present. In non-Drupal projects, assign [`@drupal/once`](https://www.npmjs.com/package/@drupal/once) to `window.once` at your entry point. Elements are guarded with the `"intersection-loader"` once key.
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
### BreakpointLoader
|
|
702
|
+
|
|
703
|
+
Lazy-loads a component when a media query matches.
|
|
704
|
+
|
|
705
|
+
```typescript
|
|
706
|
+
import { BreakpointLoader } from "@pnx-mixtape/mxds/src/Utility/utilities"
|
|
707
|
+
|
|
708
|
+
BreakpointLoader.create(
|
|
709
|
+
"[data-responsive]",
|
|
710
|
+
async () => {
|
|
711
|
+
const { default: Component } = await import("./Component")
|
|
712
|
+
// initialise Component
|
|
713
|
+
},
|
|
714
|
+
{ mediaQuery: "(max-width: 720px)" }, // default if omitted
|
|
715
|
+
)
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
**Options (`BreakpointLoaderOptions`):**
|
|
719
|
+
|
|
720
|
+
| Option | Type | Default | Description |
|
|
721
|
+
|---|---|---|---|
|
|
722
|
+
| `mediaQuery` | `string` | `"(max-width: 720px)"` | Standard media query string passed to `window.matchMedia` |
|
|
723
|
+
|
|
724
|
+
**Required HTML attributes** are the same as `IntersectionLoader` (`data-load-fallback`, `data-load-error`).
|
|
725
|
+
|
|
726
|
+
**Note:** `create()` requires `window.once` to be present. Elements are guarded with the `"breakpoint-loader"` once key.
|
|
727
|
+
|
|
728
|
+
---
|
|
729
|
+
|
|
730
|
+
## Hooks
|
|
731
|
+
|
|
732
|
+
### useLocalStorage
|
|
733
|
+
|
|
734
|
+
React hook for localStorage with sync across tabs.
|
|
735
|
+
|
|
736
|
+
```typescript
|
|
737
|
+
import { useLocalStorage } from "@pnx-mixtape/mxds/src/Utility/Hooks/useLocalStorage"
|
|
738
|
+
|
|
739
|
+
const [value, setValue] = useLocalStorage({
|
|
740
|
+
key: "theme",
|
|
741
|
+
initialValue: "light",
|
|
742
|
+
})
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
**Features:**
|
|
746
|
+
|
|
747
|
+
- Syncs across tabs
|
|
748
|
+
- JSON validation
|
|
749
|
+
- Error handling
|
|
750
|
+
- SSR-safe
|
|
751
|
+
|
|
752
|
+
---
|
|
753
|
+
|
|
754
|
+
### useUrlParams
|
|
755
|
+
|
|
756
|
+
React hook for URL parameters management.
|
|
757
|
+
|
|
758
|
+
```typescript
|
|
759
|
+
import { useUrlParams } from "@pnx-mixtape/mxds/src/Utility/Hooks/useUrlParams"
|
|
760
|
+
|
|
761
|
+
const { searchParams, pathname, history } = useUrlParams()
|
|
762
|
+
|
|
763
|
+
// Read param
|
|
764
|
+
const page = searchParams?.get("page")
|
|
765
|
+
|
|
766
|
+
// Update URL
|
|
767
|
+
history?.pushState(null, "", `${pathname}?page=2`)
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
---
|
|
771
|
+
|
|
772
|
+
## Elements
|
|
773
|
+
|
|
774
|
+
### Web Component Exports
|
|
775
|
+
|
|
776
|
+
Web Components are exported from `src/elements.ts`:
|
|
777
|
+
|
|
778
|
+
```typescript
|
|
779
|
+
// Import all Web Components
|
|
780
|
+
import {
|
|
781
|
+
Accordion,
|
|
782
|
+
AccordionGroup,
|
|
783
|
+
AccordionMobile,
|
|
784
|
+
AccordionDiv,
|
|
785
|
+
Dialog,
|
|
786
|
+
DropMenu,
|
|
787
|
+
ClosableAlert,
|
|
788
|
+
Sticky,
|
|
789
|
+
Tabs,
|
|
790
|
+
GlobalToggle,
|
|
791
|
+
InPageNavigation,
|
|
792
|
+
Navigation,
|
|
793
|
+
} from "@pnx-mixtape/mxds"
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
**Or import individually:**
|
|
797
|
+
|
|
798
|
+
```typescript
|
|
799
|
+
import Accordion from "@pnx-mixtape/mxds/src/Component/Accordion/Elements/Accordion"
|
|
800
|
+
import Dialog from "@pnx-mixtape/mxds/src/Component/Dialog/Elements/Dialog"
|
|
801
|
+
// ... and so on for each component
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
**Available Web Components:**
|
|
805
|
+
|
|
806
|
+
- `Accordion` - Accordion component
|
|
807
|
+
- `AccordionGroup` - Grouped accordions
|
|
808
|
+
- `AccordionMobile` - Mobile accordion variant
|
|
809
|
+
- `AccordionDiv` - Accordion with div wrapper
|
|
810
|
+
- `Dialog` - Modal dialog
|
|
811
|
+
- `DropMenu` - Dropdown menu
|
|
812
|
+
- `ClosableAlert` - Dismissible alert
|
|
813
|
+
- `Sticky` - Sticky positioning
|
|
814
|
+
- `Tabs` - Tab component
|
|
815
|
+
- `GlobalToggle` - Header toggle
|
|
816
|
+
- `InPageNavigation` - In-page navigation
|
|
817
|
+
- `Navigation` - Main navigation
|
|
818
|
+
|
|
819
|
+
**Utilities also exported:**
|
|
820
|
+
|
|
821
|
+
The `src/elements.ts` file also re-exports all utilities from `src/Utility/utilities.ts`:
|
|
822
|
+
|
|
823
|
+
```typescript
|
|
824
|
+
import {
|
|
825
|
+
// Helper classes
|
|
826
|
+
DisclosureWidget,
|
|
827
|
+
Keyboard,
|
|
828
|
+
CookieCompliance,
|
|
829
|
+
IntersectionLoader,
|
|
830
|
+
BreakpointLoader,
|
|
831
|
+
// Utility functions
|
|
832
|
+
validators,
|
|
833
|
+
makeAnchor,
|
|
834
|
+
makeCamelCase,
|
|
835
|
+
isInternalLink,
|
|
836
|
+
createElement,
|
|
837
|
+
handleOutsideClick,
|
|
838
|
+
handleEscape,
|
|
839
|
+
} from "@pnx-mixtape/mxds"
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
---
|
|
843
|
+
|
|
844
|
+
## React Components
|
|
845
|
+
|
|
846
|
+
### React Component Exports
|
|
847
|
+
|
|
848
|
+
React components are exported from `src/react.ts`:
|
|
849
|
+
|
|
850
|
+
```typescript
|
|
851
|
+
// Import React components
|
|
852
|
+
import {
|
|
853
|
+
// Atoms
|
|
854
|
+
Heading,
|
|
855
|
+
Media,
|
|
856
|
+
Link,
|
|
857
|
+
Text,
|
|
858
|
+
Video,
|
|
859
|
+
Button,
|
|
860
|
+
Icon,
|
|
861
|
+
|
|
862
|
+
// Components
|
|
863
|
+
Accordion,
|
|
864
|
+
AccordionTitle,
|
|
865
|
+
AccordionTitleIcon,
|
|
866
|
+
AccordionContent,
|
|
867
|
+
Breadcrumb,
|
|
868
|
+
Card,
|
|
869
|
+
CardContent,
|
|
870
|
+
CardMedia,
|
|
871
|
+
ContentBlock,
|
|
872
|
+
ContentBlockContent,
|
|
873
|
+
ContentBlockMedia,
|
|
874
|
+
Dialog,
|
|
875
|
+
DialogContent,
|
|
876
|
+
DialogCloseButton,
|
|
877
|
+
InPageNavigation,
|
|
878
|
+
InPageAlert,
|
|
879
|
+
GlobalAlert,
|
|
880
|
+
HeroBanner,
|
|
881
|
+
LinkList,
|
|
882
|
+
LinkListItem,
|
|
883
|
+
Pagination,
|
|
884
|
+
PaginationItem,
|
|
885
|
+
Sticky,
|
|
886
|
+
Tabs,
|
|
887
|
+
Tab,
|
|
888
|
+
TabPanel,
|
|
889
|
+
TabList,
|
|
890
|
+
TabDropMenu,
|
|
891
|
+
Tag,
|
|
892
|
+
ListItem,
|
|
893
|
+
ListItemContent,
|
|
894
|
+
ListItemMedia,
|
|
895
|
+
Navigation,
|
|
896
|
+
NavigationDropdown,
|
|
897
|
+
NavigationDropdownLevel,
|
|
898
|
+
DropMenu,
|
|
899
|
+
DropMenuItem,
|
|
900
|
+
ResultsBar,
|
|
901
|
+
ResultsBarInfo,
|
|
902
|
+
ResultsBarSort,
|
|
903
|
+
|
|
904
|
+
// Forms
|
|
905
|
+
FormCheckbox,
|
|
906
|
+
Checkbox,
|
|
907
|
+
SingleCheckbox,
|
|
908
|
+
FormDescription,
|
|
909
|
+
FormLabel,
|
|
910
|
+
FormRadio,
|
|
911
|
+
Radio,
|
|
912
|
+
FormSelect,
|
|
913
|
+
Option,
|
|
914
|
+
FormText,
|
|
915
|
+
FormTitle,
|
|
916
|
+
Form,
|
|
917
|
+
|
|
918
|
+
// Layout
|
|
919
|
+
Grid,
|
|
920
|
+
Page,
|
|
921
|
+
Section,
|
|
922
|
+
SectionGrid,
|
|
923
|
+
|
|
924
|
+
// Hooks
|
|
925
|
+
useAccordion,
|
|
926
|
+
useDialog,
|
|
927
|
+
useInPageNavigation,
|
|
928
|
+
usePagination,
|
|
929
|
+
useTabContext,
|
|
930
|
+
useDropMenuContext,
|
|
931
|
+
useLocalStorage,
|
|
932
|
+
useMediaQuery,
|
|
933
|
+
useUrlParams,
|
|
934
|
+
|
|
935
|
+
// Context Providers
|
|
936
|
+
ImageComponentProvider,
|
|
937
|
+
useImageComponent,
|
|
938
|
+
LinkComponentProvider,
|
|
939
|
+
useLinkComponent,
|
|
940
|
+
PaginationProvider,
|
|
941
|
+
DropMenuProvider,
|
|
942
|
+
} from "@pnx-mixtape/mxds/react"
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
**Or import individually:**
|
|
946
|
+
|
|
947
|
+
```typescript
|
|
948
|
+
import Button from "@pnx-mixtape/mxds/src/Atom/Button/Button"
|
|
949
|
+
import Card from "@pnx-mixtape/mxds/src/Component/Card/Card"
|
|
950
|
+
import { useLocalStorage } from "@pnx-mixtape/mxds/react"
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
---
|
|
954
|
+
|
|
955
|
+
## Design Tokens
|
|
956
|
+
|
|
957
|
+
### Accessing Tokens
|
|
958
|
+
|
|
959
|
+
```javascript
|
|
960
|
+
// In PostCSS config
|
|
961
|
+
import tokens from "@pnx-mixtape/mxds/tokens"
|
|
962
|
+
|
|
963
|
+
console.log(tokens.colour.brand.primary)
|
|
964
|
+
console.log(tokens.spacing.m)
|
|
965
|
+
console.log(tokens.breakpoint.m)
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
### Token Structure
|
|
969
|
+
|
|
970
|
+
```javascript
|
|
971
|
+
tokens = {
|
|
972
|
+
colour: {
|
|
973
|
+
text: { primary, secondary, ... },
|
|
974
|
+
background: { primary, secondary, ... },
|
|
975
|
+
brand: { primary, secondary, ... },
|
|
976
|
+
},
|
|
977
|
+
spacing: { xxs, xs, s, m, l, xl, xxl },
|
|
978
|
+
font: {
|
|
979
|
+
size: { ... },
|
|
980
|
+
family: { ... },
|
|
981
|
+
weight: { ... },
|
|
982
|
+
},
|
|
983
|
+
breakpoint: { xxs, xs, s, m, l, xl },
|
|
984
|
+
// ... more categories
|
|
985
|
+
}
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
---
|
|
989
|
+
|
|
990
|
+
## Related Documentation
|
|
991
|
+
|
|
992
|
+
- [Code Quality Guidelines](./CodeQuality.md)
|
|
993
|
+
- [Component Architecture](./ComponentArchitecture.md)
|
|
994
|
+
- [Security Guidelines](./Security.md)
|
|
995
|
+
- [Validation Examples](./Security.md#input-validation)
|
|
996
|
+
|
|
997
|
+
---
|
|
998
|
+
|
|
999
|
+
## Enums
|
|
1000
|
+
|
|
1001
|
+
Enums come from two sources:
|
|
1002
|
+
|
|
1003
|
+
- **`src/enums.ts`** — local enums, some of which are migration targets for `@pnx-mixtape/ids-shape`
|
|
1004
|
+
- **`@pnx-mixtape/ids-shape`** — the canonical enum package; prefer these going forward
|
|
1005
|
+
|
|
1006
|
+
### Migration status
|
|
1007
|
+
|
|
1008
|
+
| Local enum | ids-shape equivalent | Status |
|
|
1009
|
+
|---|---|---|
|
|
1010
|
+
| `AsTags` | `AsTypes` | ⏳ Migrate when reworking JSX components |
|
|
1011
|
+
| `FormStatus` | `FormStatusTypes` | ⏳ Migrate when reworking form components |
|
|
1012
|
+
| `Alignment` | `MediaAlignmentTypes` | ⏳ Migrate — ids-shape adds `center` value |
|
|
1013
|
+
| `TextTags` | — | 🏠 Local only |
|
|
1014
|
+
| `HeadingStyles` | — | 🏠 Local only |
|
|
1015
|
+
| `TextStyles` | — | 🏠 Local only |
|
|
1016
|
+
| `Icons` | — | 🏠 Local only |
|
|
1017
|
+
| `PageWidths` | — | 🏠 Local only |
|
|
1018
|
+
| `SectionFlow` | — | 🏠 Local only |
|
|
1019
|
+
| `SidebarOrder` | — | 🏠 Local only |
|
|
1020
|
+
| `BackgroundStyles` | — | 🏠 Local only |
|
|
1021
|
+
| `TableStyles` | — | 🏠 Local only |
|
|
1022
|
+
| `TabStyles` | — | 🏠 Local only |
|
|
1023
|
+
| `SocialShareTypes` | — | 🏠 Local only |
|
|
1024
|
+
|
|
1025
|
+
### Also available in ids-shape (not yet used)
|
|
1026
|
+
|
|
1027
|
+
These ids-shape enums exist and can replace current string literals in components during the rework:
|
|
1028
|
+
|
|
1029
|
+
| ids-shape enum | Purpose |
|
|
1030
|
+
|---|---|
|
|
1031
|
+
| `ButtonTypes`, `ButtonModifiers` | Button variants and types |
|
|
1032
|
+
| `IconColourModifier`, `IconRotateModifier`, `IconSizeModifier` | Icon modifiers |
|
|
1033
|
+
| `LoadingTypes` | `lazy` / `eager` image loading |
|
|
1034
|
+
| `SectionTypes` | HTML tag selection for sections |
|
|
1035
|
+
| `GridModifiers` | Grid column layouts |
|
|
1036
|
+
| `TagTypes` | Tag component variants |
|
|
1037
|
+
| `InPageAlertTypes`, `GlobalAlertTypes` | Alert type variants |
|
|
1038
|
+
| `CardModifier`, `HeroBannerModifier`, `StepsModifier` | Component modifiers |
|
|
1039
|
+
| `ListItemModifiers`, `MastheadModifier`, `FooterModifier` | Layout modifiers |
|
|
1040
|
+
| `PopoverPlacementModifier` | Popover positioning |
|
|
1041
|
+
| `TextModifiers`, `InputTypeTypes` | Text and input variants |
|
|
1042
|
+
|
|
1043
|
+
### Importing local enums
|
|
1044
|
+
|
|
1045
|
+
```typescript
|
|
1046
|
+
import { BackgroundStyles, Icons, TabStyles } from "@pnx-mixtape/mxds/src/enums"
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
### Importing ids-shape enums
|
|
1050
|
+
|
|
1051
|
+
```typescript
|
|
1052
|
+
import { AsTypes, FormStatusTypes, ButtonModifiers } from "@pnx-mixtape/ids-shape"
|
|
1053
|
+
```
|
|
1054
|
+
|
|
1055
|
+
### Local enum values
|
|
1056
|
+
|
|
1057
|
+
#### Layout & Structure
|
|
1058
|
+
|
|
1059
|
+
| Enum | Values |
|
|
1060
|
+
|---|---|
|
|
1061
|
+
| `AsTags` | `div`, `li`, `article`, `aside`, `section` |
|
|
1062
|
+
| `PageWidths` | `narrow`, `feature`, `full` |
|
|
1063
|
+
| `SectionFlow` | `flow`, `group` |
|
|
1064
|
+
| `SidebarOrder` | `""` (default), `sidebar-rev`, `sidebar-rev-lg` |
|
|
1065
|
+
|
|
1066
|
+
#### Typography
|
|
1067
|
+
|
|
1068
|
+
| Enum | Values |
|
|
1069
|
+
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
1070
|
+
| `TextTags` | `p`, `div`, `strong`, `em` |
|
|
1071
|
+
| `HeadingStyles` | `mx-headline`, `mx-text--eyebrow`, `mx-text--embellished`, `mx-text--l`, `mx-heading--xxl`, `mx-heading--xl`, `mx-heading--l`, `mx-heading--m`, `mx-heading--s`, `mx-heading--xs`, `mx-spacing--reset`, `mx-spacing--top-reset`, `mx-spacing--bottom-reset` |
|
|
1072
|
+
| `TextStyles` | `mx-headline`, `mx-text--eyebrow`, `mx-text--embellished`, `mx-text--xxl`, `mx-text--xl`, `mx-text--l`, `mx-text--m`, `mx-text--s`, `mx-text--xs`, `mx-text--primary`, `mx-text--accent` |
|
|
1073
|
+
| `Alignment` | `left`, `right` |
|
|
1074
|
+
|
|
1075
|
+
#### Components
|
|
1076
|
+
|
|
1077
|
+
| Enum | Values |
|
|
1078
|
+
|---|---|
|
|
1079
|
+
| `BackgroundStyles` | `primary`, `accent`, `alt`, `reverse`, `reset`, `box` |
|
|
1080
|
+
| `TabStyles` | `default`, `centered`, `lined`, `same-height` |
|
|
1081
|
+
| `TableStyles` | `flush`, `striped`, `small`, `bordered` |
|
|
1082
|
+
|
|
1083
|
+
#### Forms
|
|
1084
|
+
|
|
1085
|
+
| Enum | Values |
|
|
1086
|
+
|---|---|
|
|
1087
|
+
| `FormStatus` | `default`, `valid`, `invalid` |
|
|
1088
|
+
|
|
1089
|
+
#### Social & Sharing
|
|
1090
|
+
|
|
1091
|
+
| Enum | Values |
|
|
1092
|
+
|---|---|
|
|
1093
|
+
| `SocialShareTypes` | `twitter`, `facebook`, `linkedin` |
|
|
1094
|
+
|
|
1095
|
+
#### Icons
|
|
1096
|
+
|
|
1097
|
+
The `Icons` enum maps semantic names to SVG file slugs:
|
|
1098
|
+
|
|
1099
|
+
```typescript
|
|
1100
|
+
import { Icons } from "@pnx-mixtape/mxds/src/enums"
|
|
1101
|
+
// Icons.ARROW_DOWN = "arrow-down"
|
|
1102
|
+
// Icons.CLOSE = "close"
|
|
1103
|
+
// Icons.SEARCH = "search"
|
|
1104
|
+
```
|
|
1105
|
+
|
|
1106
|
+
| Key | Value | Key | Value |
|
|
1107
|
+
|---|---|---|---|
|
|
1108
|
+
| `ARROW_DOWN` | `arrow-down` | `ARROW_LEFT` | `arrow-left` |
|
|
1109
|
+
| `ARROW_UP` | `arrow-up` | `ARROW_RIGHT` | `arrow-right` |
|
|
1110
|
+
| `CHEVRON_DOWN` | `chevron-down` | `CHEVRON_LEFT` | `chevron-left` |
|
|
1111
|
+
| `CHEVRON_RIGHT` | `chevron-right` | `CHEVRON_UP` | `chevron-up` |
|
|
1112
|
+
| `CLOSE` | `close` | `COLLAPSE` | `collapse` |
|
|
1113
|
+
| `DOWNLOAD` | `download` | `DRUPAL` | `drupal` |
|
|
1114
|
+
| `ERROR` | `error` | `EXPAND` | `expand` |
|
|
1115
|
+
| `EXTERNAL` | `external-link` | `FACEBOOK` | `facebook` |
|
|
1116
|
+
| `GOOGLE` | `google` | `INFO` | `info` |
|
|
1117
|
+
| `FILE` | `file` | `INSTAGRAM` | `instagram` |
|
|
1118
|
+
| `LINKEDIN` | `linkedin` | `MASTODON` | `mastodon` |
|
|
1119
|
+
| `MENU` | `menu` | `SEARCH` | `search` |
|
|
1120
|
+
| `SUCCESS` | `success` | `TWITTER` | `twitter` |
|
|
1121
|
+
| `WARNING` | `warning` | `YOUTUBE` | `youtube` |
|
|
1122
|
+
| `EMAIL` | `email` | `HEART` | `heart-selected` |
|
|
1123
|
+
| `EMPTY_HEART` | `heart-unselected` | `LINK` | `link` |
|
|
1124
|
+
| `LOCK` | `lock` | `LOGIN` | `login` |
|
|
1125
|
+
| `MORE` | `more` | `EDIT` | `pencil` |
|
|
1126
|
+
| `PHONE` | `phone` | `PIN` | `pin` |
|
|
1127
|
+
| `PRINT` | `print` | `RESTART` | `restart` |
|
|
1128
|
+
| `STAR` | `star-selected` | `EMPTY_STAR` | `star-unselected` |
|
|
1129
|
+
| `TICK` | `tick` | `TRASH` | `trash` |
|
|
1130
|
+
| `USER` | `user` | `SHARE` | `share` |
|
|
1131
|
+
| `QUESTION` | `question` | | |
|
|
1132
|
+
|
|
1133
|
+
---
|
|
1134
|
+
|
|
1135
|
+
## Global Variables
|
|
1136
|
+
|
|
1137
|
+
These globals are injected by the host environment (Drupal CMS, etc.) and are declared in `.oxlintrc.json` so the linter does not flag them as undefined. They are **not** provided by this design system.
|
|
1138
|
+
|
|
1139
|
+
| Global | Type | Source | Purpose |
|
|
1140
|
+
|---|---|---|---|
|
|
1141
|
+
| `Drupal` | `object` | Drupal CMS | CMS integration — use `loadOnReady()` rather than accessing directly |
|
|
1142
|
+
| `once` / `window.once` | `function` | [`@drupal/once`](https://www.npmjs.com/package/@drupal/once) (standalone or Drupal core) | DOM initialisation guard — assign to `window.once` in non-Drupal projects |
|
|
1143
|
+
| `Mixtape` | `object` | Internal layer | App-level configuration and feature flags |
|
|
1144
|
+
|
|
1145
|
+
### window.once / once
|
|
1146
|
+
|
|
1147
|
+
`once()` is a DOM initialisation guard that ensures a selector is only processed once per key, even if the initialisation function is called multiple times. The design system accesses it exclusively via `window.once` and guards every call with `Object.hasOwn(window, "once")`, so **it works with or without Drupal**.
|
|
1148
|
+
|
|
1149
|
+
**Standalone usage (non-Drupal projects):**
|
|
1150
|
+
|
|
1151
|
+
Install the standalone package:
|
|
1152
|
+
|
|
1153
|
+
```bash
|
|
1154
|
+
npm install @drupal/once
|
|
1155
|
+
```
|
|
1156
|
+
|
|
1157
|
+
Then assign it to `window` once at your entry point:
|
|
1158
|
+
|
|
1159
|
+
```typescript
|
|
1160
|
+
import once from "@drupal/once"
|
|
1161
|
+
window.once = once
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
That's all — `IntersectionLoader`, `BreakpointLoader`, and `CookieCompliance` will all pick it up automatically.
|
|
1165
|
+
|
|
1166
|
+
**Inside Drupal:** `once` is already on `window` as part of Drupal core — no setup needed.
|
|
1167
|
+
|
|
1168
|
+
**Signature (from `src/Utility/global.d.ts`):**
|
|
1169
|
+
|
|
1170
|
+
```typescript
|
|
1171
|
+
window.once(name: string, selector: string, context?: any): NodeListOf<HTMLElement>
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
- Returns a `NodeList` of elements that have **not** already been processed for this `name`.
|
|
1175
|
+
- If already processed, returns an empty `NodeList` — preventing double-initialisation.
|
|
1176
|
+
- `@see` [npm: @drupal/once](https://www.npmjs.com/package/@drupal/once)
|
|
1177
|
+
|
|
1178
|
+
**Example:**
|
|
1179
|
+
|
|
1180
|
+
```typescript
|
|
1181
|
+
const [el] = window.once("cookie-compliance", "#cookie-compliance", context)
|
|
1182
|
+
if (el) {
|
|
1183
|
+
const instance = new CookieCompliance(el)
|
|
1184
|
+
instance.init()
|
|
1185
|
+
}
|
|
1186
|
+
```
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
### Mixtape
|
|
1190
|
+
|
|
1191
|
+
`window.Mixtape` is the internal app-level namespace used by the `Keyboard` class to coordinate menu focus and popup state across components. Its shape is typed in `src/Utility/global.d.ts`:
|
|
1192
|
+
|
|
1193
|
+
```typescript
|
|
1194
|
+
window.Mixtape = {
|
|
1195
|
+
// Stack of open popups. Keyboard Escape closes the most recent one.
|
|
1196
|
+
Popups: Array<{
|
|
1197
|
+
id: string // ID of the popup element
|
|
1198
|
+
closefn: () => void // Function to close it
|
|
1199
|
+
}>,
|
|
1200
|
+
|
|
1201
|
+
// Currently active keyboard-navigable menu.
|
|
1202
|
+
Menu: {
|
|
1203
|
+
context?: HTMLElement // The menu container
|
|
1204
|
+
items?: NodeListOf<HTMLAnchorElement | HTMLButtonElement> // Focusable items
|
|
1205
|
+
active?: number // Index of currently focused item
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
Direct use of `window.Mixtape` in component code is rare — use the `Keyboard` class methods (`attachMenu`, `attachPopup`, etc.) which manage this state for you.
|
|
1211
|
+
|
|
1212
|
+
### Drupal
|
|
1213
|
+
|
|
1214
|
+
The `Drupal` global is provided by Drupal CMS. Direct use of `Drupal.behaviors` in components is rare — use the `loadOnReady()` utility instead, which handles the Drupal/non-Drupal fallback automatically. See the JSDoc on `loadOnReady` in `src/Utility/utilities.ts` for details.
|
|
1215
|
+
|