@vanilla-bean/components 1.0.2 → 1.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/components/Menu/Menu.js +3 -3
- package/index.d.ts +1 -0
- package/package.json +1 -1
- package/theme/.test.js +19 -3
- package/theme/README.md +7 -0
- package/theme/button.js +1 -1
- package/theme/colors.js +10 -3
- package/theme/input.js +3 -3
- package/theme/page.js +6 -0
- package/theme/scrollbar.js +7 -3
- package/theme/table.js +2 -2
- package/Component/README.md +0 -455
- package/Elem/README.md +0 -373
- package/styled/README.md +0 -329
package/components/Menu/Menu.js
CHANGED
|
@@ -18,12 +18,12 @@ const StyledList = styled(
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
&:hover, &:focus, &:focus-visible, & a:hover, & a:focus, & a:focus-visible {
|
|
21
|
-
color: ${colors.light(colors.
|
|
22
|
-
border-color: ${colors.light(colors.
|
|
21
|
+
color: ${colors.light(colors.selected)} !important;
|
|
22
|
+
border-color: ${colors.light(colors.selected)};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
&:focus-visible, & a:focus-visible {
|
|
26
|
-
outline: 2px solid ${colors.light(colors.
|
|
26
|
+
outline: 2px solid ${colors.light(colors.selected)};
|
|
27
27
|
outline-offset: -2px;
|
|
28
28
|
}
|
|
29
29
|
}
|
package/index.d.ts
CHANGED
package/package.json
CHANGED
package/theme/.test.js
CHANGED
|
@@ -2,11 +2,14 @@ import { TinyColor } from '@ctrl/tinycolor';
|
|
|
2
2
|
import theme from '.';
|
|
3
3
|
|
|
4
4
|
const ORIGINAL_BLUE = 'hsl(209, 55%, 45%)';
|
|
5
|
+
const ORIGINAL_YELLOW = 'hsl(44, 55%, 45%)';
|
|
5
6
|
const ORIGINAL_GRAY = 'hsl(0, 0%, 45%)';
|
|
6
7
|
|
|
7
8
|
afterEach(() => {
|
|
8
9
|
theme.colors.blue = new TinyColor(ORIGINAL_BLUE);
|
|
10
|
+
theme.colors.yellow = new TinyColor(ORIGINAL_YELLOW);
|
|
9
11
|
theme.colors.gray = new TinyColor(ORIGINAL_GRAY);
|
|
12
|
+
theme.colors.selected = undefined;
|
|
10
13
|
});
|
|
11
14
|
|
|
12
15
|
describe('theme colors', () => {
|
|
@@ -21,6 +24,19 @@ describe('theme colors', () => {
|
|
|
21
24
|
theme.colors.gray = new TinyColor('hsl(200, 30%, 45%)');
|
|
22
25
|
expect(theme.colors.white.toString()).not.toBe(before);
|
|
23
26
|
});
|
|
27
|
+
|
|
28
|
+
test('selected follows yellow — updates when yellow is reassigned', () => {
|
|
29
|
+
const custom = new TinyColor('hsl(120, 55%, 45%)');
|
|
30
|
+
theme.colors.yellow = custom;
|
|
31
|
+
expect(theme.colors.selected.toString()).toBe(custom.toString());
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('selected can be assigned independently of yellow', () => {
|
|
35
|
+
const custom = new TinyColor('hsl(280, 55%, 45%)');
|
|
36
|
+
theme.colors.selected = custom;
|
|
37
|
+
expect(theme.colors.selected.toString()).toBe(custom.toString());
|
|
38
|
+
expect(theme.colors.yellow.toString()).toBe(new TinyColor(ORIGINAL_YELLOW).toString());
|
|
39
|
+
});
|
|
24
40
|
});
|
|
25
41
|
|
|
26
42
|
describe('theme lazy strings', () => {
|
|
@@ -32,19 +48,19 @@ describe('theme lazy strings', () => {
|
|
|
32
48
|
|
|
33
49
|
test('theme.table reflects a color mutation', () => {
|
|
34
50
|
const before = theme.table;
|
|
35
|
-
theme.colors.
|
|
51
|
+
theme.colors.yellow = new TinyColor('hsl(120, 55%, 45%)');
|
|
36
52
|
expect(theme.table).not.toBe(before);
|
|
37
53
|
});
|
|
38
54
|
|
|
39
55
|
test('theme.scrollbar reflects a color mutation', () => {
|
|
40
56
|
const custom = new TinyColor('hsl(120, 55%, 45%)');
|
|
41
|
-
theme.colors.
|
|
57
|
+
theme.colors.yellow = custom;
|
|
42
58
|
expect(theme.scrollbar).toContain(custom.toString());
|
|
43
59
|
});
|
|
44
60
|
|
|
45
61
|
test('theme.input reflects a color mutation', () => {
|
|
46
62
|
const custom = new TinyColor('hsl(120, 55%, 45%)');
|
|
47
|
-
theme.colors.
|
|
63
|
+
theme.colors.yellow = custom;
|
|
48
64
|
expect(theme.input).toContain(custom.toString());
|
|
49
65
|
});
|
|
50
66
|
|
package/theme/README.md
CHANGED
|
@@ -284,6 +284,12 @@ colors.black; // Pure black - text on light backgrounds
|
|
|
284
284
|
colors.superWhite; // #fefefe - slightly warmer white
|
|
285
285
|
colors.vantablack; // #0a0a0a - rich black alternative
|
|
286
286
|
|
|
287
|
+
// Semantic accent - the "focused/selected/active" color (focus outlines,
|
|
288
|
+
// ::selection, hover highlights, checked controls, scrollbar thumbs).
|
|
289
|
+
// Follows colors.yellow unless assigned its own color:
|
|
290
|
+
colors.selected;
|
|
291
|
+
theme.colors.selected = new TinyColor('#3d7aed'); // decouple from yellow
|
|
292
|
+
|
|
287
293
|
// Accessibility functions
|
|
288
294
|
colors.mostReadable(baseColor, [colors.white, colors.black]);
|
|
289
295
|
// Returns the highest contrast color for optimal readability
|
|
@@ -566,6 +572,7 @@ interface ColorSystem {
|
|
|
566
572
|
transparent: TinyColor;
|
|
567
573
|
white: TinyColor;
|
|
568
574
|
black: TinyColor;
|
|
575
|
+
selected: TinyColor;
|
|
569
576
|
superWhite: TinyColor;
|
|
570
577
|
vantablack: TinyColor;
|
|
571
578
|
|
package/theme/button.js
CHANGED
|
@@ -6,7 +6,7 @@ export default ({ colors, fonts }) => `
|
|
|
6
6
|
text-decoration: none;
|
|
7
7
|
color: ${colors.white};
|
|
8
8
|
background-color: ${colors.blue};
|
|
9
|
-
outline-color: ${colors.lighter(colors.
|
|
9
|
+
outline-color: ${colors.lighter(colors.selected)};
|
|
10
10
|
text-align: center;
|
|
11
11
|
position: relative;
|
|
12
12
|
white-space: nowrap;
|
package/theme/colors.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { TinyColor, random, readability, isReadable, mostReadable } from '@ctrl/tinycolor';
|
|
2
2
|
|
|
3
|
+
let selectedOverride;
|
|
4
|
+
|
|
3
5
|
const colors = {
|
|
4
6
|
random,
|
|
5
7
|
readability,
|
|
6
8
|
isReadable,
|
|
7
9
|
mostReadable,
|
|
8
10
|
|
|
9
|
-
// Base colors — plain writable properties so theme.colors.X = new TinyColor(...) works
|
|
10
11
|
orange: new TinyColor('hsl(29, 55%, 45%)'),
|
|
11
12
|
gray: new TinyColor('hsl(0, 0%, 45%)'),
|
|
12
13
|
yellow: new TinyColor('hsl(44, 55%, 45%)'),
|
|
@@ -20,12 +21,18 @@ const colors = {
|
|
|
20
21
|
superWhite: new TinyColor('hsl(0, 100%, 100%)'),
|
|
21
22
|
vantablack: new TinyColor('hsl(0, 0%, 0%)'),
|
|
22
23
|
|
|
23
|
-
// Derived — getters so they recompute when gray is reassigned
|
|
24
24
|
get white() {
|
|
25
25
|
return colors.whiteish();
|
|
26
26
|
},
|
|
27
27
|
get black() {
|
|
28
|
-
return colors.
|
|
28
|
+
return colors.gray.darken(45);
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
get selected() {
|
|
32
|
+
return selectedOverride ?? colors.yellow;
|
|
33
|
+
},
|
|
34
|
+
set selected(color) {
|
|
35
|
+
selectedOverride = color;
|
|
29
36
|
},
|
|
30
37
|
|
|
31
38
|
whiteish: (color = colors.gray) => color.lighten(45),
|
package/theme/input.js
CHANGED
|
@@ -31,7 +31,7 @@ export const checkbox = ({ colors }) => `
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
&:checked:before {
|
|
34
|
-
box-shadow: inset 1em 1em ${colors.
|
|
34
|
+
box-shadow: inset 1em 1em ${colors.selected};
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
&:focus {
|
|
@@ -47,9 +47,9 @@ export default ({ colors }) => `
|
|
|
47
47
|
box-sizing: border-box;
|
|
48
48
|
width: 100%;
|
|
49
49
|
color: ${colors.light(colors.red)};
|
|
50
|
-
accent-color: ${colors.
|
|
50
|
+
accent-color: ${colors.selected};
|
|
51
51
|
background-color: ${colors.black};
|
|
52
|
-
outline-color: ${colors.lighter(colors.
|
|
52
|
+
outline-color: ${colors.lighter(colors.selected)};
|
|
53
53
|
padding: 2px 4px;
|
|
54
54
|
|
|
55
55
|
&:disabled {
|
package/theme/page.js
CHANGED
|
@@ -9,6 +9,7 @@ import _table from './table';
|
|
|
9
9
|
export default theme => `
|
|
10
10
|
html {
|
|
11
11
|
height: 100%;
|
|
12
|
+
color-scheme: ${colors.black.isDark() ? 'dark' : 'light'};
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
body {
|
|
@@ -25,6 +26,11 @@ export default theme => `
|
|
|
25
26
|
margin: 0;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
::selection {
|
|
30
|
+
background: ${colors.lighter(colors.selected)};
|
|
31
|
+
color: ${colors.black};
|
|
32
|
+
}
|
|
33
|
+
|
|
28
34
|
* {
|
|
29
35
|
touch-action: manipulation;
|
|
30
36
|
-webkit-text-size-adjust: none;
|
package/theme/scrollbar.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export default ({ colors }) => `
|
|
2
|
+
html {
|
|
3
|
+
scrollbar-color: ${colors.alpha(colors.selected, 0.6)} ${colors.white.setAlpha(0.06)};
|
|
4
|
+
}
|
|
5
|
+
|
|
2
6
|
::-webkit-scrollbar {
|
|
3
7
|
width: 24px;
|
|
4
8
|
height: 24px;
|
|
@@ -10,15 +14,15 @@ export default ({ colors }) => `
|
|
|
10
14
|
}
|
|
11
15
|
::-webkit-scrollbar-thumb {
|
|
12
16
|
background: ${colors.white.setAlpha(0.06)};
|
|
13
|
-
-webkit-box-shadow: inset 0 0 2px 1px ${colors.
|
|
17
|
+
-webkit-box-shadow: inset 0 0 2px 1px ${colors.selected};
|
|
14
18
|
border-radius: 12px;
|
|
15
19
|
}
|
|
16
20
|
::-webkit-scrollbar-thumb:hover {
|
|
17
21
|
background: ${colors.white.setAlpha(0.09)};
|
|
18
|
-
-webkit-box-shadow: inset 0 0 3px 1px ${colors.
|
|
22
|
+
-webkit-box-shadow: inset 0 0 3px 1px ${colors.selected};
|
|
19
23
|
}
|
|
20
24
|
::-webkit-scrollbar-thumb:active {
|
|
21
25
|
background: ${colors.white.setAlpha(0.12)};
|
|
22
|
-
-webkit-box-shadow: inset 0 0 4px 2px ${colors.
|
|
26
|
+
-webkit-box-shadow: inset 0 0 4px 2px ${colors.selected};
|
|
23
27
|
}
|
|
24
28
|
`;
|
package/theme/table.js
CHANGED
|
@@ -42,12 +42,12 @@ export default ({ colors }) => `
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
tr:hover {
|
|
45
|
-
background-color: ${colors.blackish(colors.
|
|
45
|
+
background-color: ${colors.blackish(colors.selected)};
|
|
46
46
|
color: ${colors.lighter(colors.gray)};
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
td:hover, th:hover {
|
|
50
|
-
background-color: ${colors.
|
|
50
|
+
background-color: ${colors.selected.darken(32)};
|
|
51
51
|
color: ${colors.white};
|
|
52
52
|
}
|
|
53
53
|
`;
|
package/Component/README.md
DELETED
|
@@ -1,455 +0,0 @@
|
|
|
1
|
-
# Component
|
|
2
|
-
|
|
3
|
-
Reactive component class extending Elem with Oxject-backed reactive options, lifecycle management, and automatic cleanup.
|
|
4
|
-
|
|
5
|
-
## Key Features
|
|
6
|
-
|
|
7
|
-
- **Reactive options system** - Property changes backed by Oxject emit events and update DOM automatically
|
|
8
|
-
- **Automatic memory management** - Element-level listeners persist through DOM moves, cleaned up on destroy. External resources (timers, subscriptions) cleaned up on disconnect
|
|
9
|
-
- **Flexible render timing** - Immediate, onload, or animationFrame rendering modes
|
|
10
|
-
- **Enhanced event handling** - Input events include `.value` property, DOM connection detection
|
|
11
|
-
- **Integrated styling** - Inline objects or scoped CSS with theme system integration
|
|
12
|
-
- **Lifecycle hooks** - Built-in hover and press handlers with automatic cleanup
|
|
13
|
-
|
|
14
|
-
```js
|
|
15
|
-
import { Component } from '@vanilla-bean/components';
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Basic Usage
|
|
19
|
-
|
|
20
|
-
### Simple Components
|
|
21
|
-
|
|
22
|
-
Every Component extends EventTarget and wraps an HTMLElement with reactive options:
|
|
23
|
-
|
|
24
|
-
```js
|
|
25
|
-
import { Component } from '@vanilla-bean/components';
|
|
26
|
-
|
|
27
|
-
const button = new Component({
|
|
28
|
-
tag: 'button',
|
|
29
|
-
textContent: 'Click me',
|
|
30
|
-
className: 'primary-btn',
|
|
31
|
-
onPointerPress: () => alert('Hello!'),
|
|
32
|
-
appendTo: document.body,
|
|
33
|
-
});
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
### Reactive Property Updates
|
|
37
|
-
|
|
38
|
-
Options are backed by Oxject. Property changes trigger DOM updates automatically:
|
|
39
|
-
|
|
40
|
-
```js
|
|
41
|
-
const input = new Component({
|
|
42
|
-
tag: 'input',
|
|
43
|
-
value: 'Initial text',
|
|
44
|
-
onChange: ({ value }) => {
|
|
45
|
-
input.options.value = value; // Updates DOM reactively
|
|
46
|
-
},
|
|
47
|
-
appendTo: document.body,
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// Display that updates automatically
|
|
51
|
-
new Component({
|
|
52
|
-
tag: 'p',
|
|
53
|
-
textContent: input.options.subscriber('value', value => `Current: ${value}`),
|
|
54
|
-
appendTo: document.body,
|
|
55
|
-
});
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Extended Component Classes
|
|
59
|
-
|
|
60
|
-
Create reusable component classes with custom behavior:
|
|
61
|
-
|
|
62
|
-
```js
|
|
63
|
-
class Button extends Component {
|
|
64
|
-
constructor(options = {}, ...children) {
|
|
65
|
-
super(
|
|
66
|
-
{
|
|
67
|
-
tag: 'button',
|
|
68
|
-
...options,
|
|
69
|
-
styles: ({ button }) => `
|
|
70
|
-
${button}
|
|
71
|
-
${options.styles?.({ button }) || ''}
|
|
72
|
-
`,
|
|
73
|
-
},
|
|
74
|
-
...children,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
_setOption(key, value) {
|
|
79
|
-
if (key === 'mode') {
|
|
80
|
-
this.removeClass(/\bmode-\S+/g).addClass(`mode-${value}`);
|
|
81
|
-
} else {
|
|
82
|
-
super._setOption(key, value);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### With Styled Components
|
|
89
|
-
|
|
90
|
-
Components integrate seamlessly with the styled system:
|
|
91
|
-
|
|
92
|
-
```js
|
|
93
|
-
import { styled } from '@vanilla-bean/components';
|
|
94
|
-
|
|
95
|
-
const StyledComponent = styled(
|
|
96
|
-
Component,
|
|
97
|
-
({ colors }) => `
|
|
98
|
-
background: ${colors.blue};
|
|
99
|
-
color: ${colors.white};
|
|
100
|
-
padding: 16px;
|
|
101
|
-
border-radius: 8px;
|
|
102
|
-
`,
|
|
103
|
-
);
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## Reactive Options System
|
|
107
|
-
|
|
108
|
-
Component options are stored in an Oxject instance (not a plain object like Elem), enabling automatic reactivity:
|
|
109
|
-
|
|
110
|
-
```js
|
|
111
|
-
const button = new Component({ textContent: 'Click me' });
|
|
112
|
-
|
|
113
|
-
// Property changes trigger DOM updates
|
|
114
|
-
button.options.textContent = 'Updated'; // Updates DOM reactively
|
|
115
|
-
button.options.addClass = 'active'; // Adds CSS class reactively
|
|
116
|
-
|
|
117
|
-
// Full Oxject features available
|
|
118
|
-
const subscription = button.options.subscribe({
|
|
119
|
-
key: 'textContent',
|
|
120
|
-
callback: value => console.log('Text changed:', value),
|
|
121
|
-
});
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Option Processing Pipeline
|
|
125
|
-
|
|
126
|
-
Options are routed through `_setOption` based on key and value type:
|
|
127
|
-
|
|
128
|
-
| Condition | Routing | Example |
|
|
129
|
-
| ------------------------ | -------------------------- | ------------------------- |
|
|
130
|
-
| Key starts with 'on' | Event handler registration | `onPointerPress: handler` |
|
|
131
|
-
| Key in `knownAttributes` | `elem.setAttribute()` | `id: 'my-id'` |
|
|
132
|
-
| Component has method | Method call with value | `addClass: 'active'` |
|
|
133
|
-
| HTMLElement has property | Direct elem property | `textContent: 'hello'` |
|
|
134
|
-
| Value is function | Component property | `customMethod: fn` |
|
|
135
|
-
| Default | Elem property assignment | `customProp: value` |
|
|
136
|
-
|
|
137
|
-
## Event Handling
|
|
138
|
-
|
|
139
|
-
### Automatic Event Registration
|
|
140
|
-
|
|
141
|
-
Event handlers are registered automatically when option keys start with 'on':
|
|
142
|
-
|
|
143
|
-
```js
|
|
144
|
-
new Component({
|
|
145
|
-
onPointerPress: event => console.log('pressed'),
|
|
146
|
-
onChange: ({ value }) => console.log('value:', value), // Input events include .value
|
|
147
|
-
onConnected: () => console.log('added to DOM'),
|
|
148
|
-
onDisconnected: () => console.log('removed from DOM'),
|
|
149
|
-
});
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
> **Note:** `click` is not in the supported event set. Use `onPointerPress` for press interactions.
|
|
153
|
-
|
|
154
|
-
### Enhanced Input Events
|
|
155
|
-
|
|
156
|
-
Input-related events (`keydown`, `keyup`, `change`, `blur`, `input`, `search`) receive enhanced event objects:
|
|
157
|
-
|
|
158
|
-
```js
|
|
159
|
-
new Component({
|
|
160
|
-
tag: 'input',
|
|
161
|
-
type: 'text',
|
|
162
|
-
onChange: ({ value, event }) => {
|
|
163
|
-
console.log('New value:', value); // Extracted from input
|
|
164
|
-
console.log('Original event:', event);
|
|
165
|
-
},
|
|
166
|
-
});
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
### Connection Events
|
|
170
|
-
|
|
171
|
-
DOM connection/disconnection detected via MutationObserver:
|
|
172
|
-
|
|
173
|
-
```js
|
|
174
|
-
new Component({
|
|
175
|
-
onConnected: () => console.log('Component added to DOM'),
|
|
176
|
-
onDisconnected: () => {
|
|
177
|
-
console.log('Component removed - cleanup triggered');
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### Specialized Event Handlers
|
|
183
|
-
|
|
184
|
-
#### Hover with Movement Tracking
|
|
185
|
-
|
|
186
|
-
```js
|
|
187
|
-
component.onHover(event => {
|
|
188
|
-
console.log('Mouse position:', event.clientX, event.clientY);
|
|
189
|
-
});
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
#### Press Detection
|
|
193
|
-
|
|
194
|
-
Fires on `pointerdown` for immediate, reliable response in all contexts including scroll containers and modal dialogs:
|
|
195
|
-
|
|
196
|
-
```js
|
|
197
|
-
component.onPointerPress(event => {
|
|
198
|
-
console.log('Pressed');
|
|
199
|
-
});
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
## Styling
|
|
203
|
-
|
|
204
|
-
### Inline Style Objects
|
|
205
|
-
|
|
206
|
-
Apply styles as JavaScript objects:
|
|
207
|
-
|
|
208
|
-
```js
|
|
209
|
-
component.styles({
|
|
210
|
-
color: 'red',
|
|
211
|
-
padding: '10px',
|
|
212
|
-
backgroundColor: '#f0f0f0',
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
// Or as option
|
|
216
|
-
new Component({
|
|
217
|
-
styles: { color: 'red', padding: '10px' },
|
|
218
|
-
});
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### Scoped CSS with Theme Integration
|
|
222
|
-
|
|
223
|
-
Use theme functions for scoped, processed CSS:
|
|
224
|
-
|
|
225
|
-
```js
|
|
226
|
-
// Theme function with automatic scoping
|
|
227
|
-
component.styles(
|
|
228
|
-
({ colors, fonts }) => `
|
|
229
|
-
background: ${colors.blue};
|
|
230
|
-
${fonts.kodeMono}
|
|
231
|
-
padding: 16px;
|
|
232
|
-
border-radius: 8px;
|
|
233
|
-
|
|
234
|
-
&:hover {
|
|
235
|
-
background: ${colors.darker(colors.blue)};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
@media (max-width: 768px) {
|
|
239
|
-
padding: 8px;
|
|
240
|
-
}
|
|
241
|
-
`,
|
|
242
|
-
);
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
CSS is scope-wrapped and injected as a scoped <style> tag. No preprocessing.
|
|
246
|
-
|
|
247
|
-
## Lifecycle Management
|
|
248
|
-
|
|
249
|
-
### Render Lifecycle
|
|
250
|
-
|
|
251
|
-
`render()` orchestrates a fixed sequence every time it is called:
|
|
252
|
-
|
|
253
|
-
```
|
|
254
|
-
render()
|
|
255
|
-
├─ if (this.rendered) ← re-render only: clears children, runs descendant cleanup
|
|
256
|
-
│ empty()
|
|
257
|
-
│ this.rendered = false
|
|
258
|
-
├─ build() ← subclass structural hook, runs before options are applied
|
|
259
|
-
├─ _processOptions() ← routes every option through _setOption()
|
|
260
|
-
│ priority keys first (onConnected, textContent, content, appendTo, prependTo, value)
|
|
261
|
-
│ then all remaining keys
|
|
262
|
-
└─ this.rendered = true
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
**`build()` is the subclass structural hook.** Override `build()`, never `render()`, to create child elements and internal structure. Because `build()` runs before `_processOptions()`, all structure exists by the time `_setOption` receives values.
|
|
266
|
-
|
|
267
|
-
The preferred way to handle specific options is `static handlers` — a per-class dispatch map that composes across the constructor chain without requiring `super._setOption`:
|
|
268
|
-
|
|
269
|
-
```js
|
|
270
|
-
class Card extends Component {
|
|
271
|
-
build() {
|
|
272
|
-
this.header = new Component({ tag: 'header', appendTo: this });
|
|
273
|
-
this.body = new Component({ tag: 'section', appendTo: this });
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
static handlers = {
|
|
277
|
-
title(value) {
|
|
278
|
-
this.header.options.textContent = value;
|
|
279
|
-
},
|
|
280
|
-
variant(value) {
|
|
281
|
-
this.removeClass(/variant-\S+/).addClass(`variant-${value}`);
|
|
282
|
-
},
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
Handlers that don't call `next(value)` fully own their key. Unhandled keys fall through to standard routing automatically — no `super._setOption` required. Use `_setOption` override only for enum validation or cases that need to intercept before the routing chain.
|
|
288
|
-
|
|
289
|
-
**`empty()` only runs on re-render.** On the initial render it is skipped. On subsequent `render()` calls it removes all child elements and runs cleanup on all descendant components before the DOM is cleared.
|
|
290
|
-
|
|
291
|
-
**`async build()` is not supported.** `render()` does not await `build()`'s return value. Asynchronous initialization belongs in `onConnected`.
|
|
292
|
-
|
|
293
|
-
**Deep hierarchies must chain `super.build()` manually.** If both a parent class and its subclass define `build()`, the subclass must call `super.build()` explicitly. It is not called automatically.
|
|
294
|
-
|
|
295
|
-
### Rendering Process (summary)
|
|
296
|
-
|
|
297
|
-
```js
|
|
298
|
-
component.render(); // Re-render: empty → build → _processOptions → rendered = true
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
Rendering behavior:
|
|
302
|
-
|
|
303
|
-
1. **Content clearing** - `empty()` removes existing children on re-render only (skipped on first render)
|
|
304
|
-
2. **Structure creation** - `build()` creates sub-elements before options are applied
|
|
305
|
-
3. **Priority processing** - `_processOptions()` applies `priorityOptions` keys first
|
|
306
|
-
4. **Option routing** - Each option processed via `_setOption`
|
|
307
|
-
5. **Completion flag** - Sets `rendered = true`
|
|
308
|
-
|
|
309
|
-
### Automatic Cleanup System
|
|
310
|
-
|
|
311
|
-
Components automatically clean up resources when removed from DOM:
|
|
312
|
-
|
|
313
|
-
```js
|
|
314
|
-
// Manual cleanup registration
|
|
315
|
-
component.addCleanup('unique-id', () => {
|
|
316
|
-
console.log('Custom cleanup executed');
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
// Manual cleanup execution (automatic on disconnect)
|
|
320
|
-
component.processCleanup();
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
**Automatic cleanup includes:**
|
|
324
|
-
|
|
325
|
-
- Event listener removal
|
|
326
|
-
- Oxject subscription cleanup
|
|
327
|
-
- Style element removal from DOM
|
|
328
|
-
- Recursive child component cleanup
|
|
329
|
-
|
|
330
|
-
### Render Timing Control
|
|
331
|
-
|
|
332
|
-
Control when components render with `autoRender` option:
|
|
333
|
-
|
|
334
|
-
```js
|
|
335
|
-
// Immediate rendering (default)
|
|
336
|
-
new Component({ autoRender: true });
|
|
337
|
-
|
|
338
|
-
// Render on window load event
|
|
339
|
-
new Component({ autoRender: 'onload' });
|
|
340
|
-
|
|
341
|
-
// Render on next animation frame
|
|
342
|
-
new Component({ autoRender: 'animationFrame' });
|
|
343
|
-
|
|
344
|
-
// Manual rendering only
|
|
345
|
-
new Component({ autoRender: false });
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
## API Reference
|
|
349
|
-
|
|
350
|
-
### Constructor
|
|
351
|
-
|
|
352
|
-
```js
|
|
353
|
-
new Component(options?, ...children)
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
**Parameters:**
|
|
357
|
-
|
|
358
|
-
- `options` (Object) - Configuration options
|
|
359
|
-
- `children` (...Component|Elem) - Child elements appended to `append` option
|
|
360
|
-
|
|
361
|
-
### Configuration Options
|
|
362
|
-
|
|
363
|
-
#### Component-Specific Options
|
|
364
|
-
|
|
365
|
-
| Option | Type | Description |
|
|
366
|
-
| ------------------ | ------------------------------------- | ------------------------------------------- |
|
|
367
|
-
| `tag` | `string` | HTML tag name (default: 'div') |
|
|
368
|
-
| `autoRender` | `boolean\|'onload'\|'animationFrame'` | Render timing control |
|
|
369
|
-
| `registeredEvents` | `Set<string>` | Custom event types for on() method |
|
|
370
|
-
| `knownAttributes` | `Set<string>` | Attribute names for elem.setAttribute() |
|
|
371
|
-
| `priorityOptions` | `Set<string>` | Option keys processed first during render |
|
|
372
|
-
| `styles` | `string\|object\|Function` | CSS string, style object, or theme function |
|
|
373
|
-
| `uniqueId` | `string` | Override auto-generated unique ID |
|
|
374
|
-
|
|
375
|
-
#### Event Handler Options
|
|
376
|
-
|
|
377
|
-
| Option | Type | Description |
|
|
378
|
-
| ---------------- | ---------- | -------------------------------------------- |
|
|
379
|
-
| `onHover` | `Function` | Hover handler with move tracking |
|
|
380
|
-
| `onPointerPress` | `Function` | Fires on `pointerdown` |
|
|
381
|
-
| ~~`onClick`~~ | — | Not supported. Use `onPointerPress` instead. |
|
|
382
|
-
| `onChange` | `Function` | Change event handler (input .value included) |
|
|
383
|
-
| `onConnected` | `Function` | DOM connection detection |
|
|
384
|
-
| `onDisconnected` | `Function` | DOM disconnection detection |
|
|
385
|
-
|
|
386
|
-
All standard Elem options work as reactive Component options.
|
|
387
|
-
|
|
388
|
-
### Methods
|
|
389
|
-
|
|
390
|
-
#### Options-Compatible Methods
|
|
391
|
-
|
|
392
|
-
Can be called directly or used as reactive options:
|
|
393
|
-
|
|
394
|
-
```js
|
|
395
|
-
component.styles(styleFunction); // Direct call
|
|
396
|
-
component.onHover(callback); // Direct call
|
|
397
|
-
component.addClass('active'); // Direct call
|
|
398
|
-
component.append(child); // Direct call
|
|
399
|
-
|
|
400
|
-
// Or as reactive options:
|
|
401
|
-
new Component({
|
|
402
|
-
styles: styleFunction,
|
|
403
|
-
onHover: callback,
|
|
404
|
-
addClass: 'active',
|
|
405
|
-
append: [child],
|
|
406
|
-
});
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
#### Lifecycle Methods
|
|
410
|
-
|
|
411
|
-
```js
|
|
412
|
-
component.build(); // Subclass structural hook — override to create child elements
|
|
413
|
-
component.render(); // Re-render: empty → build → _processOptions → rendered = true
|
|
414
|
-
component.addCleanup(id, fn); // Register cleanup function (chains with existing for same id)
|
|
415
|
-
component.replaceCleanup(id, fn); // Replace cleanup, running the previous one immediately
|
|
416
|
-
component.replaceDestroyCleanup(id, fn); // Destroy-only cleanup: survives disconnect, runs on destroy()
|
|
417
|
-
component.processCleanup(); // Execute all cleanup functions
|
|
418
|
-
component.destroy(); // Disconnect observer, run all cleanup, remove from DOM
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
#### Event Methods
|
|
422
|
-
|
|
423
|
-
```js
|
|
424
|
-
component.on({ targetEvent, callback, id? }); // Event registration
|
|
425
|
-
component.emit(eventType, detail); // Event dispatch
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
#### Utility Methods
|
|
429
|
-
|
|
430
|
-
```js
|
|
431
|
-
component.ancestry(); // Prototype chain inspection
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
### Properties
|
|
435
|
-
|
|
436
|
-
```js
|
|
437
|
-
component.parent; // Parent Component/Elem instance
|
|
438
|
-
component.children; // Child Component/Elem instances
|
|
439
|
-
component.uniqueId; // Frozen unique identifier
|
|
440
|
-
component.options; // Reactive Oxject instance
|
|
441
|
-
component.elem; // Underlying HTMLElement
|
|
442
|
-
component.rendered; // Boolean render status flag
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
## Memory Management
|
|
446
|
-
|
|
447
|
-
Components provide comprehensive automatic memory management:
|
|
448
|
-
|
|
449
|
-
- **Event listeners** - Automatically removed on DOM disconnect
|
|
450
|
-
- **Oxject subscriptions** - Cleaned up when component destroyed
|
|
451
|
-
- **Style elements** - Removed from DOM head on cleanup
|
|
452
|
-
- **Child components** - Recursive cleanup propagation
|
|
453
|
-
- **Custom cleanup** - Manual cleanup function registration
|
|
454
|
-
|
|
455
|
-
No manual cleanup required for typical usage - components handle resource management automatically.
|
package/Elem/README.md
DELETED
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
# Elem
|
|
2
|
-
|
|
3
|
-
Enhanced DOM element wrapper providing fluent API methods while maintaining direct access to the underlying HTMLElement.
|
|
4
|
-
|
|
5
|
-
## Basic Usage
|
|
6
|
-
|
|
7
|
-
### Simple Element Creation
|
|
8
|
-
|
|
9
|
-
Create DOM elements with enhanced manipulation capabilities:
|
|
10
|
-
|
|
11
|
-
```js
|
|
12
|
-
import { Elem } from '@vanilla-bean/components';
|
|
13
|
-
|
|
14
|
-
const button = new Elem({
|
|
15
|
-
tag: 'button',
|
|
16
|
-
textContent: 'Click me',
|
|
17
|
-
className: 'btn btn-primary',
|
|
18
|
-
onclick: () => alert('Clicked!'),
|
|
19
|
-
appendTo: document.body,
|
|
20
|
-
});
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### Complex Nested Structures
|
|
24
|
-
|
|
25
|
-
Build complex DOM hierarchies with nested elements:
|
|
26
|
-
|
|
27
|
-
```js
|
|
28
|
-
const card = new Elem(
|
|
29
|
-
{
|
|
30
|
-
tag: 'div',
|
|
31
|
-
className: 'card',
|
|
32
|
-
style: { padding: '20px', margin: '10px' },
|
|
33
|
-
},
|
|
34
|
-
new Elem({ tag: 'h3', textContent: 'Card Title' }),
|
|
35
|
-
new Elem({ tag: 'p', textContent: 'Card content goes here.' }),
|
|
36
|
-
);
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Method Chaining
|
|
40
|
-
|
|
41
|
-
Chain methods for fluent DOM construction:
|
|
42
|
-
|
|
43
|
-
```js
|
|
44
|
-
const navigation = new Elem({ tag: 'nav' })
|
|
45
|
-
.addClass('menu', 'horizontal')
|
|
46
|
-
.setStyle({ display: 'flex', gap: '16px' })
|
|
47
|
-
.append(
|
|
48
|
-
new Elem({ tag: 'a', textContent: 'Home', href: '/' }),
|
|
49
|
-
new Elem({ tag: 'a', textContent: 'About', href: '/about' }),
|
|
50
|
-
new Elem({ tag: 'a', textContent: 'Contact', href: '/contact' }),
|
|
51
|
-
)
|
|
52
|
-
.appendTo(document.body);
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### Direct DOM Access
|
|
56
|
-
|
|
57
|
-
Access the full HTMLElement API when needed:
|
|
58
|
-
|
|
59
|
-
```js
|
|
60
|
-
const input = new Elem({ tag: 'input', type: 'text' });
|
|
61
|
-
|
|
62
|
-
// Enhanced methods
|
|
63
|
-
input.addClass('form-control').setStyle({ width: '100%' });
|
|
64
|
-
|
|
65
|
-
// Direct DOM access
|
|
66
|
-
input.elem.focus();
|
|
67
|
-
input.elem.select();
|
|
68
|
-
input.elem.scrollIntoView({ behavior: 'smooth' });
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Configuration Options
|
|
72
|
-
|
|
73
|
-
### Constructor Syntax
|
|
74
|
-
|
|
75
|
-
```js
|
|
76
|
-
new Elem(options?, ...children)
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
**Parameters:**
|
|
80
|
-
|
|
81
|
-
- `options` (Object) - Configuration options and HTML properties
|
|
82
|
-
- `children` (...Elem|HTMLElement|string) - Child elements or text content
|
|
83
|
-
|
|
84
|
-
### Core Options
|
|
85
|
-
|
|
86
|
-
| Option | Type | Description |
|
|
87
|
-
| ------------ | --------------------------- | ---------------------------------- |
|
|
88
|
-
| `tag` | `string` | HTML tag name (default: 'div') |
|
|
89
|
-
| `style` | `object` | CSS properties as key-value pairs |
|
|
90
|
-
| `attributes` | `object` | HTML attributes as key-value pairs |
|
|
91
|
-
| `content` | `string\|Elem\|HTMLElement` | Element content |
|
|
92
|
-
| `appendTo` | `Elem\|HTMLElement` | Parent element to append to |
|
|
93
|
-
| `prependTo` | `Elem\|HTMLElement` | Parent element to prepend to |
|
|
94
|
-
| `append` | `Array` | Child elements to append |
|
|
95
|
-
| `prepend` | `Array` | Child elements to prepend |
|
|
96
|
-
|
|
97
|
-
### HTML Properties
|
|
98
|
-
|
|
99
|
-
All standard HTMLElement properties work as options:
|
|
100
|
-
|
|
101
|
-
```js
|
|
102
|
-
new Elem({
|
|
103
|
-
// Content properties
|
|
104
|
-
textContent: 'Button text',
|
|
105
|
-
innerHTML: '<span>HTML content</span>',
|
|
106
|
-
|
|
107
|
-
// Form properties
|
|
108
|
-
value: 'input value',
|
|
109
|
-
checked: true,
|
|
110
|
-
disabled: false,
|
|
111
|
-
|
|
112
|
-
// Element properties
|
|
113
|
-
id: 'unique-id',
|
|
114
|
-
className: 'btn primary',
|
|
115
|
-
title: 'Tooltip text',
|
|
116
|
-
|
|
117
|
-
// Link properties
|
|
118
|
-
href: 'https://example.com',
|
|
119
|
-
target: '_blank',
|
|
120
|
-
|
|
121
|
-
// Image properties
|
|
122
|
-
src: 'image.jpg',
|
|
123
|
-
alt: 'Image description',
|
|
124
|
-
});
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Event Handler Properties
|
|
128
|
-
|
|
129
|
-
Event handlers can be assigned directly:
|
|
130
|
-
|
|
131
|
-
```js
|
|
132
|
-
new Elem({
|
|
133
|
-
tag: 'button',
|
|
134
|
-
onclick: event => console.log('clicked'),
|
|
135
|
-
onmouseover: event => console.log('hover'),
|
|
136
|
-
onchange: event => console.log('changed'),
|
|
137
|
-
onfocus: event => console.log('focused'),
|
|
138
|
-
});
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## DOM Manipulation
|
|
142
|
-
|
|
143
|
-
### Content Management
|
|
144
|
-
|
|
145
|
-
```js
|
|
146
|
-
// Set content (replaces existing)
|
|
147
|
-
elem.content('New text content');
|
|
148
|
-
elem.content(new Elem({ tag: 'span', textContent: 'HTML element' }));
|
|
149
|
-
|
|
150
|
-
// Clear all content
|
|
151
|
-
elem.empty();
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Child Element Management
|
|
155
|
-
|
|
156
|
-
```js
|
|
157
|
-
// Add children
|
|
158
|
-
elem.append(child1, child2, 'text content');
|
|
159
|
-
elem.prepend(child1, child2);
|
|
160
|
-
|
|
161
|
-
// Access children
|
|
162
|
-
const childElements = elem.children; // Array of Elem instances
|
|
163
|
-
const nativeChildren = elem.elem.children; // HTMLCollection
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### Hierarchy Management
|
|
167
|
-
|
|
168
|
-
```js
|
|
169
|
-
// Add to DOM
|
|
170
|
-
elem.appendTo(document.body);
|
|
171
|
-
elem.prependTo(document.querySelector('.container'));
|
|
172
|
-
|
|
173
|
-
// Navigate hierarchy
|
|
174
|
-
const parentElem = elem.parent; // Parent Elem instance (if exists)
|
|
175
|
-
const nativeParent = elem.parentElem; // Parent HTMLElement
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Style and Attribute Management
|
|
179
|
-
|
|
180
|
-
```js
|
|
181
|
-
// Set multiple styles
|
|
182
|
-
elem.setStyle({
|
|
183
|
-
color: 'red',
|
|
184
|
-
fontSize: '16px',
|
|
185
|
-
backgroundColor: '#f0f0f0',
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// Set multiple attributes
|
|
189
|
-
elem.setAttributes({
|
|
190
|
-
'data-id': '123',
|
|
191
|
-
'aria-label': 'Close button',
|
|
192
|
-
role: 'button',
|
|
193
|
-
});
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
## Class Management
|
|
197
|
-
|
|
198
|
-
### Enhanced Class Operations
|
|
199
|
-
|
|
200
|
-
Elem provides enhanced class manipulation with regular expression support:
|
|
201
|
-
|
|
202
|
-
```js
|
|
203
|
-
// Check for classes
|
|
204
|
-
elem.hasClass('active'); // Check single class
|
|
205
|
-
elem.hasClass('btn', 'primary'); // Check multiple classes
|
|
206
|
-
elem.hasClass(/^btn-/); // Check with regex pattern
|
|
207
|
-
|
|
208
|
-
// Add classes
|
|
209
|
-
elem.addClass('new-class');
|
|
210
|
-
elem.addClass('class1', 'class2', 'class3');
|
|
211
|
-
|
|
212
|
-
// Remove classes
|
|
213
|
-
elem.removeClass('old-class');
|
|
214
|
-
elem.removeClass(/^temp-/); // Remove all classes starting with 'temp-'
|
|
215
|
-
elem.removeClass(/\bmobile-\w+/g); // Remove classes matching pattern
|
|
216
|
-
|
|
217
|
-
// Toggle class based on a condition (add when true, remove when false)
|
|
218
|
-
elem.toggleClass('active', isActive);
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### Class Manipulation Examples
|
|
222
|
-
|
|
223
|
-
```js
|
|
224
|
-
const button = new Elem({ tag: 'button', className: 'btn btn-primary temp-123' });
|
|
225
|
-
|
|
226
|
-
// Remove all temporary classes
|
|
227
|
-
button.removeClass(/^temp-/);
|
|
228
|
-
|
|
229
|
-
// Add state classes
|
|
230
|
-
button.addClass('btn-large', 'btn-rounded');
|
|
231
|
-
|
|
232
|
-
// Conditional classes
|
|
233
|
-
if (isActive) {
|
|
234
|
-
button.addClass('active', 'selected');
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Check for button variants
|
|
238
|
-
if (button.hasClass(/^btn-(primary|secondary|danger)$/)) {
|
|
239
|
-
console.log('Has button variant class');
|
|
240
|
-
}
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
## Event Handling
|
|
244
|
-
|
|
245
|
-
### EventTarget Integration
|
|
246
|
-
|
|
247
|
-
Elem extends EventTarget, providing full event capabilities:
|
|
248
|
-
|
|
249
|
-
```js
|
|
250
|
-
const button = new Elem({ tag: 'button', textContent: 'Click me' });
|
|
251
|
-
|
|
252
|
-
// Option-based event handlers
|
|
253
|
-
new Elem({
|
|
254
|
-
tag: 'input',
|
|
255
|
-
onchange: event => console.log('Value changed:', event.target.value),
|
|
256
|
-
onfocus: event => event.target.select(),
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
// addEventListener method
|
|
260
|
-
button.addEventListener('click', event => {
|
|
261
|
-
console.log('Button clicked');
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// Custom events
|
|
265
|
-
button.addEventListener('customEvent', event => {
|
|
266
|
-
console.log('Custom event data:', event.detail);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
// Dispatch events
|
|
270
|
-
button.dispatchEvent(
|
|
271
|
-
new CustomEvent('customEvent', {
|
|
272
|
-
detail: { message: 'Hello' },
|
|
273
|
-
}),
|
|
274
|
-
);
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
### Event Handler Options vs Methods
|
|
278
|
-
|
|
279
|
-
```js
|
|
280
|
-
// Via constructor options (preferred for initial setup)
|
|
281
|
-
const elem = new Elem({
|
|
282
|
-
tag: 'button',
|
|
283
|
-
onclick: handleClick,
|
|
284
|
-
onmouseover: handleHover,
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// Via addEventListener (preferred for dynamic binding)
|
|
288
|
-
elem.addEventListener('click', handleClick);
|
|
289
|
-
elem.addEventListener('mouseover', handleHover);
|
|
290
|
-
|
|
291
|
-
// Via native element
|
|
292
|
-
elem.elem.addEventListener('click', handleClick);
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
## API Reference
|
|
296
|
-
|
|
297
|
-
### Constructor
|
|
298
|
-
|
|
299
|
-
```js
|
|
300
|
-
new Elem(options?, ...children)
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
### Core Methods
|
|
304
|
-
|
|
305
|
-
#### Content Methods
|
|
306
|
-
|
|
307
|
-
```js
|
|
308
|
-
elem.content(content); // Set element content
|
|
309
|
-
elem.empty(); // Remove all child elements
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
#### Child Management Methods
|
|
313
|
-
|
|
314
|
-
```js
|
|
315
|
-
elem.append(...children); // Append child elements
|
|
316
|
-
elem.prepend(...children); // Prepend child elements
|
|
317
|
-
elem.appendTo(parent); // Append to parent element
|
|
318
|
-
elem.prependTo(parent); // Prepend to parent element
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
#### Style and Attribute Methods
|
|
322
|
-
|
|
323
|
-
```js
|
|
324
|
-
elem.setStyle(styles); // Set CSS properties
|
|
325
|
-
elem.setAttributes(attributes); // Set HTML attributes
|
|
326
|
-
elem.setOptions(options); // Set multiple options at once
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
#### Class Methods
|
|
330
|
-
|
|
331
|
-
```js
|
|
332
|
-
elem.hasClass(...classes); // Check for classes (supports regex)
|
|
333
|
-
elem.addClass(...classes); // Add CSS classes
|
|
334
|
-
elem.removeClass(...classes); // Remove CSS classes (supports regex)
|
|
335
|
-
elem.toggleClass(className, condition); // Add when condition is true, remove when false
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
### Properties
|
|
339
|
-
|
|
340
|
-
```js
|
|
341
|
-
elem.elem; // Underlying HTMLElement
|
|
342
|
-
elem.parent; // Parent Elem instance (if created by Elem)
|
|
343
|
-
elem.parentElem; // Parent HTMLElement
|
|
344
|
-
elem.children; // Array of child Elem instances
|
|
345
|
-
elem.options; // Configuration options object
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### Utility Methods
|
|
349
|
-
|
|
350
|
-
```js
|
|
351
|
-
elem.toString(); // Returns '[object Elem]'
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
## Integration with Component
|
|
355
|
-
|
|
356
|
-
Elem serves as the foundation for the Component class:
|
|
357
|
-
|
|
358
|
-
```js
|
|
359
|
-
import { Component } from '@vanilla-bean/components';
|
|
360
|
-
|
|
361
|
-
// Component extends Elem with reactive options
|
|
362
|
-
const component = new Component({
|
|
363
|
-
tag: 'div',
|
|
364
|
-
textContent: 'I am reactive!',
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
// All Elem methods available
|
|
368
|
-
component.addClass('component-class');
|
|
369
|
-
component.setStyle({ padding: '16px' });
|
|
370
|
-
|
|
371
|
-
// Plus Component-specific features
|
|
372
|
-
component.options.textContent = 'Updated reactively!';
|
|
373
|
-
```
|
package/styled/README.md
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
# styled
|
|
2
|
-
|
|
3
|
-
Create Component subclasses with scoped CSS: each `styled()` call generates a unique class, processes the theme, and injects a `<style>` element into `<head>`.
|
|
4
|
-
|
|
5
|
-
## Basic Usage
|
|
6
|
-
|
|
7
|
-
### Template Literal Syntax
|
|
8
|
-
|
|
9
|
-
Create styled components using template literals with theme integration:
|
|
10
|
-
|
|
11
|
-
```js
|
|
12
|
-
const StyledIcon = styled.Icon`
|
|
13
|
-
background-color: ${({ colors }) => colors.black};
|
|
14
|
-
width: 24px;
|
|
15
|
-
height: 24px;
|
|
16
|
-
|
|
17
|
-
&:before {
|
|
18
|
-
${({ fonts }) => fonts.fontAwesomeSolid}
|
|
19
|
-
content: "\f015";
|
|
20
|
-
}
|
|
21
|
-
`;
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### Function Syntax with Configuration
|
|
25
|
-
|
|
26
|
-
Use function syntax when you need to pass component configuration options:
|
|
27
|
-
|
|
28
|
-
```js
|
|
29
|
-
const ConfiguredComponent = styled(
|
|
30
|
-
Component,
|
|
31
|
-
({ colors }) => `
|
|
32
|
-
background-color: ${colors.black};
|
|
33
|
-
color: ${colors.lighter(colors.blue)};
|
|
34
|
-
padding: 12px;
|
|
35
|
-
|
|
36
|
-
&:hover {
|
|
37
|
-
background-color: ${colors.dark(colors.blue)};
|
|
38
|
-
}
|
|
39
|
-
`,
|
|
40
|
-
{
|
|
41
|
-
tag: 'section',
|
|
42
|
-
role: 'banner',
|
|
43
|
-
textContent: 'Default Text',
|
|
44
|
-
},
|
|
45
|
-
);
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Named Component Shortcuts
|
|
49
|
-
|
|
50
|
-
All top-level components are available as shorthand methods:
|
|
51
|
-
|
|
52
|
-
```js
|
|
53
|
-
const StyledButton = styled.Button`
|
|
54
|
-
background-color: ${({ colors }) => colors.green};
|
|
55
|
-
border-radius: 8px;
|
|
56
|
-
`;
|
|
57
|
-
|
|
58
|
-
const StyledInput = styled.Input`
|
|
59
|
-
${({ fonts }) => fonts.kodeMono}
|
|
60
|
-
border: 2px solid ${({ colors }) => colors.blue};
|
|
61
|
-
`;
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
**Note**: Template literal syntax creates components with empty configuration. Use function syntax to pass component options.
|
|
65
|
-
|
|
66
|
-
## Theme Integration
|
|
67
|
-
|
|
68
|
-
Style functions receive the complete theme object containing colors, fonts, and component styles:
|
|
69
|
-
|
|
70
|
-
```js
|
|
71
|
-
const ThemedComponent = styled.Component`
|
|
72
|
-
${({ button }) => button} /* Apply base button styles */
|
|
73
|
-
${({ fonts }) => fonts.kodeMono}
|
|
74
|
-
background: ${({ colors }) => colors.darker(colors.blue)};
|
|
75
|
-
color: ${({ colors }) => colors.mostReadable(colors.blue, [colors.white, colors.black])};
|
|
76
|
-
`;
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Runtime Style Override
|
|
80
|
-
|
|
81
|
-
Override or extend styles when creating component instances:
|
|
82
|
-
|
|
83
|
-
```js
|
|
84
|
-
const instance = new StyledComponent({
|
|
85
|
-
styles: ({ colors }) => ({
|
|
86
|
-
backgroundColor: colors.red,
|
|
87
|
-
border: `2px solid ${colors.darker(colors.red)}`,
|
|
88
|
-
}),
|
|
89
|
-
});
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Object-based styles apply as inline styles. Function-based styles generate scoped CSS.
|
|
93
|
-
|
|
94
|
-
## Component Inheritance
|
|
95
|
-
|
|
96
|
-
### Extending Styled Components
|
|
97
|
-
|
|
98
|
-
Build component hierarchies by extending existing styled components:
|
|
99
|
-
|
|
100
|
-
```js
|
|
101
|
-
const BaseButton = styled.Button`
|
|
102
|
-
padding: 8px 16px;
|
|
103
|
-
border-radius: 4px;
|
|
104
|
-
`;
|
|
105
|
-
|
|
106
|
-
const PrimaryButton = styled(BaseButton)`
|
|
107
|
-
background: ${({ colors }) => colors.blue};
|
|
108
|
-
color: ${({ colors }) => colors.white};
|
|
109
|
-
`;
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Template Literals with Any Styled Component
|
|
113
|
-
|
|
114
|
-
Template literal syntax works with any styled component, including those created with function syntax:
|
|
115
|
-
|
|
116
|
-
```js
|
|
117
|
-
const BaseComponent = styled(Component, () => 'color: red;');
|
|
118
|
-
|
|
119
|
-
// Extend any styled component with template literals
|
|
120
|
-
const ExtendedComponent = styled(BaseComponent)`
|
|
121
|
-
background: ${({ colors }) => colors.blue};
|
|
122
|
-
padding: 16px;
|
|
123
|
-
`;
|
|
124
|
-
|
|
125
|
-
// Use in class definitions for custom methods
|
|
126
|
-
class MyComponent extends (styled(BaseComponent)`
|
|
127
|
-
font-weight: bold;
|
|
128
|
-
border-radius: 4px;
|
|
129
|
-
`) {
|
|
130
|
-
// Add custom methods here
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### Component Functionality Inheritance
|
|
135
|
-
|
|
136
|
-
Styled components inherit all functionality from their base component:
|
|
137
|
-
|
|
138
|
-
```js
|
|
139
|
-
const StyledInput = styled.Input`
|
|
140
|
-
border: 2px solid ${({ colors }) => colors.blue};
|
|
141
|
-
`;
|
|
142
|
-
|
|
143
|
-
const instance = new StyledInput({
|
|
144
|
-
value: 'initial value',
|
|
145
|
-
onChange: event => console.log(event.value),
|
|
146
|
-
placeholder: 'Enter text...',
|
|
147
|
-
});
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## CSS Processing
|
|
151
|
-
|
|
152
|
-
### Native CSS Nesting
|
|
153
|
-
|
|
154
|
-
Styles are injected as written, no transformation, no runtime compilation. VBC requires Chrome 112+, Firefox 117+, and Safari 16.5+, all of which support the `&` nesting syntax natively.
|
|
155
|
-
|
|
156
|
-
```js
|
|
157
|
-
const NestedComponent = styled.Component`
|
|
158
|
-
padding: 16px;
|
|
159
|
-
|
|
160
|
-
& .child {
|
|
161
|
-
margin: 8px;
|
|
162
|
-
|
|
163
|
-
&:hover {
|
|
164
|
-
background: ${({ colors }) => colors.blue};
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
@media (max-width: 768px) {
|
|
169
|
-
padding: 8px;
|
|
170
|
-
}
|
|
171
|
-
`;
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### Automatic Scoping
|
|
175
|
-
|
|
176
|
-
Each styled component receives a unique class identifier to prevent CSS conflicts:
|
|
177
|
-
|
|
178
|
-
```js
|
|
179
|
-
const StyledDiv = styled(Component, () => `color: red;`);
|
|
180
|
-
const instance = new StyledDiv();
|
|
181
|
-
|
|
182
|
-
// Generated CSS: .a1b2c3d4 { color: red; }
|
|
183
|
-
// Component class: "a1b2c3d4"
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### Conditional Styles
|
|
187
|
-
|
|
188
|
-
Apply conditional styles using CSS classes and selectors:
|
|
189
|
-
|
|
190
|
-
```js
|
|
191
|
-
const ConditionalComponent = styled.Component`
|
|
192
|
-
padding: 12px;
|
|
193
|
-
background: ${({ colors }) => colors.white};
|
|
194
|
-
|
|
195
|
-
&.active {
|
|
196
|
-
background: ${({ colors }) => colors.blue};
|
|
197
|
-
color: ${({ colors }) => colors.white};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
&.disabled {
|
|
201
|
-
opacity: 0.5;
|
|
202
|
-
pointer-events: none;
|
|
203
|
-
}
|
|
204
|
-
`;
|
|
205
|
-
|
|
206
|
-
const instance = new ConditionalComponent({
|
|
207
|
-
addClass: 'active',
|
|
208
|
-
textContent: 'Active Button',
|
|
209
|
-
});
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
## Performance
|
|
213
|
-
|
|
214
|
-
### Processing Pipeline
|
|
215
|
-
|
|
216
|
-
The styled system processes styles through this pipeline:
|
|
217
|
-
|
|
218
|
-
1. **Component Creation** - Generates unique class identifier via `classSafeNanoid()`
|
|
219
|
-
2. **Style Processing** - Converts template literals into theme functions
|
|
220
|
-
3. **Theme Application** - Injects complete theme object into style functions
|
|
221
|
-
4. **CSS Processing** - Processes styles through `shimCSS()` pipeline
|
|
222
|
-
5. **DOM Injection** - Injects final CSS via `appendStyles()`
|
|
223
|
-
|
|
224
|
-
### Load-Time Optimization
|
|
225
|
-
|
|
226
|
-
Style processing timing depends on document state:
|
|
227
|
-
|
|
228
|
-
| Document State | Behavior |
|
|
229
|
-
| ------------------- | ------------------------------------------ |
|
|
230
|
-
| Complete | Processes and injects immediately |
|
|
231
|
-
| Loading | Queues for batch processing on window load |
|
|
232
|
-
| Multiple Components | Batches together for efficiency |
|
|
233
|
-
|
|
234
|
-
```js
|
|
235
|
-
// Document loaded - processes immediately
|
|
236
|
-
const StyledComponent = styled(Component, () => 'color: red;');
|
|
237
|
-
|
|
238
|
-
// Document loading - queued for batch processing
|
|
239
|
-
const AnotherStyled = styled(Component, () => 'color: blue;');
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
## API Reference
|
|
243
|
-
|
|
244
|
-
### styled(BaseComponent, styles?, options?)
|
|
245
|
-
|
|
246
|
-
Creates a styled component class.
|
|
247
|
-
|
|
248
|
-
**Parameters:**
|
|
249
|
-
|
|
250
|
-
- `BaseComponent` (Function) - Component class to extend
|
|
251
|
-
- `styles` (Function|String) - Style function or CSS string
|
|
252
|
-
- `options` (Object) - Component configuration options
|
|
253
|
-
|
|
254
|
-
**Returns:** Extended component class with scoped styling
|
|
255
|
-
|
|
256
|
-
### configured(BaseComponent, options)
|
|
257
|
-
|
|
258
|
-
Creates a component with configuration but no styles:
|
|
259
|
-
|
|
260
|
-
```js
|
|
261
|
-
const ConfiguredComponent = configured(Component, {
|
|
262
|
-
tag: 'article',
|
|
263
|
-
role: 'main',
|
|
264
|
-
textContent: 'Default Content',
|
|
265
|
-
});
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
### Utility Functions
|
|
269
|
-
|
|
270
|
-
#### appendStyles(css, id?)
|
|
271
|
-
|
|
272
|
-
Inject CSS directly into the page:
|
|
273
|
-
|
|
274
|
-
```js
|
|
275
|
-
appendStyles(
|
|
276
|
-
`
|
|
277
|
-
.my-global-class {
|
|
278
|
-
font-weight: bold;
|
|
279
|
-
color: red;
|
|
280
|
-
}
|
|
281
|
-
`,
|
|
282
|
-
'my-global-styles',
|
|
283
|
-
);
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
#### themeStyles({ styles, scope })
|
|
287
|
-
|
|
288
|
-
Generate themed CSS with optional scoping:
|
|
289
|
-
|
|
290
|
-
```js
|
|
291
|
-
const themedCSS = themeStyles({
|
|
292
|
-
styles: ({ colors }) => `color: ${colors.white}; background: ${colors.black};`,
|
|
293
|
-
scope: '.my-component',
|
|
294
|
-
});
|
|
295
|
-
// Returns: ".my-component { color: hsl(0, 0%, 90%); background: hsl(0, 0%, 10%); }"
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
#### shimCSS(styleConfig)
|
|
299
|
-
|
|
300
|
-
Complete style processing pipeline:
|
|
301
|
-
|
|
302
|
-
```js
|
|
303
|
-
shimCSS({
|
|
304
|
-
styles: ({ colors }) => `
|
|
305
|
-
display: flex;
|
|
306
|
-
background: ${colors.blue};
|
|
307
|
-
& .item { padding: 8px; }
|
|
308
|
-
`,
|
|
309
|
-
scope: '.my-scoped-component',
|
|
310
|
-
});
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
## Development Features
|
|
314
|
-
|
|
315
|
-
### Debug Class Names
|
|
316
|
-
|
|
317
|
-
Development mode adds inheritance-based class names for easier debugging:
|
|
318
|
-
|
|
319
|
-
```js
|
|
320
|
-
// Development classes: "a1b2c3 MyCustomComponent Component Elem"
|
|
321
|
-
class MyCustomComponent extends Component {}
|
|
322
|
-
const StyledCustom = styled(MyCustomComponent, () => 'color: blue;');
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### Memory Management
|
|
326
|
-
|
|
327
|
-
Class-level styles injected by `styled()` persist for the page lifetime. They are scoped to a unique class and do not interfere with other components, but are not removed when instances disconnect.
|
|
328
|
-
|
|
329
|
-
Per-instance styles set via the `styles` option on a component instance are cleaned up when that component disconnects from the DOM.
|