@ailuracode/alpine-child 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) ailuracode
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # @ailuracode/alpine-child
2
+
3
+ Alpine.js directive for **asChild-style** composition: transfer attributes, classes, styles, and Alpine bindings from a wrapper to its first real child element — without an extra DOM node.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @ailuracode/alpine-child @alpinejs/morph alpinejs
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ ```js
14
+ import Alpine from "alpinejs";
15
+ import morph from "@alpinejs/morph";
16
+ import child from "@ailuracode/alpine-child";
17
+
18
+ Alpine.plugin(morph);
19
+ Alpine.plugin(child);
20
+ Alpine.start();
21
+ ```
22
+
23
+ Unwrapping uses [`Alpine.morph()`](https://alpinejs.dev/plugins/morph) — register Morph before `x-child`.
24
+
25
+ ## Usage
26
+
27
+ ```html
28
+ <span
29
+ x-child
30
+ class="inline-flex items-center rounded-md px-4 py-2 text-sm font-medium"
31
+ @click="console.log('button behavior')"
32
+ >
33
+ <a href="/docs">Docs</a>
34
+ </span>
35
+ ```
36
+
37
+ Result in the DOM:
38
+
39
+ ```html
40
+ <a
41
+ href="/docs"
42
+ class="inline-flex items-center rounded-md px-4 py-2 text-sm font-medium"
43
+ >
44
+ Docs
45
+ </a>
46
+ ```
47
+
48
+ ## Comparison with `asChild`
49
+
50
+ | React (Radix / shadcn) | Alpine (`x-child`) |
51
+ |------------------------|--------------------|
52
+ | `<Button asChild><a href="…">` | `<span x-child …><a href="…">` |
53
+ | `cloneElement` merges props | Directive merges attributes onto first child |
54
+ | No wrapper in React tree | Wrapper is removed from the live DOM |
55
+
56
+ `x-child` is useful for **headless Alpine components** and **Blade/Laravel components** that need button/link semantics without an extra `<span>` in the final markup.
57
+
58
+ ## Modifiers
59
+
60
+ | Modifier | Behavior |
61
+ |----------|----------|
62
+ | *(none)* | Merge `class` / `style`; copy other attributes only when missing on the child |
63
+ | `.merge` | Same as default (explicit) |
64
+ | `.replace` | Wrapper values win on conflicts (classes still merge) |
65
+
66
+ ```html
67
+ <div x-child.replace class="wrapper" aria-label="Wrapper">
68
+ <button class="child" aria-label="Child">Action</button>
69
+ </div>
70
+ ```
71
+
72
+ ## Attribute rules
73
+
74
+ **Merged:** `class`, `style`
75
+
76
+ **Copied when missing on child:** `aria-*`, `data-*`, `role`, `tabindex`, `@click`, `x-on:*`, `x-bind:*`, `:attr`, etc.
77
+
78
+ **Child wins by default:** existing `id`, `aria-*`, `data-*`, and most attributes
79
+
80
+ **Never copied:** `x-child`, `x-ignore`, `x-teleport`, `x-cloak`, transition internals
81
+
82
+ **Scope transfer:** `x-data`, `x-init`, and `x-ref` move to the child when the child does not already define them
83
+
84
+ ## Events
85
+
86
+ Declarative Alpine events on the wrapper (`@click`, `@keydown.enter`, `x-on:click`) are copied to the child before Alpine initializes the child, so handlers run on the real interactive element.
87
+
88
+ Programmatic listeners attached to the wrapper at runtime are **not** transferred.
89
+
90
+ ## Blade components
91
+
92
+ ```blade
93
+ {{-- resources/views/components/button.blade.php --}}
94
+ <span
95
+ x-child
96
+ {{ $attributes->merge(['class' => 'inline-flex rounded-md px-4 py-2']) }}
97
+ >
98
+ {{ $slot }}
99
+ </span>
100
+ ```
101
+
102
+ ```blade
103
+ <x-button>
104
+ <a href="{{ route('docs') }}">Docs</a>
105
+ </x-button>
106
+ ```
107
+
108
+ The anchor receives merged classes and any Alpine attributes you put on `<x-button>`.
109
+
110
+ ## Limitations
111
+
112
+ - Requires **`@alpinejs/morph`** registered before this plugin.
113
+ - Only the **first element child** is kept; text nodes and comments are skipped. Extra element siblings are discarded with the detached wrapper.
114
+ - Works best when the wrapper exists in static HTML/Blade before `Alpine.start()`. Dynamically inserted trees are supported via `Alpine.initTree()`.
115
+ - `x-for` / `x-if` on the wrapper are not supported — use `x-child` on stable wrapper markup instead.
116
+ - Nested `x-child` on the same branch is not supported.
117
+ - SSR: the wrapper is present in server HTML; after hydration the wrapper is replaced client-side.
118
+
119
+ ## API
120
+
121
+ This package registers a single directive:
122
+
123
+ - `x-child`
124
+ - `x-child.merge`
125
+ - `x-child.replace`
126
+
127
+ No stores or magics are added.
128
+
129
+ ## License
130
+
131
+ MIT
@@ -0,0 +1,9 @@
1
+ /// <reference types="@types/alpinejs" />
2
+
3
+ export type ChildMergeMode = "default" | "merge" | "replace";
4
+
5
+ declare global {
6
+ namespace Alpine {
7
+ // Directive-only plugin — no magics or stores.
8
+ }
9
+ }
@@ -0,0 +1,20 @@
1
+ import AlpineType from 'alpinejs';
2
+
3
+ type ChildMergeMode = "default" | "merge" | "replace";
4
+ /** Returns the first element child, skipping text and comment nodes. */
5
+ declare function findFirstElementChild(parent: Element): Element | null;
6
+ /** Counts element children (excluding text/comment nodes). */
7
+ declare function countElementChildren(parent: Element): number;
8
+ /** Parses `x-child` directive modifiers from element attributes. */
9
+ declare function parseChildDirective(el: Element): {
10
+ mode: ChildMergeMode;
11
+ } | null;
12
+ /** Transfers wrapper attributes onto the first element child. */
13
+ declare function transferAttributes(wrapper: Element, child: Element, mode?: ChildMergeMode): void;
14
+ /** Removes transferable attributes from the wrapper after unwrapping. */
15
+ declare function clearTransferredAttributes(wrapper: Element): void;
16
+
17
+ /** Alpine.js plugin. Registers the `x-child` directive (asChild-style unwrapping). */
18
+ declare function childPlugin(Alpine: AlpineType.Alpine): void;
19
+
20
+ export { type ChildMergeMode, clearTransferredAttributes, countElementChildren, childPlugin as default, findFirstElementChild, parseChildDirective, transferAttributes };
package/dist/index.js ADDED
@@ -0,0 +1,232 @@
1
+ // src/transfer.ts
2
+ var CHILD_DIRECTIVE_PREFIX = "x-child";
3
+ var NEVER_TRANSFER = /* @__PURE__ */ new Set([
4
+ CHILD_DIRECTIVE_PREFIX,
5
+ "x-ignore",
6
+ "x-ignore.self",
7
+ "x-teleport",
8
+ "x-cloak"
9
+ ]);
10
+ var NEVER_TRANSFER_PREFIXES = ["x-child.", "x-teleport.", "x-transition", "x-effect"];
11
+ var SCOPE_ATTRS = /* @__PURE__ */ new Set(["x-data", "x-init", "x-ref"]);
12
+ function isNeverTransfer(name) {
13
+ if (NEVER_TRANSFER.has(name)) {
14
+ return true;
15
+ }
16
+ return NEVER_TRANSFER_PREFIXES.some((prefix) => name.startsWith(prefix));
17
+ }
18
+ function findFirstElementChild(parent) {
19
+ for (const node of parent.childNodes) {
20
+ if (node.nodeType === Node.ELEMENT_NODE) {
21
+ return node;
22
+ }
23
+ }
24
+ return null;
25
+ }
26
+ function countElementChildren(parent) {
27
+ let count = 0;
28
+ for (const node of parent.childNodes) {
29
+ if (node.nodeType === Node.ELEMENT_NODE) {
30
+ count += 1;
31
+ }
32
+ }
33
+ return count;
34
+ }
35
+ function parseChildDirective(el) {
36
+ for (const { name } of el.attributes) {
37
+ if (name === CHILD_DIRECTIVE_PREFIX) {
38
+ return { mode: "default" };
39
+ }
40
+ if (!name.startsWith(`${CHILD_DIRECTIVE_PREFIX}.`)) {
41
+ continue;
42
+ }
43
+ const modifiers = name.slice(CHILD_DIRECTIVE_PREFIX.length + 1).split(".");
44
+ if (modifiers.includes("replace")) {
45
+ return { mode: "replace" };
46
+ }
47
+ if (modifiers.includes("merge")) {
48
+ return { mode: "merge" };
49
+ }
50
+ return { mode: "default" };
51
+ }
52
+ return null;
53
+ }
54
+ function mergeClassTokens(...values) {
55
+ const tokens = /* @__PURE__ */ new Set();
56
+ for (const value of values) {
57
+ for (const token of value.split(/\s+/)) {
58
+ if (token) {
59
+ tokens.add(token);
60
+ }
61
+ }
62
+ }
63
+ return [...tokens].join(" ");
64
+ }
65
+ function parseInlineStyle(style) {
66
+ const map = /* @__PURE__ */ new Map();
67
+ for (const part of style.split(";")) {
68
+ const trimmed = part.trim();
69
+ if (!trimmed) {
70
+ continue;
71
+ }
72
+ const colon = trimmed.indexOf(":");
73
+ if (colon === -1) {
74
+ continue;
75
+ }
76
+ const property = trimmed.slice(0, colon).trim().toLowerCase();
77
+ const value = trimmed.slice(colon + 1).trim();
78
+ map.set(property, value);
79
+ }
80
+ return map;
81
+ }
82
+ function serializeInlineStyle(map) {
83
+ return [...map.entries()].map(([property, value]) => `${property}: ${value}`).join("; ");
84
+ }
85
+ function mergeInlineStyle(wrapperStyle, childStyle, childWins) {
86
+ const wrapperMap = parseInlineStyle(wrapperStyle);
87
+ const childMap = parseInlineStyle(childStyle);
88
+ const merged = childWins ? new Map([...wrapperMap, ...childMap]) : new Map([...childMap, ...wrapperMap]);
89
+ return serializeInlineStyle(merged);
90
+ }
91
+ function childHasAttribute(el, name) {
92
+ return el.hasAttribute(name);
93
+ }
94
+ function shouldCopyAttribute(name, wrapper, child, mode) {
95
+ if (isNeverTransfer(name)) {
96
+ return false;
97
+ }
98
+ if (name === "id" && childHasAttribute(child, "id")) {
99
+ return false;
100
+ }
101
+ if (SCOPE_ATTRS.has(name) && childHasAttribute(child, name)) {
102
+ return false;
103
+ }
104
+ if (mode === "replace") {
105
+ return wrapper.hasAttribute(name);
106
+ }
107
+ if (mode === "default" || mode === "merge") {
108
+ if (name === "class" || name === "style") {
109
+ return wrapper.hasAttribute(name);
110
+ }
111
+ return wrapper.hasAttribute(name) && !childHasAttribute(child, name);
112
+ }
113
+ return false;
114
+ }
115
+ function applyAttribute(wrapper, child, name, mode) {
116
+ const wrapperValue = wrapper.getAttribute(name) ?? "";
117
+ const childValue = child.getAttribute(name) ?? "";
118
+ if (name === "class") {
119
+ if (mode === "replace") {
120
+ child.setAttribute("class", mergeClassTokens(wrapperValue, childValue));
121
+ return;
122
+ }
123
+ child.setAttribute("class", mergeClassTokens(childValue, wrapperValue));
124
+ return;
125
+ }
126
+ if (name === "style") {
127
+ const merged = mergeInlineStyle(wrapperValue, childValue, mode !== "replace");
128
+ if (merged) {
129
+ child.setAttribute("style", merged);
130
+ }
131
+ return;
132
+ }
133
+ if (mode === "replace" || !childHasAttribute(child, name)) {
134
+ child.setAttribute(name, wrapperValue);
135
+ }
136
+ }
137
+ function transferAttributes(wrapper, child, mode = "default") {
138
+ const names = /* @__PURE__ */ new Set();
139
+ for (const { name } of wrapper.attributes) {
140
+ names.add(name);
141
+ }
142
+ for (const name of names) {
143
+ if (!shouldCopyAttribute(name, wrapper, child, mode)) {
144
+ continue;
145
+ }
146
+ applyAttribute(wrapper, child, name, mode);
147
+ }
148
+ }
149
+ function clearTransferredAttributes(wrapper) {
150
+ const toRemove = [];
151
+ for (const { name } of wrapper.attributes) {
152
+ if (isNeverTransfer(name)) {
153
+ toRemove.push(name);
154
+ continue;
155
+ }
156
+ toRemove.push(name);
157
+ }
158
+ for (const name of toRemove) {
159
+ wrapper.removeAttribute(name);
160
+ }
161
+ }
162
+
163
+ // src/index.ts
164
+ var processedWrappers = /* @__PURE__ */ new WeakSet();
165
+ function warnChild(message) {
166
+ console.warn(message);
167
+ }
168
+ function childPlugin(Alpine) {
169
+ Alpine.addInitSelector(() => `[${Alpine.prefixed("child")}]`);
170
+ Alpine.interceptInit((el, skip) => {
171
+ const config = parseChildDirective(el);
172
+ if (!config) {
173
+ return;
174
+ }
175
+ if (processedWrappers.has(el)) {
176
+ return;
177
+ }
178
+ const target = findFirstElementChild(el);
179
+ if (!target) {
180
+ warnChild("[x-child] No element child found; directive ignored.");
181
+ return;
182
+ }
183
+ if (countElementChildren(el) > 1) {
184
+ warnChild("[x-child] Multiple element children; only the first is used.");
185
+ }
186
+ const morph = Alpine.morph;
187
+ if (typeof morph !== "function") {
188
+ warnChild(
189
+ "[x-child] @alpinejs/morph is required \u2014 register Alpine.plugin(morph) before x-child."
190
+ );
191
+ return;
192
+ }
193
+ processedWrappers.add(el);
194
+ el._x_ignoreSelf = true;
195
+ target._x_ignore = true;
196
+ skip();
197
+ Alpine.nextTick(() => {
198
+ if (!(el.isConnected || target.isConnected)) {
199
+ return;
200
+ }
201
+ transferAttributes(el, target, config.mode);
202
+ let promoted = null;
203
+ Alpine.mutateDom(() => {
204
+ morph(el, target.outerHTML, {
205
+ added(node) {
206
+ if (node.nodeType === Node.ELEMENT_NODE) {
207
+ promoted = node;
208
+ }
209
+ }
210
+ });
211
+ });
212
+ const result = promoted ?? (el.isConnected ? el : null);
213
+ if (result) {
214
+ processedWrappers.add(result);
215
+ result._x_ignore = void 0;
216
+ Alpine.initTree(result);
217
+ }
218
+ clearTransferredAttributes(el);
219
+ });
220
+ });
221
+ Alpine.directive("child", () => {
222
+ }).before("ignore");
223
+ }
224
+ export {
225
+ clearTransferredAttributes,
226
+ countElementChildren,
227
+ childPlugin as default,
228
+ findFirstElementChild,
229
+ parseChildDirective,
230
+ transferAttributes
231
+ };
232
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/transfer.ts","../src/index.ts"],"sourcesContent":["export type ChildMergeMode = \"default\" | \"merge\" | \"replace\";\n\nconst CHILD_DIRECTIVE_PREFIX = \"x-child\";\n\nconst NEVER_TRANSFER = new Set([\n CHILD_DIRECTIVE_PREFIX,\n \"x-ignore\",\n \"x-ignore.self\",\n \"x-teleport\",\n \"x-cloak\",\n]);\n\nconst NEVER_TRANSFER_PREFIXES = [\"x-child.\", \"x-teleport.\", \"x-transition\", \"x-effect\"];\n\nconst SCOPE_ATTRS = new Set([\"x-data\", \"x-init\", \"x-ref\"]);\n\nfunction isNeverTransfer(name: string): boolean {\n if (NEVER_TRANSFER.has(name)) {\n return true;\n }\n\n return NEVER_TRANSFER_PREFIXES.some((prefix) => name.startsWith(prefix));\n}\n\n/** Returns the first element child, skipping text and comment nodes. */\nexport function findFirstElementChild(parent: Element): Element | null {\n for (const node of parent.childNodes) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n return node as Element;\n }\n }\n\n return null;\n}\n\n/** Counts element children (excluding text/comment nodes). */\nexport function countElementChildren(parent: Element): number {\n let count = 0;\n\n for (const node of parent.childNodes) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n count += 1;\n }\n }\n\n return count;\n}\n\n/** Parses `x-child` directive modifiers from element attributes. */\nexport function parseChildDirective(el: Element): { mode: ChildMergeMode } | null {\n for (const { name } of el.attributes) {\n if (name === CHILD_DIRECTIVE_PREFIX) {\n return { mode: \"default\" };\n }\n\n if (!name.startsWith(`${CHILD_DIRECTIVE_PREFIX}.`)) {\n continue;\n }\n\n const modifiers = name.slice(CHILD_DIRECTIVE_PREFIX.length + 1).split(\".\");\n\n if (modifiers.includes(\"replace\")) {\n return { mode: \"replace\" };\n }\n\n if (modifiers.includes(\"merge\")) {\n return { mode: \"merge\" };\n }\n\n return { mode: \"default\" };\n }\n\n return null;\n}\n\nfunction mergeClassTokens(...values: string[]): string {\n const tokens = new Set<string>();\n\n for (const value of values) {\n for (const token of value.split(/\\s+/)) {\n if (token) {\n tokens.add(token);\n }\n }\n }\n\n return [...tokens].join(\" \");\n}\n\nfunction parseInlineStyle(style: string): Map<string, string> {\n const map = new Map<string, string>();\n\n for (const part of style.split(\";\")) {\n const trimmed = part.trim();\n if (!trimmed) {\n continue;\n }\n\n const colon = trimmed.indexOf(\":\");\n if (colon === -1) {\n continue;\n }\n\n const property = trimmed.slice(0, colon).trim().toLowerCase();\n const value = trimmed.slice(colon + 1).trim();\n map.set(property, value);\n }\n\n return map;\n}\n\nfunction serializeInlineStyle(map: Map<string, string>): string {\n return [...map.entries()].map(([property, value]) => `${property}: ${value}`).join(\"; \");\n}\n\nfunction mergeInlineStyle(wrapperStyle: string, childStyle: string, childWins: boolean): string {\n const wrapperMap = parseInlineStyle(wrapperStyle);\n const childMap = parseInlineStyle(childStyle);\n const merged = childWins\n ? new Map([...wrapperMap, ...childMap])\n : new Map([...childMap, ...wrapperMap]);\n\n return serializeInlineStyle(merged);\n}\n\nfunction childHasAttribute(el: Element, name: string): boolean {\n return el.hasAttribute(name);\n}\n\nfunction shouldCopyAttribute(\n name: string,\n wrapper: Element,\n child: Element,\n mode: ChildMergeMode\n): boolean {\n if (isNeverTransfer(name)) {\n return false;\n }\n\n if (name === \"id\" && childHasAttribute(child, \"id\")) {\n return false;\n }\n\n if (SCOPE_ATTRS.has(name) && childHasAttribute(child, name)) {\n return false;\n }\n\n if (mode === \"replace\") {\n return wrapper.hasAttribute(name);\n }\n\n if (mode === \"default\" || mode === \"merge\") {\n if (name === \"class\" || name === \"style\") {\n return wrapper.hasAttribute(name);\n }\n\n return wrapper.hasAttribute(name) && !childHasAttribute(child, name);\n }\n\n return false;\n}\n\nfunction applyAttribute(\n wrapper: Element,\n child: Element,\n name: string,\n mode: ChildMergeMode\n): void {\n const wrapperValue = wrapper.getAttribute(name) ?? \"\";\n const childValue = child.getAttribute(name) ?? \"\";\n\n if (name === \"class\") {\n if (mode === \"replace\") {\n child.setAttribute(\"class\", mergeClassTokens(wrapperValue, childValue));\n return;\n }\n\n child.setAttribute(\"class\", mergeClassTokens(childValue, wrapperValue));\n return;\n }\n\n if (name === \"style\") {\n const merged = mergeInlineStyle(wrapperValue, childValue, mode !== \"replace\");\n if (merged) {\n child.setAttribute(\"style\", merged);\n }\n return;\n }\n\n if (mode === \"replace\" || !childHasAttribute(child, name)) {\n child.setAttribute(name, wrapperValue);\n }\n}\n\n/** Transfers wrapper attributes onto the first element child. */\nexport function transferAttributes(\n wrapper: Element,\n child: Element,\n mode: ChildMergeMode = \"default\"\n): void {\n const names = new Set<string>();\n\n for (const { name } of wrapper.attributes) {\n names.add(name);\n }\n\n for (const name of names) {\n if (!shouldCopyAttribute(name, wrapper, child, mode)) {\n continue;\n }\n\n applyAttribute(wrapper, child, name, mode);\n }\n}\n\n/** Removes transferable attributes from the wrapper after unwrapping. */\nexport function clearTransferredAttributes(wrapper: Element): void {\n const toRemove: string[] = [];\n\n for (const { name } of wrapper.attributes) {\n if (isNeverTransfer(name)) {\n toRemove.push(name);\n continue;\n }\n\n toRemove.push(name);\n }\n\n for (const name of toRemove) {\n wrapper.removeAttribute(name);\n }\n}\n","import type AlpineType from \"alpinejs\";\nimport {\n clearTransferredAttributes,\n countElementChildren,\n findFirstElementChild,\n parseChildDirective,\n transferAttributes,\n} from \"./transfer.js\";\n\nexport type { ChildMergeMode } from \"./transfer.js\";\nexport {\n clearTransferredAttributes,\n countElementChildren,\n findFirstElementChild,\n parseChildDirective,\n transferAttributes,\n} from \"./transfer.js\";\n\nconst processedWrappers = new WeakSet<Element>();\n\ntype AlpineElement = Element & {\n _x_ignore?: boolean;\n _x_ignoreSelf?: boolean;\n};\n\ntype MorphOptions = {\n added?: (node: Node) => void;\n};\n\ntype AlpineWithMorph = AlpineType.Alpine & {\n morph?: (el: Element, newHtml: string | Element, options?: MorphOptions) => Element;\n};\n\nfunction warnChild(message: string): void {\n // biome-ignore lint/suspicious/noConsole: intentional developer warnings for invalid markup\n console.warn(message);\n}\n\n/** Alpine.js plugin. Registers the `x-child` directive (asChild-style unwrapping). */\nexport default function childPlugin(Alpine: AlpineType.Alpine): void {\n Alpine.addInitSelector(() => `[${Alpine.prefixed(\"child\")}]`);\n\n Alpine.interceptInit((el: Element, skip: () => void) => {\n const config = parseChildDirective(el);\n if (!config) {\n return;\n }\n\n if (processedWrappers.has(el)) {\n return;\n }\n\n const target = findFirstElementChild(el);\n if (!target) {\n warnChild(\"[x-child] No element child found; directive ignored.\");\n return;\n }\n\n if (countElementChildren(el) > 1) {\n warnChild(\"[x-child] Multiple element children; only the first is used.\");\n }\n\n const morph = (Alpine as AlpineWithMorph).morph;\n if (typeof morph !== \"function\") {\n warnChild(\n \"[x-child] @alpinejs/morph is required — register Alpine.plugin(morph) before x-child.\"\n );\n return;\n }\n\n // Defer unwrap until after the current initTree pass so DOM mutations from\n // morph do not corrupt scope for siblings initialized later (e.g. sidebar).\n processedWrappers.add(el);\n (el as AlpineElement)._x_ignoreSelf = true;\n (target as AlpineElement)._x_ignore = true;\n skip();\n\n Alpine.nextTick(() => {\n if (!(el.isConnected || target.isConnected)) {\n return;\n }\n\n transferAttributes(el, target, config.mode);\n\n let promoted: Element | null = null;\n\n Alpine.mutateDom(() => {\n morph(el, target.outerHTML, {\n added(node) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n promoted = node as Element;\n }\n },\n });\n });\n\n const result = promoted ?? (el.isConnected ? el : null);\n if (result) {\n processedWrappers.add(result);\n (result as AlpineElement)._x_ignore = undefined;\n Alpine.initTree(result as HTMLElement);\n }\n\n clearTransferredAttributes(el);\n });\n });\n\n Alpine.directive(\"child\", () => {\n // Unwrap runs in interceptInit before other directives on the wrapper.\n }).before(\"ignore\");\n}\n"],"mappings":";AAEA,IAAM,yBAAyB;AAE/B,IAAM,iBAAiB,oBAAI,IAAI;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,0BAA0B,CAAC,YAAY,eAAe,gBAAgB,UAAU;AAEtF,IAAM,cAAc,oBAAI,IAAI,CAAC,UAAU,UAAU,OAAO,CAAC;AAEzD,SAAS,gBAAgB,MAAuB;AAC9C,MAAI,eAAe,IAAI,IAAI,GAAG;AAC5B,WAAO;AAAA,EACT;AAEA,SAAO,wBAAwB,KAAK,CAAC,WAAW,KAAK,WAAW,MAAM,CAAC;AACzE;AAGO,SAAS,sBAAsB,QAAiC;AACrE,aAAW,QAAQ,OAAO,YAAY;AACpC,QAAI,KAAK,aAAa,KAAK,cAAc;AACvC,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAGO,SAAS,qBAAqB,QAAyB;AAC5D,MAAI,QAAQ;AAEZ,aAAW,QAAQ,OAAO,YAAY;AACpC,QAAI,KAAK,aAAa,KAAK,cAAc;AACvC,eAAS;AAAA,IACX;AAAA,EACF;AAEA,SAAO;AACT;AAGO,SAAS,oBAAoB,IAA8C;AAChF,aAAW,EAAE,KAAK,KAAK,GAAG,YAAY;AACpC,QAAI,SAAS,wBAAwB;AACnC,aAAO,EAAE,MAAM,UAAU;AAAA,IAC3B;AAEA,QAAI,CAAC,KAAK,WAAW,GAAG,sBAAsB,GAAG,GAAG;AAClD;AAAA,IACF;AAEA,UAAM,YAAY,KAAK,MAAM,uBAAuB,SAAS,CAAC,EAAE,MAAM,GAAG;AAEzE,QAAI,UAAU,SAAS,SAAS,GAAG;AACjC,aAAO,EAAE,MAAM,UAAU;AAAA,IAC3B;AAEA,QAAI,UAAU,SAAS,OAAO,GAAG;AAC/B,aAAO,EAAE,MAAM,QAAQ;AAAA,IACzB;AAEA,WAAO,EAAE,MAAM,UAAU;AAAA,EAC3B;AAEA,SAAO;AACT;AAEA,SAAS,oBAAoB,QAA0B;AACrD,QAAM,SAAS,oBAAI,IAAY;AAE/B,aAAW,SAAS,QAAQ;AAC1B,eAAW,SAAS,MAAM,MAAM,KAAK,GAAG;AACtC,UAAI,OAAO;AACT,eAAO,IAAI,KAAK;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAEA,SAAO,CAAC,GAAG,MAAM,EAAE,KAAK,GAAG;AAC7B;AAEA,SAAS,iBAAiB,OAAoC;AAC5D,QAAM,MAAM,oBAAI,IAAoB;AAEpC,aAAW,QAAQ,MAAM,MAAM,GAAG,GAAG;AACnC,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAEA,UAAM,QAAQ,QAAQ,QAAQ,GAAG;AACjC,QAAI,UAAU,IAAI;AAChB;AAAA,IACF;AAEA,UAAM,WAAW,QAAQ,MAAM,GAAG,KAAK,EAAE,KAAK,EAAE,YAAY;AAC5D,UAAM,QAAQ,QAAQ,MAAM,QAAQ,CAAC,EAAE,KAAK;AAC5C,QAAI,IAAI,UAAU,KAAK;AAAA,EACzB;AAEA,SAAO;AACT;AAEA,SAAS,qBAAqB,KAAkC;AAC9D,SAAO,CAAC,GAAG,IAAI,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,UAAU,KAAK,MAAM,GAAG,QAAQ,KAAK,KAAK,EAAE,EAAE,KAAK,IAAI;AACzF;AAEA,SAAS,iBAAiB,cAAsB,YAAoB,WAA4B;AAC9F,QAAM,aAAa,iBAAiB,YAAY;AAChD,QAAM,WAAW,iBAAiB,UAAU;AAC5C,QAAM,SAAS,YACX,IAAI,IAAI,CAAC,GAAG,YAAY,GAAG,QAAQ,CAAC,IACpC,IAAI,IAAI,CAAC,GAAG,UAAU,GAAG,UAAU,CAAC;AAExC,SAAO,qBAAqB,MAAM;AACpC;AAEA,SAAS,kBAAkB,IAAa,MAAuB;AAC7D,SAAO,GAAG,aAAa,IAAI;AAC7B;AAEA,SAAS,oBACP,MACA,SACA,OACA,MACS;AACT,MAAI,gBAAgB,IAAI,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,MAAI,SAAS,QAAQ,kBAAkB,OAAO,IAAI,GAAG;AACnD,WAAO;AAAA,EACT;AAEA,MAAI,YAAY,IAAI,IAAI,KAAK,kBAAkB,OAAO,IAAI,GAAG;AAC3D,WAAO;AAAA,EACT;AAEA,MAAI,SAAS,WAAW;AACtB,WAAO,QAAQ,aAAa,IAAI;AAAA,EAClC;AAEA,MAAI,SAAS,aAAa,SAAS,SAAS;AAC1C,QAAI,SAAS,WAAW,SAAS,SAAS;AACxC,aAAO,QAAQ,aAAa,IAAI;AAAA,IAClC;AAEA,WAAO,QAAQ,aAAa,IAAI,KAAK,CAAC,kBAAkB,OAAO,IAAI;AAAA,EACrE;AAEA,SAAO;AACT;AAEA,SAAS,eACP,SACA,OACA,MACA,MACM;AACN,QAAM,eAAe,QAAQ,aAAa,IAAI,KAAK;AACnD,QAAM,aAAa,MAAM,aAAa,IAAI,KAAK;AAE/C,MAAI,SAAS,SAAS;AACpB,QAAI,SAAS,WAAW;AACtB,YAAM,aAAa,SAAS,iBAAiB,cAAc,UAAU,CAAC;AACtE;AAAA,IACF;AAEA,UAAM,aAAa,SAAS,iBAAiB,YAAY,YAAY,CAAC;AACtE;AAAA,EACF;AAEA,MAAI,SAAS,SAAS;AACpB,UAAM,SAAS,iBAAiB,cAAc,YAAY,SAAS,SAAS;AAC5E,QAAI,QAAQ;AACV,YAAM,aAAa,SAAS,MAAM;AAAA,IACpC;AACA;AAAA,EACF;AAEA,MAAI,SAAS,aAAa,CAAC,kBAAkB,OAAO,IAAI,GAAG;AACzD,UAAM,aAAa,MAAM,YAAY;AAAA,EACvC;AACF;AAGO,SAAS,mBACd,SACA,OACA,OAAuB,WACjB;AACN,QAAM,QAAQ,oBAAI,IAAY;AAE9B,aAAW,EAAE,KAAK,KAAK,QAAQ,YAAY;AACzC,UAAM,IAAI,IAAI;AAAA,EAChB;AAEA,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,oBAAoB,MAAM,SAAS,OAAO,IAAI,GAAG;AACpD;AAAA,IACF;AAEA,mBAAe,SAAS,OAAO,MAAM,IAAI;AAAA,EAC3C;AACF;AAGO,SAAS,2BAA2B,SAAwB;AACjE,QAAM,WAAqB,CAAC;AAE5B,aAAW,EAAE,KAAK,KAAK,QAAQ,YAAY;AACzC,QAAI,gBAAgB,IAAI,GAAG;AACzB,eAAS,KAAK,IAAI;AAClB;AAAA,IACF;AAEA,aAAS,KAAK,IAAI;AAAA,EACpB;AAEA,aAAW,QAAQ,UAAU;AAC3B,YAAQ,gBAAgB,IAAI;AAAA,EAC9B;AACF;;;ACrNA,IAAM,oBAAoB,oBAAI,QAAiB;AAe/C,SAAS,UAAU,SAAuB;AAExC,UAAQ,KAAK,OAAO;AACtB;AAGe,SAAR,YAA6B,QAAiC;AACnE,SAAO,gBAAgB,MAAM,IAAI,OAAO,SAAS,OAAO,CAAC,GAAG;AAE5D,SAAO,cAAc,CAAC,IAAa,SAAqB;AACtD,UAAM,SAAS,oBAAoB,EAAE;AACrC,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AAEA,QAAI,kBAAkB,IAAI,EAAE,GAAG;AAC7B;AAAA,IACF;AAEA,UAAM,SAAS,sBAAsB,EAAE;AACvC,QAAI,CAAC,QAAQ;AACX,gBAAU,sDAAsD;AAChE;AAAA,IACF;AAEA,QAAI,qBAAqB,EAAE,IAAI,GAAG;AAChC,gBAAU,8DAA8D;AAAA,IAC1E;AAEA,UAAM,QAAS,OAA2B;AAC1C,QAAI,OAAO,UAAU,YAAY;AAC/B;AAAA,QACE;AAAA,MACF;AACA;AAAA,IACF;AAIA,sBAAkB,IAAI,EAAE;AACxB,IAAC,GAAqB,gBAAgB;AACtC,IAAC,OAAyB,YAAY;AACtC,SAAK;AAEL,WAAO,SAAS,MAAM;AACpB,UAAI,EAAE,GAAG,eAAe,OAAO,cAAc;AAC3C;AAAA,MACF;AAEA,yBAAmB,IAAI,QAAQ,OAAO,IAAI;AAE1C,UAAI,WAA2B;AAE/B,aAAO,UAAU,MAAM;AACrB,cAAM,IAAI,OAAO,WAAW;AAAA,UAC1B,MAAM,MAAM;AACV,gBAAI,KAAK,aAAa,KAAK,cAAc;AACvC,yBAAW;AAAA,YACb;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAED,YAAM,SAAS,aAAa,GAAG,cAAc,KAAK;AAClD,UAAI,QAAQ;AACV,0BAAkB,IAAI,MAAM;AAC5B,QAAC,OAAyB,YAAY;AACtC,eAAO,SAAS,MAAqB;AAAA,MACvC;AAEA,iCAA2B,EAAE;AAAA,IAC/B,CAAC;AAAA,EACH,CAAC;AAED,SAAO,UAAU,SAAS,MAAM;AAAA,EAEhC,CAAC,EAAE,OAAO,QAAQ;AACpB;","names":[]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@ailuracode/alpine-child",
3
+ "version": "0.1.0",
4
+ "description": "Alpine.js x-child directive — asChild-style attribute transfer to the first child element",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "ailuracode",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/ailuracode/alpinejs-toolkit.git",
14
+ "directory": "packages/child"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/ailuracode/alpinejs-toolkit/issues"
18
+ },
19
+ "homepage": "https://github.com/ailuracode/alpinejs-toolkit/tree/master/packages/child#readme",
20
+ "files": [
21
+ "dist",
22
+ "README.md"
23
+ ],
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ },
29
+ "./global": {
30
+ "types": "./dist/global.d.ts"
31
+ }
32
+ },
33
+ "types": "./dist/global.d.ts",
34
+ "peerDependencies": {
35
+ "@alpinejs/morph": "^3.0.0",
36
+ "@types/alpinejs": "^3.13.11",
37
+ "alpinejs": "^3.0.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "@types/alpinejs": {
41
+ "optional": true
42
+ }
43
+ },
44
+ "devDependencies": {
45
+ "@alpinejs/morph": "^3.15.12"
46
+ },
47
+ "keywords": [
48
+ "alpinejs",
49
+ "alpine",
50
+ "plugin",
51
+ "directive",
52
+ "as-child",
53
+ "headless"
54
+ ],
55
+ "scripts": {
56
+ "build": "tsup src/index.ts --format esm --dts --clean --sourcemap --out-dir dist && cp src/global.d.ts dist/global.d.ts",
57
+ "test": "vitest run --config ../../vitest.config.ts test"
58
+ }
59
+ }