@svelte-atoms/core 1.0.0-alpha.29 → 1.0.0-alpha.31
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 +3 -2
- package/dist/attachments/clickout.svelte.d.ts +1 -1
- package/dist/attachments/clickout.svelte.js +2 -2
- package/dist/components/accordion/accordion-root.svelte +65 -61
- package/dist/components/accordion/accordion-root.svelte.d.ts +1 -1
- package/dist/components/accordion/accordion.stories.svelte +70 -134
- package/dist/components/accordion/item/accordion-item-body.svelte +44 -42
- package/dist/components/accordion/item/accordion-item-header.svelte +51 -50
- package/dist/components/accordion/item/accordion-item-indicator.svelte +51 -50
- package/dist/components/accordion/item/accordion-item-root.svelte +66 -65
- package/dist/components/accordion/item/bond.svelte.d.ts +2 -0
- package/dist/components/accordion/item/index.d.ts +3 -0
- package/dist/components/accordion/item/index.js +3 -0
- package/dist/components/accordion/item/motion.svelte.d.ts +15 -0
- package/dist/components/accordion/item/motion.svelte.js +30 -0
- package/dist/components/accordion/item/types.d.ts +7 -24
- package/dist/components/alert/alert-close-button.svelte +66 -70
- package/dist/components/alert/alert-description.svelte +42 -42
- package/dist/components/alert/alert-description.svelte.d.ts +3 -6
- package/dist/components/alert/alert-root.svelte +68 -103
- package/dist/components/alert/alert-root.svelte.d.ts +2 -2
- package/dist/components/alert/alert.stories.svelte +10 -11
- package/dist/components/alert/bond.svelte.d.ts +0 -13
- package/dist/components/alert/bond.svelte.js +0 -32
- package/dist/components/alert/types.d.ts +8 -32
- package/dist/components/atom/html-atom.svelte +261 -207
- package/dist/components/avatar/avatar.stories.svelte +8 -13
- package/dist/components/badge/badge.stories.svelte +1 -6
- package/dist/components/badge/badge.svelte +1 -1
- package/dist/components/breadcrumb/breadcrumb.stories.svelte +16 -21
- package/dist/components/button/button.stories.svelte +1 -34
- package/dist/components/calendar/calendar-day.svelte +9 -4
- package/dist/components/calendar/calendar-header.svelte +29 -29
- package/dist/components/calendar/calendar-root.svelte +206 -206
- package/dist/components/calendar/calendar.stories.svelte +26 -31
- package/dist/components/card/card-body.svelte +1 -1
- package/dist/components/card/card-footer.svelte +1 -1
- package/dist/components/card/card-root.svelte +1 -1
- package/dist/components/card/card.stories.svelte +92 -104
- package/dist/components/checkbox/checkbox.stories.svelte +4 -9
- package/dist/components/checkbox/checkbox.svelte +159 -157
- package/dist/components/collapsible/collapsible.stories.svelte +2 -3
- package/dist/components/combobox/atoms.d.ts +1 -1
- package/dist/components/combobox/atoms.js +1 -1
- package/dist/components/combobox/combobox-root.svelte +1 -1
- package/dist/components/combobox/compobox.stories.svelte +19 -22
- package/dist/components/combobox/index.d.ts +1 -0
- package/dist/components/container/container.stories.svelte +8 -11
- package/dist/components/container/container.svelte.d.ts +1 -1
- package/dist/components/datagrid/datagrid-root.svelte +59 -59
- package/dist/components/datagrid/datagrid.css +5 -5
- package/dist/components/datagrid/datagrid.stories.svelte +47 -50
- package/dist/components/datagrid/tr/bond.svelte.d.ts +4 -2
- package/dist/components/datagrid/tr/bond.svelte.js +9 -7
- package/dist/components/datagrid/tr/datagrid-tr.svelte +90 -88
- package/dist/components/date-picker/date-picker-calendar.svelte +2 -2
- package/dist/components/date-picker/date-picker-header.svelte +100 -100
- package/dist/components/date-picker/date-picker-months.svelte +142 -142
- package/dist/components/date-picker/date-picker-root.svelte +95 -95
- package/dist/components/date-picker/date-picker-years.svelte +205 -205
- package/dist/components/date-picker/date-picker.stories.svelte +35 -42
- package/dist/components/dialog/bond.svelte.d.ts +13 -3
- package/dist/components/dialog/bond.svelte.js +66 -5
- package/dist/components/dialog/dialog-content.svelte +2 -20
- package/dist/components/dialog/dialog-root.svelte +91 -110
- package/dist/components/dialog/dialog.stories.svelte +34 -37
- package/dist/components/dialog/motion.svelte.d.ts +13 -0
- package/dist/components/dialog/motion.svelte.js +44 -0
- package/dist/components/drawer/attachments.svelte.d.ts +1 -1
- package/dist/components/drawer/attachments.svelte.js +7 -10
- package/dist/components/drawer/bond.svelte.d.ts +24 -5
- package/dist/components/drawer/bond.svelte.js +77 -11
- package/dist/components/drawer/drawer-content.svelte +49 -42
- package/dist/components/drawer/drawer.stories.svelte +144 -224
- package/dist/components/drawer/index.d.ts +2 -0
- package/dist/components/drawer/index.js +2 -0
- package/dist/components/drawer/motion.d.ts +15 -0
- package/dist/components/drawer/motion.js +28 -0
- package/dist/components/dropdown/atoms.d.ts +1 -1
- package/dist/components/dropdown/atoms.js +1 -1
- package/dist/components/dropdown/bond.svelte.d.ts +5 -1
- package/dist/components/dropdown/dropdown-root.svelte +1 -1
- package/dist/components/dropdown/dropdown.stories.svelte +38 -41
- package/dist/components/dropdown/index.d.ts +1 -0
- package/dist/components/form/form.stories.svelte +58 -61
- package/dist/components/image/image.stories.svelte +9 -12
- package/dist/components/input/input.stories.svelte +11 -14
- package/dist/components/label/label.stories.svelte +1 -12
- package/dist/components/label/label.stories.svelte.d.ts +24 -4
- package/dist/components/lazy/lazy.stories.svelte +28 -35
- package/dist/components/lazy/lazy.svelte +28 -28
- package/dist/components/link/link.stories.svelte +1 -12
- package/dist/components/link/link.stories.svelte.d.ts +24 -4
- package/dist/components/list/list-item.svelte +20 -20
- package/dist/components/menu/atoms.d.ts +1 -0
- package/dist/components/menu/atoms.js +1 -0
- package/dist/components/menu/index.d.ts +2 -1
- package/dist/components/menu/index.js +1 -1
- package/dist/components/menu/menu-item.svelte +69 -51
- package/dist/components/menu/menu-item.svelte.d.ts +1 -0
- package/dist/components/menu/menu-list.svelte +40 -40
- package/dist/components/menu/menu.stories.svelte +9 -12
- package/dist/components/popover/bond.svelte.d.ts +20 -7
- package/dist/components/popover/bond.svelte.js +104 -45
- package/dist/components/popover/motion.d.ts +6 -0
- package/dist/components/popover/motion.js +56 -0
- package/dist/components/popover/popover-arrow.svelte +4 -4
- package/dist/components/popover/popover-content.svelte +137 -178
- package/dist/components/popover/popover-indicator.svelte +2 -1
- package/dist/components/popover/popover-root.svelte +1 -1
- package/dist/components/popover/popover.stories.svelte +49 -52
- package/dist/components/popover/types.d.ts +9 -7
- package/dist/components/portal/active-portal.svelte +29 -22
- package/dist/components/portal/active-portal.svelte.d.ts +2 -9
- package/dist/components/portal/portal-root.svelte +76 -83
- package/dist/components/portal/portal-root.svelte.d.ts +4 -6
- package/dist/components/portal/teleport.svelte +49 -50
- package/dist/components/portal/teleport.svelte.d.ts +3 -4
- package/dist/components/qr-code/qr-code.stories.svelte +18 -27
- package/dist/components/qr-code/qr-code.svelte +75 -75
- package/dist/components/radio/radio-group.stories.svelte +21 -30
- package/dist/components/radio/radio.stories.svelte +1 -10
- package/dist/components/radio/radio.svelte +109 -109
- package/dist/components/radio/types.d.ts +98 -0
- package/dist/components/radio/types.js +2 -0
- package/dist/components/root/root.svelte +104 -121
- package/dist/components/scrollable/scrollable-root.svelte.d.ts +2 -2
- package/dist/components/scrollable/scrollable.stories.svelte +95 -105
- package/dist/components/sidebar/index.d.ts +2 -0
- package/dist/components/sidebar/index.js +2 -0
- package/dist/components/sidebar/motion.svelte.d.ts +11 -0
- package/dist/components/sidebar/motion.svelte.js +16 -0
- package/dist/components/sidebar/sidebar-content.svelte +3 -2
- package/dist/components/sidebar/sidebar-root.svelte +39 -41
- package/dist/components/sidebar/sidebar.stories.svelte +43 -54
- package/dist/components/sidebar/types.d.ts +3 -12
- package/dist/components/tabs/tab/bond.svelte.d.ts +4 -1
- package/dist/components/tabs/tab/bond.svelte.js +4 -1
- package/dist/components/tabs/tabs.stories.svelte +31 -34
- package/dist/components/textarea/atoms.d.ts +1 -0
- package/dist/components/textarea/atoms.js +1 -0
- package/dist/components/textarea/textarea-input.svelte +9 -6
- package/dist/components/textarea/textarea-root.svelte +9 -9
- package/dist/components/textarea/textarea-root.svelte.d.ts +2 -0
- package/dist/components/tooltip/tooltip-trigger.svelte +39 -37
- package/dist/components/tooltip/tooltip-trigger.svelte.d.ts +1 -0
- package/dist/components/tooltip/tooltip.stories.svelte +7 -10
- package/dist/components/tree/tree.stories.svelte +102 -94
- package/dist/context/preset.svelte.d.ts +3 -3
- package/dist/icons/icon-copy.svelte +6 -0
- package/dist/{components/radio/types.svelte.d.ts → icons/icon-copy.svelte.d.ts} +3 -3
- package/dist/utils/function.d.ts +2 -0
- package/dist/utils/function.js +6 -0
- package/dist/utils/markdown-to-llm.d.ts +28 -0
- package/dist/utils/markdown-to-llm.js +76 -0
- package/package.json +6 -10
- package/dist/actions/animation.svelte.d.ts +0 -6
- package/dist/actions/animation.svelte.js +0 -14
- package/dist/actions/clickout.svelte.d.ts +0 -2
- package/dist/actions/clickout.svelte.js +0 -15
- package/dist/actions/popover.svelte.d.ts +0 -19
- package/dist/actions/popover.svelte.js +0 -81
- package/dist/actions/portal.svelte.d.ts +0 -8
- package/dist/actions/portal.svelte.js +0 -32
- package/dist/attachments/gsap.svelte.d.ts +0 -2
- package/dist/attachments/gsap.svelte.js +0 -26
- package/dist/components/radio/types.svelte +0 -0
- package/llm/composition.md +0 -395
- package/llm/crafting.md +0 -838
- package/llm/motion.md +0 -970
- package/llm/philosophy.md +0 -23
- package/llm/preset-variant-integration.md +0 -516
- package/llm/preset.md +0 -383
- package/llm/styling.md +0 -216
- package/llm/usage.md +0 -46
- package/llm/variants.md +0 -1259
package/llm/crafting.md
DELETED
|
@@ -1,838 +0,0 @@
|
|
|
1
|
-
# Crafting Components from Scratch in @svelte-atoms/core
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
Components in @svelte-atoms/core follow a **Bond-based architecture** where state, DOM element references, and component methods are centralized in Bond/BondState classes. This guide shows how to build components from scratch using the Bond pattern.
|
|
6
|
-
|
|
7
|
-
## Component Architecture
|
|
8
|
-
|
|
9
|
-
Every component consists of three layers:
|
|
10
|
-
|
|
11
|
-
1. **Bond Layer** (`bond.svelte.ts`) - State management and DOM coordination
|
|
12
|
-
2. **Root Component** (`component-root.svelte`) - Parent container that creates and shares the bond
|
|
13
|
-
3. **Child Components** (`component-title.svelte`, etc.) - Access shared bond via context
|
|
14
|
-
|
|
15
|
-
## Core Building Blocks
|
|
16
|
-
|
|
17
|
-
### 1. Base Classes
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
import { Bond, BondState, type BondStateProps } from '$svelte-atoms/core/shared/bond.svelte';
|
|
21
|
-
|
|
22
|
-
// BondStateProps: Base props type with optional id
|
|
23
|
-
export type BondStateProps = Record<string, unknown> & { id?: string };
|
|
24
|
-
|
|
25
|
-
// BondState: Manages component props and generates unique IDs
|
|
26
|
-
export abstract class BondState<S extends BondStateProps = BondStateProps> {
|
|
27
|
-
constructor(props: () => S, id?: string);
|
|
28
|
-
get id(): string;
|
|
29
|
-
get props(): S;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Bond: Manages component state, DOM elements, and context sharing
|
|
33
|
-
export abstract class Bond<
|
|
34
|
-
Props extends BondStateProps = BondStateProps,
|
|
35
|
-
State extends BondState<Props> = BondState<Props>,
|
|
36
|
-
Elements extends BondElements = BondElements
|
|
37
|
-
> {
|
|
38
|
-
constructor(state: State);
|
|
39
|
-
get state(): State;
|
|
40
|
-
get elements(): Elements;
|
|
41
|
-
abstract share(): this;
|
|
42
|
-
static get(): unknown | undefined;
|
|
43
|
-
static set(bond: unknown): unknown;
|
|
44
|
-
}
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### 2. Utility Functions
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
import { defineState, defineProperty } from '$svelte-atoms/core/utils';
|
|
51
|
-
|
|
52
|
-
// defineState: Create reactive props object
|
|
53
|
-
const bondProps = defineState<MyBondProps>(
|
|
54
|
-
[
|
|
55
|
-
defineProperty('open', () => open, (v) => { open = v; }),
|
|
56
|
-
defineProperty('disabled', () => disabled)
|
|
57
|
-
],
|
|
58
|
-
() => ({ extend: {} })
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
// defineProperty: Create reactive property with getter/setter
|
|
62
|
-
defineProperty<T, R>(
|
|
63
|
-
property: keyof T,
|
|
64
|
-
get: () => R,
|
|
65
|
-
set?: (value: R) => void
|
|
66
|
-
)
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Step-by-Step: Building a Component
|
|
70
|
-
|
|
71
|
-
### Step 1: Define Bond Types and Classes
|
|
72
|
-
|
|
73
|
-
Create `my-component/bond.svelte.ts`:
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
import { createAttachmentKey } from 'svelte/attachments';
|
|
77
|
-
import { getContext, setContext } from 'svelte';
|
|
78
|
-
import { getElementId } from '$svelte-atoms/core/utils/dom.svelte';
|
|
79
|
-
import { Bond, BondState, type BondStateProps } from '$svelte-atoms/core/shared/bond.svelte';
|
|
80
|
-
|
|
81
|
-
// 1. Define Props Type
|
|
82
|
-
export type MyComponentBondProps = BondStateProps & {
|
|
83
|
-
open: boolean;
|
|
84
|
-
disabled: boolean;
|
|
85
|
-
variant?: 'default' | 'primary' | 'danger';
|
|
86
|
-
extend?: Record<string, unknown>;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// 2. Define Elements Type (all DOM elements your component manages)
|
|
90
|
-
export type MyComponentBondElements = {
|
|
91
|
-
root: HTMLElement;
|
|
92
|
-
header: HTMLElement;
|
|
93
|
-
title: HTMLElement;
|
|
94
|
-
content: HTMLElement;
|
|
95
|
-
footer: HTMLElement;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// 3. Define Element Kinds (for data-kind attributes and ID generation)
|
|
99
|
-
const MY_COMPONENT_ELEMENTS_KIND = {
|
|
100
|
-
root: 'my-component-root',
|
|
101
|
-
header: 'my-component-header',
|
|
102
|
-
title: 'my-component-title',
|
|
103
|
-
content: 'my-component-content',
|
|
104
|
-
footer: 'my-component-footer'
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
// 4. Create BondState Class
|
|
108
|
-
export class MyComponentBondState<
|
|
109
|
-
Props extends MyComponentBondProps = MyComponentBondProps
|
|
110
|
-
> extends BondState<Props> {
|
|
111
|
-
constructor(props: () => Props) {
|
|
112
|
-
super(props);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Add component-specific methods
|
|
116
|
-
open() {
|
|
117
|
-
this.props.open = true;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
close() {
|
|
121
|
-
this.props.open = false;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
toggle() {
|
|
125
|
-
this.props.open = !this.props.open;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// 5. Create Bond Class
|
|
130
|
-
export class MyComponentBond<
|
|
131
|
-
Props extends MyComponentBondProps = MyComponentBondProps,
|
|
132
|
-
State extends MyComponentBondState<Props> = MyComponentBondState<Props>
|
|
133
|
-
> extends Bond<Props, State, MyComponentBondElements> {
|
|
134
|
-
static CONTEXT_KEY = '@atoms/context/my-component';
|
|
135
|
-
|
|
136
|
-
constructor(state: State) {
|
|
137
|
-
super(state);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// 6. Create element prop generators (return props + attachment for each element)
|
|
141
|
-
root(props: Record<string, unknown> = {}) {
|
|
142
|
-
const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.root);
|
|
143
|
-
const titleId = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.title);
|
|
144
|
-
|
|
145
|
-
const isOpen = this.state.props.open ?? false;
|
|
146
|
-
const isDisabled = this.state.props.disabled ?? false;
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
id,
|
|
150
|
-
role: 'dialog',
|
|
151
|
-
'aria-labelledby': titleId,
|
|
152
|
-
'aria-expanded': isOpen,
|
|
153
|
-
'aria-disabled': isDisabled,
|
|
154
|
-
'data-kind': MY_COMPONENT_ELEMENTS_KIND.root,
|
|
155
|
-
'data-variant': this.state.props.variant,
|
|
156
|
-
...props,
|
|
157
|
-
[createAttachmentKey()]: (node: HTMLElement) => {
|
|
158
|
-
this.elements.root = node;
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
header(props: Record<string, unknown> = {}) {
|
|
164
|
-
const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.header);
|
|
165
|
-
return {
|
|
166
|
-
id,
|
|
167
|
-
'data-kind': MY_COMPONENT_ELEMENTS_KIND.header,
|
|
168
|
-
...props,
|
|
169
|
-
[createAttachmentKey()]: (node: HTMLElement) => {
|
|
170
|
-
this.elements.header = node;
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
title(props: Record<string, unknown> = {}) {
|
|
176
|
-
const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.title);
|
|
177
|
-
return {
|
|
178
|
-
id,
|
|
179
|
-
'data-kind': MY_COMPONENT_ELEMENTS_KIND.title,
|
|
180
|
-
...props,
|
|
181
|
-
[createAttachmentKey()]: (node: HTMLElement) => {
|
|
182
|
-
this.elements.title = node;
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
content(props: Record<string, unknown> = {}) {
|
|
188
|
-
const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.content);
|
|
189
|
-
return {
|
|
190
|
-
id,
|
|
191
|
-
role: 'region',
|
|
192
|
-
'data-kind': MY_COMPONENT_ELEMENTS_KIND.content,
|
|
193
|
-
...props,
|
|
194
|
-
[createAttachmentKey()]: (node: HTMLElement) => {
|
|
195
|
-
this.elements.content = node;
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
footer(props: Record<string, unknown> = {}) {
|
|
201
|
-
const id = getElementId(this.id, MY_COMPONENT_ELEMENTS_KIND.footer);
|
|
202
|
-
return {
|
|
203
|
-
id,
|
|
204
|
-
'data-kind': MY_COMPONENT_ELEMENTS_KIND.footer,
|
|
205
|
-
...props,
|
|
206
|
-
[createAttachmentKey()]: (node: HTMLElement) => {
|
|
207
|
-
this.elements.footer = node;
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// 7. Implement share() to set bond in context
|
|
213
|
-
share(): this {
|
|
214
|
-
return MyComponentBond.set(this) as this;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// 8. Implement static get/set for context management
|
|
218
|
-
static get(): MyComponentBond | undefined {
|
|
219
|
-
return getContext(MyComponentBond.CONTEXT_KEY);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
static set(bond: MyComponentBond): MyComponentBond {
|
|
223
|
-
return setContext(MyComponentBond.CONTEXT_KEY, bond);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### Step 2: Create Root Component
|
|
229
|
-
|
|
230
|
-
Create `my-component/my-component-root.svelte`:
|
|
231
|
-
|
|
232
|
-
```svelte
|
|
233
|
-
<script module lang="ts">
|
|
234
|
-
import type { Factory } from '$svelte-atoms/core/types';
|
|
235
|
-
|
|
236
|
-
export type MyComponentRootProps<
|
|
237
|
-
E extends keyof HTMLElementTagNameMap = 'div',
|
|
238
|
-
B extends Base = Base
|
|
239
|
-
> = Override<
|
|
240
|
-
HtmlAtomProps<E, B>,
|
|
241
|
-
{
|
|
242
|
-
children?: Snippet<[{ myComponent: MyComponentBond }]>;
|
|
243
|
-
}
|
|
244
|
-
> & {
|
|
245
|
-
open?: boolean;
|
|
246
|
-
disabled?: boolean;
|
|
247
|
-
variant?: 'default' | 'primary' | 'danger';
|
|
248
|
-
onclose?: (event: Event, bond: MyComponentBond) => void;
|
|
249
|
-
factory?: Factory<MyComponentBond>;
|
|
250
|
-
};
|
|
251
|
-
</script>
|
|
252
|
-
|
|
253
|
-
<script lang="ts" generics="E extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
|
|
254
|
-
import type { Snippet } from 'svelte';
|
|
255
|
-
import { defineProperty, defineState } from '$svelte-atoms/core/utils';
|
|
256
|
-
import { HtmlAtom, type HtmlAtomProps, type Base } from '$svelte-atoms/core/components/atom';
|
|
257
|
-
import type { Override } from '$svelte-atoms/core/types';
|
|
258
|
-
import { MyComponentBond, MyComponentBondState, type MyComponentBondProps } from './bond.svelte';
|
|
259
|
-
|
|
260
|
-
// 1. Define props with defaults and $bindable for two-way binding
|
|
261
|
-
let {
|
|
262
|
-
open = $bindable(false),
|
|
263
|
-
disabled = false,
|
|
264
|
-
variant = 'default',
|
|
265
|
-
children = undefined,
|
|
266
|
-
class: klass = '',
|
|
267
|
-
onclose = undefined,
|
|
268
|
-
onmount = undefined,
|
|
269
|
-
ondestroy = undefined,
|
|
270
|
-
animate = undefined,
|
|
271
|
-
enter = undefined,
|
|
272
|
-
exit = undefined,
|
|
273
|
-
initial = undefined,
|
|
274
|
-
factory = _factory,
|
|
275
|
-
...restProps
|
|
276
|
-
}: MyComponentRootProps<E, B> = $props();
|
|
277
|
-
|
|
278
|
-
// 2. Create reactive bond props using defineState and defineProperty
|
|
279
|
-
const bondProps = defineState<MyComponentBondProps>(
|
|
280
|
-
[
|
|
281
|
-
defineProperty(
|
|
282
|
-
'open',
|
|
283
|
-
() => open,
|
|
284
|
-
(v) => {
|
|
285
|
-
open = v;
|
|
286
|
-
}
|
|
287
|
-
),
|
|
288
|
-
defineProperty('disabled', () => disabled),
|
|
289
|
-
defineProperty('variant', () => variant)
|
|
290
|
-
],
|
|
291
|
-
() => ({})
|
|
292
|
-
);
|
|
293
|
-
|
|
294
|
-
// 3. Create and share bond
|
|
295
|
-
const bond = factory(bondProps).share();
|
|
296
|
-
|
|
297
|
-
// 4. Merge bond props with user props
|
|
298
|
-
const rootProps = $derived({
|
|
299
|
-
...bond.root(),
|
|
300
|
-
...restProps
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
// 5. Factory function to create bond
|
|
304
|
-
function _factory(props: typeof bondProps) {
|
|
305
|
-
const state = new MyComponentBondState(() => props);
|
|
306
|
-
return new MyComponentBond(state);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// 6. Optional: Export function to access bond from parent
|
|
310
|
-
export function getBond() {
|
|
311
|
-
return bond;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// 7. Optional: Handle side effects
|
|
315
|
-
$effect(() => {
|
|
316
|
-
if (open && !disabled) {
|
|
317
|
-
// Do something when opened
|
|
318
|
-
console.log('Component opened');
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
</script>
|
|
322
|
-
|
|
323
|
-
<HtmlAtom
|
|
324
|
-
preset="my-component"
|
|
325
|
-
class={[
|
|
326
|
-
'my-component bg-card border-border rounded-lg border shadow-sm',
|
|
327
|
-
disabled && 'cursor-not-allowed opacity-50',
|
|
328
|
-
'$preset',
|
|
329
|
-
klass
|
|
330
|
-
]}
|
|
331
|
-
{bond}
|
|
332
|
-
enter={enter?.bind(bond.state)}
|
|
333
|
-
exit={exit?.bind(bond.state)}
|
|
334
|
-
initial={initial?.bind(bond.state)}
|
|
335
|
-
animate={animate?.bind(bond.state)}
|
|
336
|
-
onmount={onmount?.bind(bond.state)}
|
|
337
|
-
ondestroy={ondestroy?.bind(bond.state)}
|
|
338
|
-
{...rootProps}
|
|
339
|
-
>
|
|
340
|
-
{@render children?.({ myComponent: bond })}
|
|
341
|
-
</HtmlAtom>
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
### Step 3: Create Child Components
|
|
345
|
-
|
|
346
|
-
Create `my-component/my-component-title.svelte`:
|
|
347
|
-
|
|
348
|
-
```svelte
|
|
349
|
-
<script module lang="ts">
|
|
350
|
-
export type MyComponentTitleProps<
|
|
351
|
-
E extends keyof HTMLElementTagNameMap = 'h3',
|
|
352
|
-
B extends Base = Base
|
|
353
|
-
> = HtmlAtomProps<E, B>;
|
|
354
|
-
</script>
|
|
355
|
-
|
|
356
|
-
<script lang="ts" generics="E extends keyof HTMLElementTagNameMap = 'h3', B extends Base = Base">
|
|
357
|
-
import { HtmlAtom, type HtmlAtomProps, type Base } from '$svelte-atoms/core/components/atom';
|
|
358
|
-
import { MyComponentBond } from './bond.svelte';
|
|
359
|
-
|
|
360
|
-
// 1. Get bond from context (shared by root component)
|
|
361
|
-
const bond = MyComponentBond.get();
|
|
362
|
-
|
|
363
|
-
// 2. Define props
|
|
364
|
-
let {
|
|
365
|
-
class: klass = '',
|
|
366
|
-
as = 'h3' as E,
|
|
367
|
-
children = undefined,
|
|
368
|
-
onmount = undefined,
|
|
369
|
-
ondestroy = undefined,
|
|
370
|
-
animate = undefined,
|
|
371
|
-
enter = undefined,
|
|
372
|
-
exit = undefined,
|
|
373
|
-
initial = undefined,
|
|
374
|
-
...restProps
|
|
375
|
-
}: MyComponentTitleProps<E, B> = $props();
|
|
376
|
-
|
|
377
|
-
// 3. Merge bond props with user props
|
|
378
|
-
const titleProps = $derived({
|
|
379
|
-
...bond?.title(),
|
|
380
|
-
...restProps
|
|
381
|
-
});
|
|
382
|
-
</script>
|
|
383
|
-
|
|
384
|
-
<HtmlAtom
|
|
385
|
-
{as}
|
|
386
|
-
{bond}
|
|
387
|
-
preset="my-component.title"
|
|
388
|
-
class={['my-component-title text-lg font-semibold', '$preset', klass]}
|
|
389
|
-
enter={enter?.bind(bond.state)}
|
|
390
|
-
exit={exit?.bind(bond.state)}
|
|
391
|
-
initial={initial?.bind(bond.state)}
|
|
392
|
-
animate={animate?.bind(bond.state)}
|
|
393
|
-
onmount={onmount?.bind(bond.state)}
|
|
394
|
-
ondestroy={ondestroy?.bind(bond.state)}
|
|
395
|
-
{...titleProps}
|
|
396
|
-
>
|
|
397
|
-
{@render children?.()}
|
|
398
|
-
</HtmlAtom>
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
Create `my-component/my-component-content.svelte`:
|
|
402
|
-
|
|
403
|
-
```svelte
|
|
404
|
-
<script module lang="ts">
|
|
405
|
-
export type MyComponentContentProps<
|
|
406
|
-
E extends keyof HTMLElementTagNameMap = 'div',
|
|
407
|
-
B extends Base = Base
|
|
408
|
-
> = Override<
|
|
409
|
-
HtmlAtomProps<E, B>,
|
|
410
|
-
{
|
|
411
|
-
children?: Snippet<[{ myComponent?: MyComponentBond }]>;
|
|
412
|
-
}
|
|
413
|
-
>;
|
|
414
|
-
</script>
|
|
415
|
-
|
|
416
|
-
<script lang="ts" generics="E extends keyof HTMLElementTagNameMap = 'div', B extends Base = Base">
|
|
417
|
-
import type { Snippet } from 'svelte';
|
|
418
|
-
import { HtmlAtom, type HtmlAtomProps, type Base } from '$svelte-atoms/core/components/atom';
|
|
419
|
-
import type { Override } from '$svelte-atoms/core/types';
|
|
420
|
-
import { MyComponentBond } from './bond.svelte';
|
|
421
|
-
|
|
422
|
-
const bond = MyComponentBond.get();
|
|
423
|
-
|
|
424
|
-
let {
|
|
425
|
-
class: klass = '',
|
|
426
|
-
children = undefined,
|
|
427
|
-
onmount = undefined,
|
|
428
|
-
ondestroy = undefined,
|
|
429
|
-
animate = undefined,
|
|
430
|
-
enter = undefined,
|
|
431
|
-
exit = undefined,
|
|
432
|
-
initial = undefined,
|
|
433
|
-
...restProps
|
|
434
|
-
}: MyComponentContentProps<E, B> = $props();
|
|
435
|
-
|
|
436
|
-
const contentProps = $derived({
|
|
437
|
-
...bond?.content(),
|
|
438
|
-
...restProps
|
|
439
|
-
});
|
|
440
|
-
</script>
|
|
441
|
-
|
|
442
|
-
<HtmlAtom
|
|
443
|
-
preset="my-component.content"
|
|
444
|
-
class={['my-component-content p-4', '$preset', klass]}
|
|
445
|
-
{bond}
|
|
446
|
-
onmount={onmount?.bind(bond.state)}
|
|
447
|
-
ondestroy={ondestroy?.bind(bond.state)}
|
|
448
|
-
enter={enter?.bind(bond.state)}
|
|
449
|
-
exit={exit?.bind(bond.state)}
|
|
450
|
-
initial={initial?.bind(bond.state)}
|
|
451
|
-
animate={animate?.bind(bond.state)}
|
|
452
|
-
{...contentProps}
|
|
453
|
-
>
|
|
454
|
-
{@render children?.({ myComponent: bond })}
|
|
455
|
-
</HtmlAtom>
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
### Step 4: Create Index Exports
|
|
459
|
-
|
|
460
|
-
Create `my-component/atoms.ts`:
|
|
461
|
-
|
|
462
|
-
```typescript
|
|
463
|
-
export { default as Root } from './my-component-root.svelte';
|
|
464
|
-
export { default as Header } from './my-component-header.svelte';
|
|
465
|
-
export { default as Title } from './my-component-title.svelte';
|
|
466
|
-
export { default as Content } from './my-component-content.svelte';
|
|
467
|
-
export { default as Footer } from './my-component-footer.svelte';
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
Create `my-component/index.ts`:
|
|
471
|
-
|
|
472
|
-
```typescript
|
|
473
|
-
export * as MyComponent from './atoms';
|
|
474
|
-
export {
|
|
475
|
-
MyComponentBond,
|
|
476
|
-
MyComponentBondState,
|
|
477
|
-
type MyComponentBondProps,
|
|
478
|
-
type MyComponentBondElements
|
|
479
|
-
} from './bond.svelte';
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
### Step 5: Usage
|
|
483
|
-
|
|
484
|
-
```svelte
|
|
485
|
-
<script>
|
|
486
|
-
import { MyComponent } from '@svelte-atoms/core/components/my-component';
|
|
487
|
-
import { Button } from '@svelte-atoms/core/components/button';
|
|
488
|
-
|
|
489
|
-
let open = $state(false);
|
|
490
|
-
</script>
|
|
491
|
-
|
|
492
|
-
<MyComponent.Root bind:open>
|
|
493
|
-
{#snippet children({ myComponent })}
|
|
494
|
-
<MyComponent.Header>
|
|
495
|
-
<MyComponent.Title>My Component Title</MyComponent.Title>
|
|
496
|
-
</MyComponent.Header>
|
|
497
|
-
|
|
498
|
-
<MyComponent.Content>
|
|
499
|
-
<p>This is the content</p>
|
|
500
|
-
|
|
501
|
-
<!-- Access bond state -->
|
|
502
|
-
<p>Is open: {myComponent.state.isOpen}</p>
|
|
503
|
-
|
|
504
|
-
<!-- Control from inside -->
|
|
505
|
-
<Button onclick={() => myComponent.state.close()}>Close from inside</Button>
|
|
506
|
-
</MyComponent.Content>
|
|
507
|
-
|
|
508
|
-
<MyComponent.Footer>
|
|
509
|
-
<Button>Action</Button>
|
|
510
|
-
</MyComponent.Footer>
|
|
511
|
-
{/snippet}
|
|
512
|
-
</MyComponent.Root>
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
## Key Patterns
|
|
516
|
-
|
|
517
|
-
### 1. Reactive Props with defineState
|
|
518
|
-
|
|
519
|
-
Use `defineState` and `defineProperty` to create reactive props that sync with component props:
|
|
520
|
-
|
|
521
|
-
```typescript
|
|
522
|
-
const bondProps = defineState<MyBondProps>(
|
|
523
|
-
[
|
|
524
|
-
// Two-way binding: changes in component prop update bond, vice versa
|
|
525
|
-
defineProperty(
|
|
526
|
-
'open',
|
|
527
|
-
() => open,
|
|
528
|
-
(v) => {
|
|
529
|
-
open = v;
|
|
530
|
-
}
|
|
531
|
-
),
|
|
532
|
-
|
|
533
|
-
// Read-only: only reads from component prop
|
|
534
|
-
defineProperty('disabled', () => disabled),
|
|
535
|
-
|
|
536
|
-
// Computed: derived from other values
|
|
537
|
-
defineProperty('isActive', () => open && !disabled)
|
|
538
|
-
],
|
|
539
|
-
() => ({ extend: {} }) // Base props
|
|
540
|
-
);
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
### 2. Element Prop Generators
|
|
544
|
-
|
|
545
|
-
Each Bond method returns props object with:
|
|
546
|
-
|
|
547
|
-
- **Unique ID**: `getElementId(this.id, kind)`
|
|
548
|
-
- **ARIA attributes**: Accessibility attributes based on state
|
|
549
|
-
- **Data attributes**: `data-kind`, `data-variant`, etc.
|
|
550
|
-
- **Attachment key**: Captures DOM element reference
|
|
551
|
-
|
|
552
|
-
```typescript
|
|
553
|
-
content(props: Record<string, unknown> = {}) {
|
|
554
|
-
const id = getElementId(this.id, ELEMENT_KIND.content);
|
|
555
|
-
const isOpen = this.state.props.open ?? false;
|
|
556
|
-
|
|
557
|
-
return {
|
|
558
|
-
id,
|
|
559
|
-
role: 'region',
|
|
560
|
-
'aria-expanded': isOpen,
|
|
561
|
-
'data-kind': ELEMENT_KIND.content,
|
|
562
|
-
...props, // Merge user props
|
|
563
|
-
[createAttachmentKey()]: (node: HTMLElement) => {
|
|
564
|
-
this.elements.content = node; // Capture element
|
|
565
|
-
}
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
### 3. Context Sharing
|
|
571
|
-
|
|
572
|
-
Bond is shared via Svelte context:
|
|
573
|
-
|
|
574
|
-
```typescript
|
|
575
|
-
// Root component: Create and share
|
|
576
|
-
const bond = factory(bondProps).share();
|
|
577
|
-
|
|
578
|
-
// Bond.share() implementation
|
|
579
|
-
share(): this {
|
|
580
|
-
return MyComponentBond.set(this) as this;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
static set(bond: MyComponentBond): MyComponentBond {
|
|
584
|
-
return setContext(MyComponentBond.CONTEXT_KEY, bond);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// Child components: Access from context
|
|
588
|
-
const bond = MyComponentBond.get();
|
|
589
|
-
|
|
590
|
-
static get(): MyComponentBond | undefined {
|
|
591
|
-
return getContext(MyComponentBond.CONTEXT_KEY);
|
|
592
|
-
}
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
### 4. State Methods
|
|
596
|
-
|
|
597
|
-
Add component-specific methods to BondState:
|
|
598
|
-
|
|
599
|
-
```typescript
|
|
600
|
-
export class MyComponentBondState extends BondState<MyComponentBondProps> {
|
|
601
|
-
open() {
|
|
602
|
-
this.props.open = true;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
close() {
|
|
606
|
-
this.props.open = false;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
toggle() {
|
|
610
|
-
this.props.open = !this.props.open;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// Complex state logic
|
|
614
|
-
validateAndClose() {
|
|
615
|
-
if (this.isValid()) {
|
|
616
|
-
this.close();
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
isValid() {
|
|
621
|
-
return this.props.value !== undefined;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
### 5. Binding Lifecycle Hooks
|
|
627
|
-
|
|
628
|
-
Bind animation/lifecycle hooks to bond.state to pass state to callbacks:
|
|
629
|
-
|
|
630
|
-
```svelte
|
|
631
|
-
<HtmlAtom
|
|
632
|
-
{bond}
|
|
633
|
-
enter={enter?.bind(bond.state)}
|
|
634
|
-
exit={exit?.bind(bond.state)}
|
|
635
|
-
initial={initial?.bind(bond.state)}
|
|
636
|
-
animate={animate?.bind(bond.state)}
|
|
637
|
-
onmount={onmount?.bind(bond.state)}
|
|
638
|
-
ondestroy={ondestroy?.bind(bond.state)}
|
|
639
|
-
{...props}
|
|
640
|
-
>
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
This allows hooks to receive bond state as `this`:
|
|
644
|
-
|
|
645
|
-
```svelte
|
|
646
|
-
<MyComponent.Root
|
|
647
|
-
enter={function(node) {
|
|
648
|
-
// `this` is MyComponentBondState
|
|
649
|
-
console.log('Opening:', this.props.open);
|
|
650
|
-
return animate(node, { opacity: [0, 1] });
|
|
651
|
-
}}
|
|
652
|
-
>
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
### 6. Factory Pattern
|
|
656
|
-
|
|
657
|
-
Allow custom bond creation via factory prop:
|
|
658
|
-
|
|
659
|
-
```typescript
|
|
660
|
-
type MyComponentRootProps = {
|
|
661
|
-
factory?: Factory<MyComponentBond>;
|
|
662
|
-
};
|
|
663
|
-
|
|
664
|
-
function _factory(props: typeof bondProps) {
|
|
665
|
-
const state = new MyComponentBondState(() => props);
|
|
666
|
-
return new MyComponentBond(state);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// User can extend
|
|
670
|
-
<MyComponent.Root
|
|
671
|
-
factory={(props) => {
|
|
672
|
-
const state = new CustomBondState(() => props);
|
|
673
|
-
return new CustomBond(state);
|
|
674
|
-
}}
|
|
675
|
-
>
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
### 7. Attachment Key Pattern
|
|
679
|
-
|
|
680
|
-
Use `createAttachmentKey()` to capture DOM elements in bond:
|
|
681
|
-
|
|
682
|
-
```typescript
|
|
683
|
-
[createAttachmentKey()]: (node: HTMLElement) => {
|
|
684
|
-
this.elements.content = node;
|
|
685
|
-
|
|
686
|
-
// Optional: Do setup
|
|
687
|
-
node.addEventListener('scroll', handleScroll);
|
|
688
|
-
|
|
689
|
-
// Optional: Return cleanup
|
|
690
|
-
return () => {
|
|
691
|
-
node.removeEventListener('scroll', handleScroll);
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
```
|
|
695
|
-
|
|
696
|
-
## Advanced Patterns
|
|
697
|
-
|
|
698
|
-
### Extending Existing Bonds
|
|
699
|
-
|
|
700
|
-
Create component that extends another:
|
|
701
|
-
|
|
702
|
-
```typescript
|
|
703
|
-
// Dropdown extends Popover
|
|
704
|
-
export class DropdownBond<
|
|
705
|
-
Props extends DropdownBondProps = DropdownBondProps,
|
|
706
|
-
State extends DropdownBondState<Props> = DropdownBondState<Props>,
|
|
707
|
-
Elements extends DropdownBondElements = DropdownBondElements
|
|
708
|
-
> extends PopoverBond<Props, State, Elements> {
|
|
709
|
-
// Add dropdown-specific functionality
|
|
710
|
-
selectItem(value: string) {
|
|
711
|
-
this.state.props.value = value;
|
|
712
|
-
this.state.close();
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
export class DropdownBondState<
|
|
717
|
-
Props extends DropdownBondProps = DropdownBondProps
|
|
718
|
-
> extends PopoverState<Props> {
|
|
719
|
-
// Inherit open/close/toggle from PopoverState
|
|
720
|
-
// Add dropdown-specific state
|
|
721
|
-
}
|
|
722
|
-
```
|
|
723
|
-
|
|
724
|
-
### Multiple Element References
|
|
725
|
-
|
|
726
|
-
Track multiple instances of same element type:
|
|
727
|
-
|
|
728
|
-
```typescript
|
|
729
|
-
export type ListBondElements = {
|
|
730
|
-
root: HTMLElement;
|
|
731
|
-
items: HTMLElement[]; // Array of items
|
|
732
|
-
};
|
|
733
|
-
|
|
734
|
-
export class ListBond extends Bond<ListBondProps, ListBondState, ListBondElements> {
|
|
735
|
-
constructor(state: ListBondState) {
|
|
736
|
-
super(state);
|
|
737
|
-
this.elements.items = []; // Initialize array
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
item(index: number, props: Record<string, unknown> = {}) {
|
|
741
|
-
return {
|
|
742
|
-
id: getElementId(this.id, `item-${index}`),
|
|
743
|
-
'data-index': index,
|
|
744
|
-
...props,
|
|
745
|
-
[createAttachmentKey()]: (node: HTMLElement) => {
|
|
746
|
-
this.elements.items[index] = node;
|
|
747
|
-
}
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
```
|
|
752
|
-
|
|
753
|
-
### Validation Integration
|
|
754
|
-
|
|
755
|
-
Add validation to component state:
|
|
756
|
-
|
|
757
|
-
```typescript
|
|
758
|
-
export class FieldBondState extends BondState<FieldBondProps> {
|
|
759
|
-
#errors = $state<ValidationError[]>([]);
|
|
760
|
-
|
|
761
|
-
get errors() {
|
|
762
|
-
return this.#errors;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
validate(): ValidationResult {
|
|
766
|
-
const { schema, validator, value } = this.props;
|
|
767
|
-
|
|
768
|
-
if (!schema || !validator) {
|
|
769
|
-
return { success: true, errors: [] };
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
const result = validator.validate(schema, value);
|
|
773
|
-
this.#errors = result.errors;
|
|
774
|
-
|
|
775
|
-
this.props.onvalidation?.(result.errors);
|
|
776
|
-
|
|
777
|
-
return result;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
async validateAsync(): Promise<ValidationResult> {
|
|
781
|
-
const { schema, validator, value } = this.props;
|
|
782
|
-
|
|
783
|
-
if (!schema || !validator?.validateAsync) {
|
|
784
|
-
return this.validate();
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
const result = await validator.validateAsync(schema, value);
|
|
788
|
-
this.#errors = result.errors;
|
|
789
|
-
|
|
790
|
-
this.props.onvalidation?.(result.errors);
|
|
791
|
-
|
|
792
|
-
return result;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
```
|
|
796
|
-
|
|
797
|
-
## Component Checklist
|
|
798
|
-
|
|
799
|
-
When creating a new component:
|
|
800
|
-
|
|
801
|
-
- [ ] Define `MyComponentBondProps` type with all component props
|
|
802
|
-
- [ ] Define `MyComponentBondElements` type with all managed DOM elements
|
|
803
|
-
- [ ] Create element kinds object for consistent `data-kind` attributes
|
|
804
|
-
- [ ] Create `MyComponentBondState` class extending `BondState`
|
|
805
|
-
- [ ] Add state methods (open, close, validate, etc.)
|
|
806
|
-
- [ ] Create `MyComponentBond` class extending `Bond`
|
|
807
|
-
- [ ] Implement element prop generators (root, title, content, etc.)
|
|
808
|
-
- [ ] Implement `share()` method
|
|
809
|
-
- [ ] Implement static `get()` and `set()` methods with unique `CONTEXT_KEY`
|
|
810
|
-
- [ ] Create root component with `defineState`/`defineProperty` for reactive props
|
|
811
|
-
- [ ] Create factory function to instantiate bond
|
|
812
|
-
- [ ] Call `bond.share()` to set in context
|
|
813
|
-
- [ ] Create child components that access bond via `MyComponentBond.get()`
|
|
814
|
-
- [ ] Merge bond props with user props using `$derived`
|
|
815
|
-
- [ ] Bind lifecycle hooks to `bond.state`
|
|
816
|
-
- [ ] Export all components in `atoms.ts`
|
|
817
|
-
- [ ] Export bond classes and types in `index.ts`
|
|
818
|
-
- [ ] Add ARIA attributes for accessibility
|
|
819
|
-
- [ ] Add `data-kind` attributes for styling hooks
|
|
820
|
-
- [ ] Generate unique IDs using `getElementId()`
|
|
821
|
-
|
|
822
|
-
## Summary
|
|
823
|
-
|
|
824
|
-
The Bond pattern centralizes component state and DOM coordination:
|
|
825
|
-
|
|
826
|
-
1. **BondState**: Manages props and state methods
|
|
827
|
-
2. **Bond**: Manages DOM elements, generates props, shares via context
|
|
828
|
-
3. **Root Component**: Creates bond, shares via context, renders with children snippet
|
|
829
|
-
4. **Child Components**: Access bond from context, merge with own props
|
|
830
|
-
|
|
831
|
-
Benefits:
|
|
832
|
-
|
|
833
|
-
- **Centralized state**: All state and methods in one place
|
|
834
|
-
- **Type-safe**: Full TypeScript support across all components
|
|
835
|
-
- **Context-based**: Child components automatically access shared state
|
|
836
|
-
- **Composable**: Components can extend existing bonds
|
|
837
|
-
- **Accessible**: ARIA attributes automatically generated
|
|
838
|
-
- **Flexible**: User props override bond defaults
|