@vanilla-bean/components 1.1.0 → 1.1.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.
- package/Component/Component.js +5 -5
- package/Component/Component.scenarios.js +2 -2
- package/Component/Component.test.js +2 -2
- package/Component/observeElementConnection.js +1 -1
- package/README.md +1 -1
- package/components/BottomSheet/README.md +1 -1
- package/components/Button/Button.lld.md +1 -1
- package/components/Code/Code.lld.md +1 -1
- package/components/ColorPicker/ColorPicker.js +1 -1
- package/components/Dialog/Dialog.js +1 -1
- package/components/Dialog/Dialog.lld.md +1 -1
- package/components/Dialog/README.md +10 -10
- package/components/Form/Form.js +9 -9
- package/components/Form/Form.lld.md +2 -2
- package/components/Form/README.md +3 -3
- package/components/Input/README.md +7 -7
- package/components/Keyboard/Keyboard.lld.md +1 -1
- package/components/Notify/Notify.lld.md +2 -2
- package/components/Page/Page.lld.md +1 -1
- package/components/RadioButton/RadioButton.lld.md +1 -1
- package/components/Router/README.md +12 -12
- package/components/Select/README.md +5 -5
- package/components/Select/Select.js +1 -1
- package/components/Table/README.md +4 -4
- package/components/Table/Table.lld.md +1 -1
- package/components/TagList/TagList.lld.md +1 -1
- package/components/TooltipWrapper/TooltipWrapper.lld.md +2 -2
- package/components/Whiteboard/Whiteboard.lld.md +1 -1
- package/index.d.ts +3 -3
- package/package.json +1 -1
- package/theme/.test.js +2 -2
- package/theme/README.md +8 -8
- package/theme/colors.js +1 -1
- package/utils/browser.js +1 -1
package/Component/Component.js
CHANGED
|
@@ -139,7 +139,7 @@ class Component extends Elem {
|
|
|
139
139
|
if (this.constructor !== Component && this.constructor.prototype.hasOwnProperty('render')) {
|
|
140
140
|
// eslint-disable-next-line no-console
|
|
141
141
|
console.warn(
|
|
142
|
-
`[Component] ${this.constructor.name} overrides render(). Structure belongs in build()
|
|
142
|
+
`[Component] ${this.constructor.name} overrides render(). Structure belongs in build() - render() is the lifecycle orchestrator.`,
|
|
143
143
|
);
|
|
144
144
|
}
|
|
145
145
|
}
|
|
@@ -154,7 +154,7 @@ class Component extends Elem {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
* Subclass structural hook
|
|
157
|
+
* Subclass structural hook - override to create child elements and component structure.
|
|
158
158
|
* Called by render() before options are processed, so all structure exists
|
|
159
159
|
* before _setOption receives values.
|
|
160
160
|
*/
|
|
@@ -204,7 +204,7 @@ class Component extends Elem {
|
|
|
204
204
|
* Routes option changes through the static handlers chain, then standard routing.
|
|
205
205
|
*
|
|
206
206
|
* Walks the constructor chain collecting all handlers for the given key (deepest class
|
|
207
|
-
* first), then executes them in order. Each handler receives `next(value?)`
|
|
207
|
+
* first), then executes them in order. Each handler receives `next(value?)` - call it
|
|
208
208
|
* to continue to the next handler in the chain, or to standard routing when the chain
|
|
209
209
|
* is exhausted. Handlers that do not call `next` fully own the key.
|
|
210
210
|
* @param {string} key - Option property name being changed
|
|
@@ -235,7 +235,7 @@ class Component extends Elem {
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
/**
|
|
238
|
-
* Standard option routing pipeline
|
|
238
|
+
* Standard option routing pipeline - event handlers, special keys, attributes, methods, properties.
|
|
239
239
|
* Called by _setOption when no handler claims the key, or when a handler calls next() past
|
|
240
240
|
* the end of its chain.
|
|
241
241
|
* @param {string} key - Option property name
|
|
@@ -260,7 +260,7 @@ class Component extends Elem {
|
|
|
260
260
|
}
|
|
261
261
|
return;
|
|
262
262
|
}
|
|
263
|
-
// method route
|
|
263
|
+
// method route - e.g. onPointerPress, onHover
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
if (key === 'uniqueId') this.elem.id = typeof value === 'string' ? value : this.uniqueId;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Component from './Component.js';
|
|
2
2
|
|
|
3
3
|
export const __lld_api = `
|
|
4
|
-
destructiveRender() -> boolean | renders a component twice; returns true if child count matches (1)
|
|
4
|
+
destructiveRender() -> boolean | renders a component twice; returns true if child count matches (1) - second render cleared the first
|
|
5
5
|
optionReactionFires() -> number | assigns to option after render; returns how many times _setOption was called (1 = reactive)
|
|
6
6
|
buildBeforeOptions() -> boolean | verifies build() DOM structure exists when _setOption runs (true = ordering is correct)
|
|
7
7
|
priorityRunsFirst() -> boolean | textContent (priority option) runs before style (non-priority); returns true if correct order
|
|
@@ -84,5 +84,5 @@ export const replaceCleanupRunsOnce = () => {
|
|
|
84
84
|
}); // runs previous = 2, stores latest
|
|
85
85
|
const countBeforeCleanup = count;
|
|
86
86
|
comp.processCleanup(); // runs only the latest = 3
|
|
87
|
-
return count - countBeforeCleanup; // 1
|
|
87
|
+
return count - countBeforeCleanup; // 1 - only latest ran at cleanup time
|
|
88
88
|
};
|
|
@@ -621,7 +621,7 @@ describe('Component', () => {
|
|
|
621
621
|
});
|
|
622
622
|
|
|
623
623
|
describe('cleanup system', () => {
|
|
624
|
-
test('addCleanup chains
|
|
624
|
+
test('addCleanup chains - both functions run on processCleanup', () => {
|
|
625
625
|
const comp = new Component({ autoRender: false });
|
|
626
626
|
const calls = [];
|
|
627
627
|
|
|
@@ -695,7 +695,7 @@ describe('Component', () => {
|
|
|
695
695
|
expect(handler).toHaveBeenCalled();
|
|
696
696
|
});
|
|
697
697
|
|
|
698
|
-
test('on* method route
|
|
698
|
+
test('on* method route - onPointerPress calls the component method', () => {
|
|
699
699
|
const handler = mock();
|
|
700
700
|
component = new Component({ tag: 'button', onPointerPress: handler, appendTo: document.body });
|
|
701
701
|
|
|
@@ -33,7 +33,7 @@ function ensureObserver() {
|
|
|
33
33
|
* @param {Node} config.target - Target element to watch for add/remove
|
|
34
34
|
* @param {Function} config.onConnected - Called when target is added to the document
|
|
35
35
|
* @param {Function} config.onDisconnected - Called when target is removed from the document
|
|
36
|
-
* @returns {{ disconnect: Function }} Handle
|
|
36
|
+
* @returns {{ disconnect: Function }} Handle - call disconnect() to deregister
|
|
37
37
|
*/
|
|
38
38
|
export const observeElementConnection = ({ target, onConnected, onDisconnected }) => {
|
|
39
39
|
ensureObserver();
|
package/README.md
CHANGED
|
@@ -379,7 +379,7 @@ vanilla-bean-components/
|
|
|
379
379
|
├── Component/ # Core Component class
|
|
380
380
|
├── styled/ # Scoped styling system
|
|
381
381
|
├── theme/ # Design tokens and helpers
|
|
382
|
-
├── demo/ # Component explorer
|
|
382
|
+
├── demo/ # Component explorer - live option editor, API docs, example apps
|
|
383
383
|
└── docs/ # Additional documentation
|
|
384
384
|
```
|
|
385
385
|
|
|
@@ -20,7 +20,7 @@ sheet.show();
|
|
|
20
20
|
| Option | Type | Default | Description |
|
|
21
21
|
| ---------- | ---------- | --------------- | ----------------------------------------------------------------- |
|
|
22
22
|
| `appendTo` | `Element` | `document.body` | Where to mount the sheet in the DOM |
|
|
23
|
-
| `onClose` | `Function` |
|
|
23
|
+
| `onClose` | `Function` | - | Called when the sheet is dismissed - by drag or explicit `hide()` |
|
|
24
24
|
|
|
25
25
|
All standard `Component` options are supported.
|
|
26
26
|
|
|
@@ -15,7 +15,7 @@ Activatable element that unifies pointer and keyboard interaction under one hand
|
|
|
15
15
|
- a caller-provided `onKeyUp` runs alongside the built-in keyboard activation; registering both handlers does not suppress either
|
|
16
16
|
- does providing a custom onKeyUp still trigger onPointerPress on Space/Enter?
|
|
17
17
|
|
|
18
|
-
## Tooltip is automatic
|
|
18
|
+
## Tooltip is automatic - no setup beyond the tooltip option
|
|
19
19
|
|
|
20
20
|
- Button extends TooltipWrapper; a `tooltip` option produces tooltip behavior with no additional wiring
|
|
21
21
|
- does a Button with a tooltip option show a tooltip on hover?
|
|
@@ -15,7 +15,7 @@ Code display that automatically promotes from inline to block when the content s
|
|
|
15
15
|
- the `language` option adds a `language-{name}` class; highlighting libraries target this class by convention
|
|
16
16
|
- does the language option add an identifiable class to the code element?
|
|
17
17
|
|
|
18
|
-
## Copy confirms success visually
|
|
18
|
+
## Copy confirms success visually - clipboard writes can fail silently
|
|
19
19
|
|
|
20
20
|
- the copy button shows a notification on success so the user knows the copy worked rather than discovering it failed on paste
|
|
21
21
|
- does a successful copy show the user a confirmation?
|
|
@@ -283,7 +283,7 @@ class ColorPicker extends StyledInput {
|
|
|
283
283
|
|
|
284
284
|
const clamp = (val, min, max) => Math.min(max, Math.max(min, val));
|
|
285
285
|
|
|
286
|
-
// HSV: x = saturation, y = (1 - value)
|
|
286
|
+
// HSV: x = saturation, y = (1 - value) - matches the gradient exactly
|
|
287
287
|
const pickerX = clamp((this.sv ?? 0) * pickerW, 0, pickerW);
|
|
288
288
|
const pickerY = clamp((1 - (this.v ?? 1)) * pickerH, 0, pickerH);
|
|
289
289
|
this.pickerIndicator.elem.style.transform = `translate3d(${pickerX}px, ${pickerY}px, 0)`;
|
|
@@ -232,7 +232,7 @@ class Dialog extends styled(
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
/**
|
|
235
|
-
* Content container
|
|
235
|
+
* Content container - primary injection point for subclass and consumer content.
|
|
236
236
|
* @returns {Component} The dialog body component
|
|
237
237
|
*/
|
|
238
238
|
get body() {
|
|
@@ -14,7 +14,7 @@ Native `<dialog>` element wrapper. The decision to use the native element rather
|
|
|
14
14
|
- an unrecognized size or variant throws at the point of assignment, not later when layout breaks
|
|
15
15
|
- does an unrecognized size value throw?
|
|
16
16
|
|
|
17
|
-
## Header, body, and footer are stable references
|
|
17
|
+
## Header, body, and footer are stable references - options update them in place
|
|
18
18
|
|
|
19
19
|
- the structural elements are created once in `build()` and persist across option updates; assigning a new `header` value changes the header's content, not the dialog's structure
|
|
20
20
|
- does updating the header option change the header's content without replacing the dialog structure?
|
|
@@ -22,25 +22,25 @@ const dialog = new Dialog({
|
|
|
22
22
|
|
|
23
23
|
| Option | Type | Default | Description |
|
|
24
24
|
| --- | --- | --- | --- |
|
|
25
|
-
| `header` | `string` |
|
|
26
|
-
| `body` | `string\|Component\|Array` |
|
|
27
|
-
| `buttons` | `Array<string\|object>` |
|
|
28
|
-
| `footer` | `Array<Component>` |
|
|
29
|
-
| `onButtonPress` | `Function` |
|
|
25
|
+
| `header` | `string` | - | Text rendered in the `<h2>` header |
|
|
26
|
+
| `body` | `string\|Component\|Array` | - | Content for the scrollable body section. Reactive: updating `options.body` replaces content |
|
|
27
|
+
| `buttons` | `Array<string\|object>` | - | Shorthand footer buttons. Each entry is a string label or `{ textContent, variant?, onPointerPress?, ...buttonOptions }` |
|
|
28
|
+
| `footer` | `Array<Component>` | - | Fully custom footer components. Takes precedence over `buttons` |
|
|
29
|
+
| `onButtonPress` | `Function` | - | Called when any `buttons` entry is pressed. Receives `{ button, closeDialog, event }` |
|
|
30
30
|
| `openOnRender` | `number\|boolean` | `16` | Auto-open delay in ms after render. Set to `false` to disable auto-open |
|
|
31
31
|
| `modal` | `boolean` | `true` | Open as a modal (with backdrop) vs. non-modal |
|
|
32
32
|
| `size` | `string` | `'small'` | Dialog dimensions: `'small'` (420×210px), `'standard'` (840×420px), `'large'` (90vw×90vh) |
|
|
33
|
-
| `variant` | `string` |
|
|
34
|
-
| `closeDialog` | `Function` |
|
|
33
|
+
| `variant` | `string` | - | Color theme: `'info'`, `'success'`, `'warning'`, `'error'` |
|
|
34
|
+
| `closeDialog` | `Function` | - | Override the default close behavior used inside `onButtonPress` |
|
|
35
35
|
| `appendTo` | `Element` | `document.body` | Where to append the dialog in the DOM |
|
|
36
36
|
|
|
37
37
|
### `onButtonPress` callback signature
|
|
38
38
|
|
|
39
39
|
```js
|
|
40
40
|
onButtonPress: ({ button, closeDialog, event }) => { ... }
|
|
41
|
-
// button
|
|
42
|
-
// closeDialog
|
|
43
|
-
// event
|
|
41
|
+
// button - the original entry from the buttons array (string or object)
|
|
42
|
+
// closeDialog - function that closes the dialog (calls dialog.close() by default)
|
|
43
|
+
// event - the pointer event that triggered the press
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
## Methods
|
package/components/Form/Form.js
CHANGED
|
@@ -14,18 +14,18 @@ import { Label } from '../Label';
|
|
|
14
14
|
* **Field definition:**
|
|
15
15
|
* ```js
|
|
16
16
|
* {
|
|
17
|
-
* key: 'name', // required
|
|
18
|
-
* label: 'Full Name', // optional
|
|
19
|
-
* InputComponent: Select, // optional
|
|
20
|
-
* onChange: event => {}, // optional
|
|
21
|
-
* parse: value => value.trim(), // optional
|
|
22
|
-
* condition: data => data.type === 'x', // optional
|
|
23
|
-
* validate: value => value ? null : 'Required', // optional
|
|
17
|
+
* key: 'name', // required - data key
|
|
18
|
+
* label: 'Full Name', // optional - defaults to formatted key
|
|
19
|
+
* InputComponent: Select, // optional - defaults to Input
|
|
20
|
+
* onChange: event => {}, // optional - called after data update
|
|
21
|
+
* parse: value => value.trim(), // optional - transform before storing
|
|
22
|
+
* condition: data => data.type === 'x', // optional - hide field when false
|
|
23
|
+
* validate: value => value ? null : 'Required', // optional - field-level validator
|
|
24
24
|
* // ...any other InputComponent options
|
|
25
25
|
* }
|
|
26
26
|
* ```
|
|
27
27
|
*
|
|
28
|
-
* **Group definition**
|
|
28
|
+
* **Group definition** - renders inputs side-by-side in a layout container:
|
|
29
29
|
* ```js
|
|
30
30
|
* {
|
|
31
31
|
* type: 'group',
|
|
@@ -91,7 +91,7 @@ export default class Form extends Component {
|
|
|
91
91
|
if (!this._conditionals.length) return;
|
|
92
92
|
const evaluate = () => this._evaluateConditions();
|
|
93
93
|
this.options.data.addEventListener('set', evaluate);
|
|
94
|
-
// replaceCleanup
|
|
94
|
+
// replaceCleanup - safe to call multiple times (inputs handler re-wires on rebuild)
|
|
95
95
|
this.replaceCleanup('conditions', () => this.options.data.removeEventListener('set', evaluate));
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Declarative form from an array of input configurations. The key design decision is that the form owns data binding. Each input's changes update a shared reactive `data` context, and `hasErrors()` validates the whole form in one call.
|
|
6
6
|
|
|
7
|
-
## Label text falls back to the field key
|
|
7
|
+
## Label text falls back to the field key - no separate label required for obvious fields
|
|
8
8
|
|
|
9
9
|
- if an input config omits `label`, the key name becomes the label
|
|
10
10
|
- does an input with no label option display the key name as its label?
|
|
@@ -15,7 +15,7 @@ Declarative form from an array of input configurations. The key design decision
|
|
|
15
15
|
- does hasErrors return false when all inputs are valid?
|
|
16
16
|
- does hasErrors return true when an input's validation fails?
|
|
17
17
|
|
|
18
|
-
## Form data is live
|
|
18
|
+
## Form data is live - changes are visible before any submit action
|
|
19
19
|
|
|
20
20
|
- each input's value feeds the shared `data` context as it changes
|
|
21
21
|
- does typing in an input field update the form's data context before any submit?
|
|
@@ -20,7 +20,7 @@ const form = new Form({
|
|
|
20
20
|
|
|
21
21
|
| Option | Type | Default | Description |
|
|
22
22
|
| -------- | --------------- | ------- | ----------------------------------------------------------------------- |
|
|
23
|
-
| `inputs` | `Array<object>` |
|
|
23
|
+
| `inputs` | `Array<object>` | - | Input configuration array; see shape below |
|
|
24
24
|
| `data` | `object` | `{}` | Initial form data. Replaced with an `Oxject` instance on each `build()` |
|
|
25
25
|
|
|
26
26
|
### `inputs` entry shape
|
|
@@ -32,8 +32,8 @@ const form = new Form({
|
|
|
32
32
|
| `InputComponent` | `Component class` | `Input` | Component class to instantiate. Can be `Select`, `ColorPicker`, or any component that accepts `value` and `onChange` |
|
|
33
33
|
| `onChange` | `Function` | `() => {}` | Called after the data context is updated with the new value |
|
|
34
34
|
| `parse` | `Function(value, input)` | `v => v` | Transforms the raw event value before storing it in `options.data` |
|
|
35
|
-
| `validations` | `Array` |
|
|
36
|
-
| `...inputOptions` | `any` |
|
|
35
|
+
| `validations` | `Array` | - | Passed directly to the `InputComponent` (see Input validation format) |
|
|
36
|
+
| `...inputOptions` | `any` | - | Any additional options forwarded to the `InputComponent` constructor |
|
|
37
37
|
|
|
38
38
|
### `data` Oxject lifecycle
|
|
39
39
|
|
|
@@ -27,12 +27,12 @@ const input = new Input({
|
|
|
27
27
|
| `autocapitalize` | `string` | `'off'` | Native autocapitalize attribute |
|
|
28
28
|
| `autocorrect` | `string` | `'off'` | Native autocorrect attribute |
|
|
29
29
|
| `height` | `string\|number` | `'auto'` | Height for textarea. `'auto'` dynamically sizes to content; a number sets `em` units |
|
|
30
|
-
| `syntaxHighlighting` | `boolean` |
|
|
31
|
-
| `language` | `string` |
|
|
32
|
-
| `validations` | `Array` |
|
|
33
|
-
| `onInput` | `Function` |
|
|
34
|
-
| `onChange` | `Function` |
|
|
35
|
-
| `onKeyUp` | `Function` |
|
|
30
|
+
| `syntaxHighlighting` | `boolean` | - | Adds `syntaxHighlighting` class and Tab key indentation handling (textarea only) |
|
|
31
|
+
| `language` | `string` | - | Adds `language-<value>` class for Prism-compatible highlighting (requires `syntaxHighlighting`) |
|
|
32
|
+
| `validations` | `Array` | - | Validation rules; see Validation section below |
|
|
33
|
+
| `onInput` | `Function` | - | Called on every keystroke with an enhanced event containing `.value` |
|
|
34
|
+
| `onChange` | `Function` | - | Called on change with an enhanced event containing `.value` |
|
|
35
|
+
| `onKeyUp` | `Function` | - | Called on keyup with an enhanced event containing `.value` |
|
|
36
36
|
|
|
37
37
|
### Supported `type` values
|
|
38
38
|
|
|
@@ -62,7 +62,7 @@ input.validate({ validations?, value? }): Array | undefined
|
|
|
62
62
|
// Runs validation rules. Returns array of error messages if invalid, undefined if valid.
|
|
63
63
|
// Calling with no args uses options.validations and options.value.
|
|
64
64
|
|
|
65
|
-
input.isDirty: boolean // getter
|
|
65
|
+
input.isDirty: boolean // getter - true if current value differs from the initial value
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
## Events
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
On-screen keyboard where layout switching rebuilds the key DOM rather than showing/hiding rows. The decision: a clean rebuild on layout change is safer than managing per-key visibility across layout transitions; no key from one layout can bleed into another.
|
|
6
6
|
|
|
7
|
-
## Layout switch produces only keys from the new layout
|
|
7
|
+
## Layout switch produces only keys from the new layout - no residual keys remain
|
|
8
8
|
|
|
9
9
|
- changing `layout` removes all existing keys and builds the new set from scratch; a key that exists in layout A but not layout B is definitively absent after the switch
|
|
10
10
|
- does switching layout remove keys from the previous layout?
|
|
@@ -9,12 +9,12 @@ Transient notification that self-destructs. The design choice is that click-to-d
|
|
|
9
9
|
- callers pass `type: 'error'` and the component picks the matching icon; a custom `icon` option overrides this only when the standard mapping doesn't fit
|
|
10
10
|
- does a success notification use a visually distinct icon from an error notification?
|
|
11
11
|
|
|
12
|
-
## Clicking anywhere on the notification dismisses it
|
|
12
|
+
## Clicking anywhere on the notification dismisses it - the whole surface is the target
|
|
13
13
|
|
|
14
14
|
- notifications are temporary and should be easy to clear; there is no separate close button
|
|
15
15
|
- does clicking the notification body remove it from the page?
|
|
16
16
|
|
|
17
|
-
## Manual dismiss cancels a pending timeout
|
|
17
|
+
## Manual dismiss cancels a pending timeout - no duplicate destroy fires
|
|
18
18
|
|
|
19
19
|
- when a `timeout` is set and the user dismisses manually before it fires, the timer is cancelled so a second destroy call after the component is already gone cannot happen
|
|
20
20
|
- does manually dismissing a notification before its timeout prevent a second dismiss?
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Top-level layout component that takes ownership of stylesheet loading. The design decision: Page loads FontAwesome and typography as part of mounting; application code does not import or configure these separately.
|
|
6
6
|
|
|
7
|
-
## Required stylesheets load once
|
|
7
|
+
## Required stylesheets load once - remounting does not re-fetch
|
|
8
8
|
|
|
9
9
|
- Page checks whether each stylesheet is already present before injecting; remounting in a single-page app does not duplicate link elements or re-trigger network requests
|
|
10
10
|
- does mounting a Page a second time leave the stylesheet count unchanged?
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Radio group from an array of options. The design decision: the HTML `name` coordination that makes radios mutually exclusive is handled automatically; callers pass values and get a working group without managing name attributes.
|
|
6
6
|
|
|
7
|
-
## All radios in the group share one name
|
|
7
|
+
## All radios in the group share one name - mutual exclusivity is provided by the browser
|
|
8
8
|
|
|
9
9
|
- the component generates a shared `name` attribute; the browser enforces that only one radio in the group can be checked at a time
|
|
10
10
|
- does each radio input in the group share the same name attribute?
|
|
@@ -9,7 +9,7 @@ import { Router, View, Page } from '@vanilla-bean/components';
|
|
|
9
9
|
|
|
10
10
|
class HomeView extends View {
|
|
11
11
|
build() {
|
|
12
|
-
// render view content here
|
|
12
|
+
// render view content here - no Page needed, the shell provides it
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -43,20 +43,20 @@ new Router({
|
|
|
43
43
|
|
|
44
44
|
| Option | Type | Default | Description |
|
|
45
45
|
| --- | --- | --- | --- |
|
|
46
|
-
| `views` | `object` |
|
|
46
|
+
| `views` | `object` | - | **Required.** Maps route patterns to component classes. Patterns may contain `:param` segments. |
|
|
47
47
|
| `defaultPath` | `string` | First key of `views` | Route used when the hash is empty and for fallback on unmatched routes. |
|
|
48
|
-
| `notFound` | `typeof Component` |
|
|
49
|
-
| `onRenderView` | `Function` |
|
|
48
|
+
| `notFound` | `typeof Component` | - | Component class rendered when no route matches and `defaultPath` fallback also fails. Receives `{ route }` as an option. |
|
|
49
|
+
| `onRenderView` | `Function` | - | Called with the matched route string whenever a new view is rendered. |
|
|
50
50
|
| `mode` | `'hash' \| 'history'` | `'hash'` | `'hash'` uses URL fragments (`#/path`). `'history'` uses `pushState` and real pathnames (`/path`). History mode requires the server to serve `index.html` for all routes. |
|
|
51
51
|
|
|
52
52
|
## Properties
|
|
53
53
|
|
|
54
54
|
```js
|
|
55
|
-
router.path; // string
|
|
55
|
+
router.path; // string - current URL hash, stripped of '#/', '/', and query strings
|
|
56
56
|
router.path = '/users/42'; // navigate; sets window.location.hash and re-renders
|
|
57
57
|
|
|
58
|
-
router.route; // string
|
|
59
|
-
router.currentRoute; // string
|
|
58
|
+
router.route; // string - the matched route pattern for the current path (e.g. '/users/:id')
|
|
59
|
+
router.currentRoute; // string - the last successfully rendered route pattern
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
## Methods
|
|
@@ -77,9 +77,9 @@ Routes are matched using exact string equality first, then by replacing `:param`
|
|
|
77
77
|
|
|
78
78
|
```js
|
|
79
79
|
views: {
|
|
80
|
-
'/users/new': NewUserView, // exact match
|
|
81
|
-
'/users/:id': UserView, // one param
|
|
82
|
-
'/:section/:id': SectionView, // two params
|
|
80
|
+
'/users/new': NewUserView, // exact match - always wins
|
|
81
|
+
'/users/:id': UserView, // one param - tested before /:section/:id
|
|
82
|
+
'/:section/:id': SectionView, // two params - tested last
|
|
83
83
|
}
|
|
84
84
|
```
|
|
85
85
|
|
|
@@ -121,7 +121,7 @@ class SettingsView extends View {
|
|
|
121
121
|
}
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
## Example
|
|
124
|
+
## Example - full SPA shell
|
|
125
125
|
|
|
126
126
|
```js
|
|
127
127
|
import { Router, View, Nav, NavItem, Page } from '@vanilla-bean/components';
|
|
@@ -138,7 +138,7 @@ class ArticleView extends View {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
// Page is the app shell
|
|
141
|
+
// Page is the app shell - Router and Nav live inside it, not inside each view
|
|
142
142
|
const page = new Page({ title: 'My App', appendTo: document.body });
|
|
143
143
|
|
|
144
144
|
const router = new Router({
|
|
@@ -20,9 +20,9 @@ Select inherits all options from `Input`. Key additions:
|
|
|
20
20
|
|
|
21
21
|
| Option | Type | Default | Description |
|
|
22
22
|
| --- | --- | --- | --- |
|
|
23
|
-
| `options` | `Array<string\|object>` |
|
|
24
|
-
| `value` | `any` |
|
|
25
|
-
| `onChange` | `Function` |
|
|
23
|
+
| `options` | `Array<string\|object>` | - | List of selectable options. See shape below. Reactive: reassigning `options.options` rebuilds the `<option>` elements |
|
|
24
|
+
| `value` | `any` | - | Currently selected value. Matched against option `value` attributes |
|
|
25
|
+
| `onChange` | `Function` | - | Called with an enhanced event containing `.value` (the selected value string) on change |
|
|
26
26
|
|
|
27
27
|
All other `Input` options (`placeholder`, `validations`, `onInput`, etc.) are available but less commonly used with Select.
|
|
28
28
|
|
|
@@ -46,8 +46,8 @@ options: ['plain string', { label: 'Display Text', value: 42 }, { label: 'Unavai
|
|
|
46
46
|
## Methods
|
|
47
47
|
|
|
48
48
|
```js
|
|
49
|
-
select.value // getter
|
|
50
|
-
select.value = newVal // setter
|
|
49
|
+
select.value // getter - returns value, label, or textContent of the selected <option>
|
|
50
|
+
select.value = newVal // setter - sets elem.value directly
|
|
51
51
|
|
|
52
52
|
// Inherited from Input:
|
|
53
53
|
select.validate({ validations?, value? }): Array | undefined
|
|
@@ -56,7 +56,7 @@ class Select extends Input {
|
|
|
56
56
|
* @returns {*} Selected option value, label, or text content
|
|
57
57
|
*/
|
|
58
58
|
get value() {
|
|
59
|
-
// Use elem.options (HTMLOptionsCollection)
|
|
59
|
+
// Use elem.options (HTMLOptionsCollection) - works across optgroups
|
|
60
60
|
const selected = Array.from(this.elem.options).find(({ selected }) => selected);
|
|
61
61
|
|
|
62
62
|
return selected?.value ?? selected?.label ?? selected?.textContent ?? this.elem.value;
|
|
@@ -20,11 +20,11 @@ const table = new Table({
|
|
|
20
20
|
|
|
21
21
|
| Option | Type | Default | Description |
|
|
22
22
|
| --- | --- | --- | --- |
|
|
23
|
-
| `data` | `Array<object>` |
|
|
23
|
+
| `data` | `Array<object>` | - | Array of row data objects. Reactive: reassigning `options.data` rebuilds the tbody |
|
|
24
24
|
| `columns` | `Array<string\|object>` | `[]` | Column definitions; see shape below |
|
|
25
|
-
| `footer` | `Array<string\|object>` |
|
|
26
|
-
| `sortProperty` | `string` |
|
|
27
|
-
| `sortDirection` | `string` |
|
|
25
|
+
| `footer` | `Array<string\|object>` | - | Footer row cells, same shape as columns. Strings are capitalized automatically |
|
|
26
|
+
| `sortProperty` | `string` | - | Key of the currently sorted column |
|
|
27
|
+
| `sortDirection` | `string` | - | `'asc'` or `'desc'` |
|
|
28
28
|
| `onSort` | `Function` | built-in | Called with `(property, direction)` when a sortable column header is clicked. Default sorts `options.data` in place using `orderBy` |
|
|
29
29
|
|
|
30
30
|
### `columns` entry shape
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Sortable data table where sort state is explicit options. The design decision: `sortProperty` and `sortDirection` are readable and writable options; the sort state is in the component's options object, not hidden inside event handlers, so it's readable and settable from outside.
|
|
6
6
|
|
|
7
|
-
## Sort state lives in options
|
|
7
|
+
## Sort state lives in options - externally readable and settable
|
|
8
8
|
|
|
9
9
|
- clicking a column header updates `sortProperty` and `sortDirection` as regular options; external code can read or set sort state without querying the DOM
|
|
10
10
|
- does clicking a sortable column update sortProperty to that column's key?
|
|
@@ -10,7 +10,7 @@ Editable tag collection. The key decision: read-only mode removes the editing in
|
|
|
10
10
|
- does a readOnly TagList contain no input element?
|
|
11
11
|
- does a non-readOnly TagList contain both an input and an add button?
|
|
12
12
|
|
|
13
|
-
## Duplicate tags are silently rejected
|
|
13
|
+
## Duplicate tags are silently rejected - adding them does nothing
|
|
14
14
|
|
|
15
15
|
**method:** `duplicateRejected`
|
|
16
16
|
|
|
@@ -9,13 +9,13 @@ Component that adds a tooltip to whatever it wraps. The Tooltip is created durin
|
|
|
9
9
|
- the Tooltip component is created when the `tooltip` option is processed during render; after construction it exists as `this._tooltip`
|
|
10
10
|
- does the Tooltip component exist after construction with a tooltip option?
|
|
11
11
|
|
|
12
|
-
## Hover shows after a delay
|
|
12
|
+
## Hover shows after a delay - brief mouse-overs do not trigger the tooltip
|
|
13
13
|
|
|
14
14
|
- pointerover registers a 700ms timer; pointerout cancels it and hides immediately; hovering for longer than 700ms shows the tooltip
|
|
15
15
|
- does hovering for longer than 700ms show the tooltip?
|
|
16
16
|
- does leaving before 700ms prevent the tooltip from appearing?
|
|
17
17
|
|
|
18
|
-
## Cleanup removes the tooltip element
|
|
18
|
+
## Cleanup removes the tooltip element - nothing outlives the component
|
|
19
19
|
|
|
20
20
|
- when the TooltipWrapper is destroyed, the Tooltip component is also destroyed and removed from the DOM
|
|
21
21
|
- does destroying the wrapper also remove the tooltip from the DOM?
|
|
@@ -10,7 +10,7 @@ Multi-touch drawing canvas that tracks each pointer independently. The design de
|
|
|
10
10
|
- the whiteboard tracks each active pointer by its ID
|
|
11
11
|
- does a second finger touching the canvas start a separate line from the first?
|
|
12
12
|
|
|
13
|
-
## Draw throttle rate adapts to line width
|
|
13
|
+
## Draw throttle rate adapts to line width - no separate configuration needed
|
|
14
14
|
|
|
15
15
|
- the throttle delay is derived from `lineWidth + 3` (clamped to a range); thicker lines are visually coarser and tolerate a longer delay
|
|
16
16
|
- callers set `lineWidth` and the draw rate adjusts automatically; they do not configure throttle separately
|
package/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Minimal type declarations for IDE option-name recognition.
|
|
2
|
-
// These are a projection of the source
|
|
2
|
+
// These are a projection of the source - not a TypeScript implementation.
|
|
3
3
|
// VBC is plain JavaScript. These declarations exist so your IDE can suggest
|
|
4
4
|
// option names while you write. They do not constrain what the library can do.
|
|
5
5
|
|
|
@@ -190,7 +190,7 @@ export declare function styled<T extends typeof Elem>(
|
|
|
190
190
|
export declare function configured<T extends typeof Elem>(Base: T, options: Partial<ComponentOptions>): T;
|
|
191
191
|
|
|
192
192
|
// ── GENERATED: Built-in components ────────────────────────────────────────────
|
|
193
|
-
// This section is generated by devTools/buildTypes.js
|
|
193
|
+
// This section is generated by devTools/buildTypes.js - do not edit manually.
|
|
194
194
|
// Run `bun run build:types` to regenerate from JSDoc annotations.
|
|
195
195
|
|
|
196
196
|
export interface BottomSheetOptions extends ComponentOptions {
|
|
@@ -311,7 +311,7 @@ export interface DialogOptions extends ComponentOptions {
|
|
|
311
311
|
|
|
312
312
|
export declare class Dialog extends Component {
|
|
313
313
|
constructor(options?: DialogOptions, ...children: Array<Elem | HTMLElement | string>);
|
|
314
|
-
/** Content container
|
|
314
|
+
/** Content container - primary injection point for subclass and consumer content. */
|
|
315
315
|
readonly body: Component;
|
|
316
316
|
/** Opens the dialog, either as modal or non-modal */
|
|
317
317
|
open(): void;
|
package/package.json
CHANGED
package/theme/.test.js
CHANGED
|
@@ -19,13 +19,13 @@ describe('theme colors', () => {
|
|
|
19
19
|
expect(theme.colors.blue.toString()).not.toBe(before);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
test('white derives from gray
|
|
22
|
+
test('white derives from gray - updates when gray is reassigned', () => {
|
|
23
23
|
const before = theme.colors.white.toString();
|
|
24
24
|
theme.colors.gray = new TinyColor('hsl(200, 30%, 45%)');
|
|
25
25
|
expect(theme.colors.white.toString()).not.toBe(before);
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
test('selected follows yellow
|
|
28
|
+
test('selected follows yellow - updates when yellow is reassigned', () => {
|
|
29
29
|
const custom = new TinyColor('hsl(120, 55%, 45%)');
|
|
30
30
|
theme.colors.yellow = custom;
|
|
31
31
|
expect(theme.colors.selected.toString()).toBe(custom.toString());
|
package/theme/README.md
CHANGED
|
@@ -15,10 +15,10 @@ That said, the theme system is layered. You can work at any layer depending on h
|
|
|
15
15
|
For one-off overrides on a specific component instance, pass `styles` as an option:
|
|
16
16
|
|
|
17
17
|
```js
|
|
18
|
-
// Object form
|
|
18
|
+
// Object form - applied as inline styles
|
|
19
19
|
new Button({ styles: { backgroundColor: '#your-brand', borderRadius: '4px' } });
|
|
20
20
|
|
|
21
|
-
// Function form
|
|
21
|
+
// Function form - receives theme, generates scoped CSS
|
|
22
22
|
new Dialog({
|
|
23
23
|
styles: ({ colors }) => `
|
|
24
24
|
border-color: ${colors.purple};
|
|
@@ -69,10 +69,10 @@ The theme singleton is imported once and shared across all `styled()` calls. Mut
|
|
|
69
69
|
import { theme } from '@vanilla-bean/components';
|
|
70
70
|
import { TinyColor } from '@ctrl/tinycolor';
|
|
71
71
|
|
|
72
|
-
// Replace the accent color
|
|
72
|
+
// Replace the accent color - affects every component that uses colors.teal
|
|
73
73
|
theme.colors.teal = new TinyColor('#3d7aed');
|
|
74
74
|
|
|
75
|
-
// Then import and use components as normal
|
|
75
|
+
// Then import and use components as normal - they'll use your teal
|
|
76
76
|
import { Button, Dialog } from '@vanilla-bean/components';
|
|
77
77
|
```
|
|
78
78
|
|
|
@@ -279,10 +279,10 @@ Special utility colors and WCAG compliance helpers:
|
|
|
279
279
|
```js
|
|
280
280
|
// Utility colors
|
|
281
281
|
colors.transparent; // Transparent - for overlays, hidden elements
|
|
282
|
-
colors.white; //
|
|
283
|
-
colors.black; //
|
|
284
|
-
colors.superWhite; // #
|
|
285
|
-
colors.vantablack; // #
|
|
282
|
+
colors.white; // Near-white derived from gray (gray lightened 45%) - text on dark backgrounds
|
|
283
|
+
colors.black; // Near-black derived from gray (gray darkened 35%) - backgrounds, text on light surfaces
|
|
284
|
+
colors.superWhite; // #ffffff - pure white constant, independent of gray
|
|
285
|
+
colors.vantablack; // #000000 - pure black constant, independent of gray
|
|
286
286
|
|
|
287
287
|
// Semantic accent - the "focused/selected/active" color (focus outlines,
|
|
288
288
|
// ::selection, hover highlights, checked controls, scrollbar thumbs).
|
package/theme/colors.js
CHANGED
package/utils/browser.js
CHANGED
|
@@ -13,7 +13,7 @@ export const isDev = (() => {
|
|
|
13
13
|
(import.meta.env?.DEV === true || import.meta.env?.NODE_ENV === 'development')
|
|
14
14
|
)
|
|
15
15
|
return true;
|
|
16
|
-
// Bare browser import with no bundler
|
|
16
|
+
// Bare browser import with no bundler - treat localhost as dev so warnings surface
|
|
17
17
|
if (typeof location !== 'undefined' && (location.hostname === 'localhost' || location.hostname === '127.0.0.1'))
|
|
18
18
|
return true;
|
|
19
19
|
return false;
|