@vanilla-bean/components 1.1.1 → 2.0.1

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.
Files changed (52) hide show
  1. package/Component/Component.js +222 -36
  2. package/Component/Component.test.js +425 -38
  3. package/Component/README.md +480 -0
  4. package/Elem/README.md +373 -0
  5. package/README.md +1 -1
  6. package/components/BottomSheet/BottomSheet.js +8 -16
  7. package/components/Button/Button.js +12 -18
  8. package/components/Calendar/Calendar.js +139 -59
  9. package/components/Calendar/CalendarEvent.js +9 -4
  10. package/components/Calendar/Toolbar.js +28 -20
  11. package/components/Calendar/index.js +1 -0
  12. package/components/Code/Code.js +26 -30
  13. package/components/ColorPicker/ColorPicker.js +57 -57
  14. package/components/Dialog/Dialog.js +78 -72
  15. package/components/Form/Form.js +12 -8
  16. package/components/Icon/Icon.js +19 -11
  17. package/components/Input/Input.js +85 -67
  18. package/components/Input/README.md +0 -2
  19. package/components/Keyboard/Key.js +7 -10
  20. package/components/Keyboard/Keyboard.js +38 -46
  21. package/components/Label/Label.js +52 -50
  22. package/components/Link/Link.js +14 -20
  23. package/components/List/List.js +28 -30
  24. package/components/Menu/Menu.js +19 -10
  25. package/components/Menu/Menu.lld.md +2 -2
  26. package/components/Notify/Notify.js +29 -19
  27. package/components/Popover/Popover.js +49 -44
  28. package/components/RadioButton/RadioButton.js +33 -29
  29. package/components/Router/Router.js +19 -21
  30. package/components/Select/Select.js +24 -30
  31. package/components/Table/Table.js +55 -36
  32. package/components/TagList/Tag.js +16 -14
  33. package/components/TagList/TagList.js +15 -8
  34. package/components/Tooltip/Tooltip.js +12 -25
  35. package/components/TooltipWrapper/TooltipWrapper.js +55 -49
  36. package/components/Whiteboard/Whiteboard.js +45 -43
  37. package/devTools/build.js +43 -0
  38. package/devTools/buildTypes.js +322 -0
  39. package/devTools/createComponent.js +155 -0
  40. package/devTools/extractJSDoc.js +395 -0
  41. package/devTools/processTemplate.js +500 -0
  42. package/devTools/updateComponentIndex.js +16 -0
  43. package/devTools/updateDemoViewIndex.js +90 -0
  44. package/eslint.config.cjs +6 -3
  45. package/index.d.ts +117 -59
  46. package/package.json +67 -22
  47. package/spellcheck.config.cjs +4 -0
  48. package/styled/README.md +329 -0
  49. package/theme/colors.js +15 -12
  50. package/theme/colors.test.js +28 -0
  51. package/utils/element.js +2 -2
  52. package/FontWithASyntaxHighlighter-Regular.woff2 +0 -0
