@data-slot/core 0.1.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/README.md +119 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +1 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# @data-slot/core
|
|
2
|
+
|
|
3
|
+
Shared utilities for data-slot headless UI components.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @data-slot/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
### DOM Utilities
|
|
14
|
+
|
|
15
|
+
#### `getPart(root, slot)`
|
|
16
|
+
|
|
17
|
+
Query a single part/slot within a component root.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
const trigger = getPart<HTMLButtonElement>(root, "dialog-trigger");
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
#### `getParts(root, slot)`
|
|
24
|
+
|
|
25
|
+
Query all parts/slots within a component root.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
const items = getParts<HTMLElement>(root, "accordion-item");
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
#### `getRoots(scope, slot)`
|
|
32
|
+
|
|
33
|
+
Find all component roots within a scope by data-slot value.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
const dialogs = getRoots(document, "dialog");
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### ARIA Utilities
|
|
40
|
+
|
|
41
|
+
#### `ensureId(element, prefix)`
|
|
42
|
+
|
|
43
|
+
Ensure an element has an id, generating one if needed.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const id = ensureId(content, "dialog-content");
|
|
47
|
+
// Returns existing id or generates "dialog-content-1"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
#### `setAria(element, name, value)`
|
|
51
|
+
|
|
52
|
+
Set or remove an ARIA attribute. Boolean values are converted to strings.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
setAria(trigger, "expanded", true); // aria-expanded="true"
|
|
56
|
+
setAria(trigger, "expanded", null); // removes aria-expanded
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### `linkLabelledBy(content, title, description)`
|
|
60
|
+
|
|
61
|
+
Link content element to its label and description via ARIA.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
linkLabelledBy(dialogContent, titleElement, descriptionElement);
|
|
65
|
+
// Sets aria-labelledby and aria-describedby
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Event Utilities
|
|
69
|
+
|
|
70
|
+
#### `on(element, type, handler, options?)`
|
|
71
|
+
|
|
72
|
+
Add an event listener and return a cleanup function.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const cleanup = on(button, "click", () => console.log("clicked"));
|
|
76
|
+
// Later: cleanup() to remove listener
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### `emit(element, name, detail?)`
|
|
80
|
+
|
|
81
|
+
Dispatch a custom event with optional detail.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
emit(root, "tabs:change", { value: "tab-2" });
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### `composeHandlers(...handlers)`
|
|
88
|
+
|
|
89
|
+
Compose multiple event handlers into one. Stops if `event.defaultPrevented`.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const handler = composeHandlers(onClickProp, internalHandler);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Usage in Components
|
|
96
|
+
|
|
97
|
+
This package is used internally by all `@data-slot/*` component packages. You typically don't need to import it directly unless building custom components.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { getPart, setAria, on } from "@data-slot/core";
|
|
101
|
+
|
|
102
|
+
function createCustomComponent(root: Element) {
|
|
103
|
+
const trigger = getPart(root, "custom-trigger");
|
|
104
|
+
const content = getPart(root, "custom-content");
|
|
105
|
+
|
|
106
|
+
const cleanup = on(trigger, "click", () => {
|
|
107
|
+
const isOpen = content.hidden;
|
|
108
|
+
content.hidden = !isOpen;
|
|
109
|
+
setAria(trigger, "expanded", isOpen);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return { destroy: cleanup };
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT
|
|
119
|
+
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=(e,t)=>e.querySelector(`[data-slot="${t}"]`),t=(e,t)=>[...e.querySelectorAll(`[data-slot="${t}"]`)],n=(e,t)=>[...e.querySelectorAll(`[data-slot="${t}"]`)];let r=0;const i=(e,t)=>e.id||=`${t}-${++r}`,a=(e,t,n)=>{n===null?e.removeAttribute(`aria-${t}`):e.setAttribute(`aria-${t}`,String(n))},o=(e,t,n)=>{t&&e.setAttribute(`aria-labelledby`,i(t,`title`)),n&&e.setAttribute(`aria-describedby`,i(n,`desc`))},s=(e,t,n,r)=>(e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)),c=(e,t,n)=>e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:n})),l=(...e)=>t=>{for(let n of e){if(t.defaultPrevented)break;n?.(t)}};exports.composeHandlers=l,exports.emit=c,exports.ensureId=i,exports.getPart=e,exports.getParts=t,exports.getRoots=n,exports.linkLabelledBy=o,exports.on=s,exports.setAria=a;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/parts.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Query a single part/slot within a component root
|
|
4
|
+
*/
|
|
5
|
+
declare const getPart: <T extends Element = Element>(root: Element, slot: string) => T | null;
|
|
6
|
+
/**
|
|
7
|
+
* Query all parts/slots within a component root
|
|
8
|
+
*/
|
|
9
|
+
declare const getParts: <T extends Element = Element>(root: Element, slot: string) => T[];
|
|
10
|
+
/**
|
|
11
|
+
* Find all component roots within a scope by data-slot value
|
|
12
|
+
*/
|
|
13
|
+
declare const getRoots: <T extends Element = Element>(scope: ParentNode, slot: string) => T[];
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/aria.d.ts
|
|
16
|
+
/**
|
|
17
|
+
* Ensure an element has an id, generating one if needed
|
|
18
|
+
*/
|
|
19
|
+
declare const ensureId: (el: Element, prefix: string) => string;
|
|
20
|
+
/**
|
|
21
|
+
* Set or remove an ARIA attribute
|
|
22
|
+
* Note: boolean values are converted to strings, use null to remove
|
|
23
|
+
*/
|
|
24
|
+
declare const setAria: (el: Element, name: string, value: string | boolean | null) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Link content element to its label and description via ARIA
|
|
27
|
+
*/
|
|
28
|
+
declare const linkLabelledBy: (content: Element, title?: Element | null, description?: Element | null) => void;
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/events.d.ts
|
|
31
|
+
/**
|
|
32
|
+
* Add an event listener and return a cleanup function
|
|
33
|
+
*/
|
|
34
|
+
declare const on: <K extends keyof HTMLElementEventMap>(el: EventTarget, type: K, fn: (e: HTMLElementEventMap[K]) => void, opts?: AddEventListenerOptions) => (() => void);
|
|
35
|
+
/**
|
|
36
|
+
* Dispatch a custom event with optional detail
|
|
37
|
+
*/
|
|
38
|
+
declare const emit: <T = unknown>(el: Element, name: string, detail?: T) => boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Compose multiple event handlers into one
|
|
41
|
+
* Handlers are called in order, stops if event.defaultPrevented
|
|
42
|
+
*/
|
|
43
|
+
declare const composeHandlers: <E extends Event>(...handlers: Array<((e: E) => void) | undefined>) => ((e: E) => void);
|
|
44
|
+
//#endregion
|
|
45
|
+
export { composeHandlers, emit, ensureId, getPart, getParts, getRoots, linkLabelledBy, on, setAria };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/parts.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Query a single part/slot within a component root
|
|
4
|
+
*/
|
|
5
|
+
declare const getPart: <T extends Element = Element>(root: Element, slot: string) => T | null;
|
|
6
|
+
/**
|
|
7
|
+
* Query all parts/slots within a component root
|
|
8
|
+
*/
|
|
9
|
+
declare const getParts: <T extends Element = Element>(root: Element, slot: string) => T[];
|
|
10
|
+
/**
|
|
11
|
+
* Find all component roots within a scope by data-slot value
|
|
12
|
+
*/
|
|
13
|
+
declare const getRoots: <T extends Element = Element>(scope: ParentNode, slot: string) => T[];
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/aria.d.ts
|
|
16
|
+
/**
|
|
17
|
+
* Ensure an element has an id, generating one if needed
|
|
18
|
+
*/
|
|
19
|
+
declare const ensureId: (el: Element, prefix: string) => string;
|
|
20
|
+
/**
|
|
21
|
+
* Set or remove an ARIA attribute
|
|
22
|
+
* Note: boolean values are converted to strings, use null to remove
|
|
23
|
+
*/
|
|
24
|
+
declare const setAria: (el: Element, name: string, value: string | boolean | null) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Link content element to its label and description via ARIA
|
|
27
|
+
*/
|
|
28
|
+
declare const linkLabelledBy: (content: Element, title?: Element | null, description?: Element | null) => void;
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/events.d.ts
|
|
31
|
+
/**
|
|
32
|
+
* Add an event listener and return a cleanup function
|
|
33
|
+
*/
|
|
34
|
+
declare const on: <K extends keyof HTMLElementEventMap>(el: EventTarget, type: K, fn: (e: HTMLElementEventMap[K]) => void, opts?: AddEventListenerOptions) => (() => void);
|
|
35
|
+
/**
|
|
36
|
+
* Dispatch a custom event with optional detail
|
|
37
|
+
*/
|
|
38
|
+
declare const emit: <T = unknown>(el: Element, name: string, detail?: T) => boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Compose multiple event handlers into one
|
|
41
|
+
* Handlers are called in order, stops if event.defaultPrevented
|
|
42
|
+
*/
|
|
43
|
+
declare const composeHandlers: <E extends Event>(...handlers: Array<((e: E) => void) | undefined>) => ((e: E) => void);
|
|
44
|
+
//#endregion
|
|
45
|
+
export { composeHandlers, emit, ensureId, getPart, getParts, getRoots, linkLabelledBy, on, setAria };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=(e,t)=>e.querySelector(`[data-slot="${t}"]`),t=(e,t)=>[...e.querySelectorAll(`[data-slot="${t}"]`)],n=(e,t)=>[...e.querySelectorAll(`[data-slot="${t}"]`)];let r=0;const i=(e,t)=>e.id||=`${t}-${++r}`,a=(e,t,n)=>{n===null?e.removeAttribute(`aria-${t}`):e.setAttribute(`aria-${t}`,String(n))},o=(e,t,n)=>{t&&e.setAttribute(`aria-labelledby`,i(t,`title`)),n&&e.setAttribute(`aria-describedby`,i(n,`desc`))},s=(e,t,n,r)=>(e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)),c=(e,t,n)=>e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:n})),l=(...e)=>t=>{for(let n of e){if(t.defaultPrevented)break;n?.(t)}};export{l as composeHandlers,c as emit,i as ensureId,e as getPart,t as getParts,n as getRoots,o as linkLabelledBy,s as on,a as setAria};
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@data-slot/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": ["dist"],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsdown"
|
|
24
|
+
},
|
|
25
|
+
"keywords": ["headless", "ui", "components", "vanilla", "data-slot"],
|
|
26
|
+
"license": "MIT"
|
|
27
|
+
}
|