@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 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;
@@ -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 };
@@ -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
+ }