@@ -0,0 +1,329 @@
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.
package/theme/colors.js CHANGED
@@ -8,18 +8,21 @@ const colors = {
8
8
  isReadable,
9
9
  mostReadable,
10
10
 
11
- orange: new TinyColor('hsl(29, 55%, 45%)'),
12
- gray: new TinyColor('hsl(0, 0%, 45%)'),
13
- yellow: new TinyColor('hsl(44, 55%, 45%)'),
14
- green: new TinyColor('hsl(74, 55%, 45%)'),
15
- teal: new TinyColor('hsl(164, 55%, 45%)'),
16
- blue: new TinyColor('hsl(209, 55%, 45%)'),
17
- purple: new TinyColor('hsl(254, 55%, 45%)'),
18
- pink: new TinyColor('hsl(314, 55%, 45%)'),
19
- red: new TinyColor('hsl(359, 55%, 45%)'),
20
- transparent: new TinyColor('rgba(255, 255, 255, 0)'),
21
- superWhite: new TinyColor('hsl(0, 100%, 100%)'),
22
- vantablack: new TinyColor('hsl(0, 0%, 0%)'),
11
+ // Shared instances for speed (cloning per access proved too slow) - frozen so an
12
+ // accidental mutation (e.g. setAlpha) throws instead of silently recoloring the app.
13
+ // Use colors.alpha(color, alpha) for transparency.
14
+ orange: Object.freeze(new TinyColor('hsl(29, 55%, 45%)')),
15
+ gray: Object.freeze(new TinyColor('hsl(0, 0%, 45%)')),
16
+ yellow: Object.freeze(new TinyColor('hsl(44, 55%, 45%)')),
17
+ green: Object.freeze(new TinyColor('hsl(74, 55%, 45%)')),
18
+ teal: Object.freeze(new TinyColor('hsl(164, 55%, 45%)')),
19
+ blue: Object.freeze(new TinyColor('hsl(209, 55%, 45%)')),
20
+ purple: Object.freeze(new TinyColor('hsl(254, 55%, 45%)')),
21
+ pink: Object.freeze(new TinyColor('hsl(314, 55%, 45%)')),
22
+ red: Object.freeze(new TinyColor('hsl(359, 55%, 45%)')),
23
+ transparent: Object.freeze(new TinyColor('rgba(255, 255, 255, 0)')),
24
+ superWhite: Object.freeze(new TinyColor('hsl(0, 100%, 100%)')),
25
+ vantablack: Object.freeze(new TinyColor('hsl(0, 0%, 0%)')),
23
26
 
24
27
  get white() {
25
28
  return colors.whiteish();
@@ -0,0 +1,28 @@
1
+ import colors from './colors';
2
+
3
+ describe('theme colors', () => {
4
+ test('named hues are frozen shared instances', () => {
5
+ expect(Object.isFrozen(colors.yellow)).toBe(true);
6
+ expect(Object.isFrozen(colors.selected)).toBe(true);
7
+ });
8
+
9
+ test('mutating a shared hue throws instead of recoloring the app', () => {
10
+ expect(() => colors.selected.setAlpha(0.5)).toThrow();
11
+ expect(colors.yellow.a).toBe(1);
12
+ });
13
+
14
+ test('alpha() derives transparency without touching the source', () => {
15
+ const translucent = colors.alpha(colors.blue, 0.4);
16
+
17
+ expect(translucent.a).toBe(0.4);
18
+ expect(colors.blue.a).toBe(1);
19
+ });
20
+
21
+ test('ramp helpers return fresh instances safe to modify', () => {
22
+ const light = colors.light(colors.teal);
23
+
24
+ expect(Object.isFrozen(light)).toBe(false);
25
+ light.setAlpha(0.5);
26
+ expect(colors.teal.a).toBe(1);
27
+ });
28
+ });
package/utils/element.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * @returns {number} Zero-based index position within parent element
6
6
  */
7
7
  export const getElementIndex = (element, index = 0) => {
8
- if (element.previousElementSibling) return getElementIndex(element.previousElementSibling, ++index);
8
+ if (element.previousElementSibling) return getElementIndex(element.previousElementSibling, index + 1);
9
9
 
10
10
  return index;
11
11
  };
@@ -44,7 +44,7 @@ export const getElementsContainingText = (text, options = {}) => {
44
44
  const xPath = `.//${xPathElement}[contains(${caseSensitive ? `text(),${esc(text)}` : `translate(.,${esc(text.toUpperCase())},${esc(text.toLowerCase())}),${esc(text.toLowerCase())}`})]`;
45
45
  const result = document.evaluate(xPath, scope, null, XPathResult.ANY_TYPE, null);
46
46
 
47
- let node = null;
47
+ let node;
48
48
  const nodes = [];
49
49
  while ((node = result.iterateNext())) {
50
50
  if (nodes.length > 0 && nodes.at(-1).contains(node)) nodes[nodes.length - 1] = node;