@vanilla-bean/components 1.1.0 → 2.0.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/Component/Component.js +226 -40
- package/Component/Component.scenarios.js +2 -2
- package/Component/Component.test.js +427 -40
- package/Component/README.md +480 -0
- package/Component/observeElementConnection.js +1 -1
- package/Elem/README.md +373 -0
- package/README.md +2 -2
- package/components/BottomSheet/BottomSheet.js +8 -16
- package/components/BottomSheet/README.md +1 -1
- package/components/Button/Button.js +12 -18
- package/components/Button/Button.lld.md +1 -1
- package/components/Calendar/Calendar.js +139 -59
- package/components/Calendar/CalendarEvent.js +9 -4
- package/components/Calendar/Toolbar.js +28 -20
- package/components/Calendar/index.js +1 -0
- package/components/Code/Code.js +26 -30
- package/components/Code/Code.lld.md +1 -1
- package/components/ColorPicker/ColorPicker.js +58 -58
- package/components/Dialog/Dialog.js +79 -73
- package/components/Dialog/Dialog.lld.md +1 -1
- package/components/Dialog/README.md +10 -10
- package/components/Form/Form.js +21 -17
- package/components/Form/Form.lld.md +2 -2
- package/components/Form/README.md +3 -3
- package/components/Icon/Icon.js +19 -11
- package/components/Input/Input.js +85 -67
- package/components/Input/README.md +7 -9
- package/components/Keyboard/Key.js +7 -10
- package/components/Keyboard/Keyboard.js +38 -46
- package/components/Keyboard/Keyboard.lld.md +1 -1
- package/components/Label/Label.js +52 -50
- package/components/Link/Link.js +14 -20
- package/components/List/List.js +28 -30
- package/components/Menu/Menu.js +19 -10
- package/components/Menu/Menu.lld.md +2 -2
- package/components/Notify/Notify.js +29 -19
- package/components/Notify/Notify.lld.md +2 -2
- package/components/Page/Page.lld.md +1 -1
- package/components/Popover/Popover.js +49 -44
- package/components/RadioButton/RadioButton.js +33 -29
- package/components/RadioButton/RadioButton.lld.md +1 -1
- package/components/Router/README.md +12 -12
- package/components/Router/Router.js +19 -21
- package/components/Select/README.md +5 -5
- package/components/Select/Select.js +25 -31
- package/components/Table/README.md +4 -4
- package/components/Table/Table.js +55 -36
- package/components/Table/Table.lld.md +1 -1
- package/components/TagList/Tag.js +16 -14
- package/components/TagList/TagList.js +15 -8
- package/components/TagList/TagList.lld.md +1 -1
- package/components/Tooltip/Tooltip.js +12 -25
- package/components/TooltipWrapper/TooltipWrapper.js +55 -49
- package/components/TooltipWrapper/TooltipWrapper.lld.md +2 -2
- package/components/Whiteboard/Whiteboard.js +45 -43
- package/components/Whiteboard/Whiteboard.lld.md +1 -1
- package/devTools/build.js +43 -0
- package/devTools/buildTypes.js +322 -0
- package/devTools/createComponent.js +155 -0
- package/devTools/extractJSDoc.js +395 -0
- package/devTools/processTemplate.js +500 -0
- package/devTools/updateComponentIndex.js +16 -0
- package/devTools/updateDemoViewIndex.js +90 -0
- package/eslint.config.cjs +5 -2
- package/index.d.ts +120 -62
- package/package.json +23 -22
- package/spellcheck.config.cjs +3 -0
- package/styled/README.md +329 -0
- package/theme/.test.js +2 -2
- package/theme/README.md +8 -8
- package/theme/colors.js +16 -13
- package/theme/colors.test.js +28 -0
- package/utils/browser.js +1 -1
- package/utils/element.js +2 -2
- package/FontWithASyntaxHighlighter-Regular.woff2 +0 -0
|
@@ -69,38 +69,42 @@ const RadioButtonInput = styled(
|
|
|
69
69
|
* @returns {RadioButton} RadioButton component instance
|
|
70
70
|
*/
|
|
71
71
|
class RadioButton extends Component {
|
|
72
|
-
static
|
|
73
|
-
options
|
|
74
|
-
|
|
72
|
+
static schema = {
|
|
73
|
+
options: {
|
|
74
|
+
set(value) {
|
|
75
|
+
this.empty();
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
if (!value) return;
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
79
|
+
this.append(
|
|
80
|
+
value.map(
|
|
81
|
+
option =>
|
|
82
|
+
new RadioButtonLabel({
|
|
83
|
+
tag: 'label',
|
|
84
|
+
append: [
|
|
85
|
+
new RadioButtonInput({
|
|
86
|
+
tag: 'input',
|
|
87
|
+
type: 'radio',
|
|
88
|
+
value: option?.value || option,
|
|
89
|
+
name: this.uniqueId,
|
|
90
|
+
checked: this.options.value === (option?.value || option),
|
|
91
|
+
onChange: event => {
|
|
92
|
+
if (event.target.checked) this.options.value = event.target.value;
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
option?.label || option,
|
|
96
|
+
],
|
|
97
|
+
}),
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
},
|
|
99
101
|
},
|
|
100
|
-
value
|
|
101
|
-
|
|
102
|
-
input.
|
|
103
|
-
|
|
102
|
+
value: {
|
|
103
|
+
set(value) {
|
|
104
|
+
this.elem.querySelectorAll('input[type="radio"]').forEach(input => {
|
|
105
|
+
input.checked = input.value === String(value);
|
|
106
|
+
});
|
|
107
|
+
},
|
|
104
108
|
},
|
|
105
109
|
};
|
|
106
110
|
}
|
|
@@ -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({
|
|
@@ -16,20 +16,26 @@ import { routeToRegex } from './utils';
|
|
|
16
16
|
* @returns {Router} Router component instance
|
|
17
17
|
*/
|
|
18
18
|
class Router extends Component {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
// Routing config read from this.options by the path getters and renderView()
|
|
20
|
+
static schema = {
|
|
21
|
+
views: {},
|
|
22
|
+
defaultPath: {},
|
|
23
|
+
notFound: {},
|
|
24
|
+
onRenderView: {},
|
|
25
|
+
mode: { default: 'hash', enum: ['hash', 'history'] },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
static prepareOptions(options) {
|
|
29
|
+
return {
|
|
30
|
+
defaultPath: Object.keys(options.views || {})[0],
|
|
31
|
+
...options,
|
|
32
|
+
style: {
|
|
33
|
+
display: 'flex',
|
|
34
|
+
flex: 1,
|
|
35
|
+
overflow: 'hidden',
|
|
36
|
+
...options.style,
|
|
30
37
|
},
|
|
31
|
-
|
|
32
|
-
);
|
|
38
|
+
};
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
/**
|
|
@@ -102,14 +108,6 @@ class Router extends Component {
|
|
|
102
108
|
return parameters;
|
|
103
109
|
}
|
|
104
110
|
|
|
105
|
-
static handlers = {
|
|
106
|
-
views() {},
|
|
107
|
-
defaultPath() {},
|
|
108
|
-
notFound() {},
|
|
109
|
-
onRenderView() {},
|
|
110
|
-
mode() {},
|
|
111
|
-
};
|
|
112
|
-
|
|
113
111
|
build() {
|
|
114
112
|
const reRenderView = () => this.renderView();
|
|
115
113
|
|
|
@@ -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
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import { Elem } from '../../Elem';
|
|
2
2
|
import { Input } from '../Input';
|
|
3
3
|
|
|
4
|
-
const defaultOptions = {
|
|
5
|
-
tag: 'select',
|
|
6
|
-
get priorityOptions() {
|
|
7
|
-
return new Set(['textContent', 'content', 'appendTo', 'prependTo', 'options']);
|
|
8
|
-
},
|
|
9
|
-
};
|
|
10
|
-
|
|
11
4
|
/**
|
|
12
5
|
* Select dropdown component extending Input with dynamic option management.
|
|
13
6
|
*
|
|
@@ -21,33 +14,34 @@ const defaultOptions = {
|
|
|
21
14
|
* @returns {Select} Select component instance
|
|
22
15
|
*/
|
|
23
16
|
class Select extends Input {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
static schema = {
|
|
18
|
+
tag: { default: 'select' },
|
|
19
|
+
autocomplete: { default: 'off' },
|
|
20
|
+
options: {
|
|
21
|
+
set(value) {
|
|
22
|
+
this.empty();
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
options(value) {
|
|
32
|
-
this.empty();
|
|
24
|
+
if (!value) return;
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
26
|
+
for (const option of value) {
|
|
27
|
+
// Optgroup: { label: 'Group', options: [...] }
|
|
28
|
+
if (typeof option === 'object' && Array.isArray(option.options)) {
|
|
29
|
+
const group = document.createElement('optgroup');
|
|
30
|
+
if (option.label) group.label = option.label;
|
|
31
|
+
for (const o of option.options) {
|
|
32
|
+
group.append(new Elem({ tag: 'option', ...(typeof o === 'object' ? o : { label: o, value: o }) }).elem);
|
|
33
|
+
}
|
|
34
|
+
this.elem.append(group);
|
|
35
|
+
} else {
|
|
36
|
+
this.append(
|
|
37
|
+
new Elem({ tag: 'option', ...(typeof option === 'object' ? option : { label: option, value: option }) }),
|
|
38
|
+
);
|
|
43
39
|
}
|
|
44
|
-
this.elem.append(group);
|
|
45
|
-
} else {
|
|
46
|
-
this.append(
|
|
47
|
-
new Elem({ tag: 'option', ...(typeof option === 'object' ? option : { label: option, value: option }) }),
|
|
48
|
-
);
|
|
49
40
|
}
|
|
50
|
-
|
|
41
|
+
|
|
42
|
+
// Re-apply the current value - option elements may not have existed when value was processed
|
|
43
|
+
if (this.options.value !== undefined) this.elem.value = this.options.value;
|
|
44
|
+
},
|
|
51
45
|
},
|
|
52
46
|
};
|
|
53
47
|
|
|
@@ -56,7 +50,7 @@ class Select extends Input {
|
|
|
56
50
|
* @returns {*} Selected option value, label, or text content
|
|
57
51
|
*/
|
|
58
52
|
get value() {
|
|
59
|
-
// Use elem.options (HTMLOptionsCollection)
|
|
53
|
+
// Use elem.options (HTMLOptionsCollection) - works across optgroups
|
|
60
54
|
const selected = Array.from(this.elem.options).find(({ selected }) => selected);
|
|
61
55
|
|
|
62
56
|
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,8 +4,6 @@ import { Elem } from '../../Elem';
|
|
|
4
4
|
import { Component } from '../../Component';
|
|
5
5
|
import { Icon } from '../Icon';
|
|
6
6
|
|
|
7
|
-
const defaultOptions = { tag: 'table' };
|
|
8
|
-
|
|
9
7
|
/**
|
|
10
8
|
* Data table component with sorting, column configuration, and footer support.
|
|
11
9
|
*
|
|
@@ -23,37 +21,55 @@ const defaultOptions = { tag: 'table' };
|
|
|
23
21
|
* @returns {Table} Table component instance
|
|
24
22
|
*/
|
|
25
23
|
class Table extends Component {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
footer: options.footer?.map(column => (typeof column === 'string' ? { content: capitalize(column) } : column)),
|
|
24
|
+
static schema = {
|
|
25
|
+
tag: { default: 'table' },
|
|
26
|
+
sortDirection: {
|
|
27
|
+
enum: ['asc', 'desc'],
|
|
28
|
+
set(value) {
|
|
29
|
+
if (!this.rendered) return;
|
|
30
|
+
if (this.options.onSort) this.options.onSort(this.options.sortProperty, value);
|
|
31
|
+
else
|
|
32
|
+
this.options.data = this.options.data.sort(
|
|
33
|
+
orderBy({ property: this.options.sortProperty, direction: value }),
|
|
34
|
+
);
|
|
38
35
|
},
|
|
39
|
-
...children,
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
static handlers = {
|
|
44
|
-
sortDirection(value) {
|
|
45
|
-
if (this.rendered) this.options.onSort(this.options.sortProperty, value);
|
|
46
36
|
},
|
|
47
|
-
data
|
|
48
|
-
|
|
37
|
+
data: {
|
|
38
|
+
set() {
|
|
39
|
+
this._renderTable();
|
|
40
|
+
},
|
|
49
41
|
},
|
|
50
|
-
selection
|
|
51
|
-
|
|
42
|
+
selection: {
|
|
43
|
+
set() {
|
|
44
|
+
this._renderTable();
|
|
45
|
+
},
|
|
52
46
|
},
|
|
47
|
+
columns: {
|
|
48
|
+
set() {
|
|
49
|
+
if (this.rendered) this._renderTable();
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
footer: {
|
|
53
|
+
set() {
|
|
54
|
+
if (this.rendered) this._renderTable();
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
// Read from this.options at sort time; sortProperty changes are always followed
|
|
58
|
+
// by a sortDirection change, which drives the re-render
|
|
59
|
+
sortProperty: {},
|
|
60
|
+
onSort: {},
|
|
53
61
|
};
|
|
54
62
|
|
|
55
63
|
_renderTable() {
|
|
56
64
|
const value = this.options.data;
|
|
65
|
+
// Columns and footer accept string shorthand - normalized at read time so
|
|
66
|
+
// construction and reactive assignment take the same path
|
|
67
|
+
const columns = (this.options.columns || []).map(column =>
|
|
68
|
+
typeof column === 'string' ? { key: column, content: capitalize(column) } : column,
|
|
69
|
+
);
|
|
70
|
+
const footer = this.options.footer?.map(column =>
|
|
71
|
+
typeof column === 'string' ? { content: capitalize(column) } : column,
|
|
72
|
+
);
|
|
57
73
|
|
|
58
74
|
this._sortSubscribers?.forEach(sub => sub.destroy?.());
|
|
59
75
|
this._sortSubscribers = null;
|
|
@@ -75,19 +91,22 @@ class Table extends Component {
|
|
|
75
91
|
this.thead.append(
|
|
76
92
|
new Component(
|
|
77
93
|
{ tag: 'tr' },
|
|
78
|
-
|
|
79
|
-
|
|
94
|
+
columns.map(column => {
|
|
95
|
+
// key/sort/dataColumn are column config for the Table itself - keep them off the th elem
|
|
96
|
+
const { key, sort, ...columnOptions } = column;
|
|
97
|
+
delete columnOptions.dataColumn;
|
|
98
|
+
const th = new Component({ tag: 'th', ...columnOptions, scope: 'col' });
|
|
80
99
|
|
|
81
|
-
if (
|
|
100
|
+
if (sort) {
|
|
82
101
|
const iconSub = this.options.subscriber('sortDirection', () => {
|
|
83
|
-
if (this.options.sortProperty !==
|
|
102
|
+
if (this.options.sortProperty !== key) return 'sort';
|
|
84
103
|
|
|
85
104
|
return this.options.sortDirection === 'asc' ? 'sort-down' : 'sort-up';
|
|
86
105
|
});
|
|
87
106
|
const styleSub = this.options.subscriber('sortDirection', () => ({
|
|
88
107
|
display: 'inline',
|
|
89
108
|
marginLeft: '6px',
|
|
90
|
-
...(this.options.sortProperty !==
|
|
109
|
+
...(this.options.sortProperty !== key
|
|
91
110
|
? { color: theme.colors.dark(theme.colors.gray) }
|
|
92
111
|
: { color: theme.colors.white }),
|
|
93
112
|
}));
|
|
@@ -96,7 +115,7 @@ class Table extends Component {
|
|
|
96
115
|
|
|
97
116
|
const updateAriaSort = () => {
|
|
98
117
|
let ariaSort = 'none';
|
|
99
|
-
if (this.options.sortProperty ===
|
|
118
|
+
if (this.options.sortProperty === key) {
|
|
100
119
|
ariaSort = this.options.sortDirection === 'asc' ? 'ascending' : 'descending';
|
|
101
120
|
}
|
|
102
121
|
th.elem.setAttribute('aria-sort', ariaSort);
|
|
@@ -121,12 +140,12 @@ class Table extends Component {
|
|
|
121
140
|
});
|
|
122
141
|
|
|
123
142
|
th.onPointerPress(() => {
|
|
124
|
-
if (this.options.sortProperty ===
|
|
143
|
+
if (this.options.sortProperty === key) {
|
|
125
144
|
this.options.sortDirection = this.options.sortDirection === 'asc' ? 'desc' : 'asc';
|
|
126
145
|
return;
|
|
127
146
|
}
|
|
128
147
|
|
|
129
|
-
this.options.sortProperty =
|
|
148
|
+
this.options.sortProperty = key;
|
|
130
149
|
this.options.sortDirection = 'desc';
|
|
131
150
|
});
|
|
132
151
|
}
|
|
@@ -141,7 +160,7 @@ class Table extends Component {
|
|
|
141
160
|
rowData =>
|
|
142
161
|
new Component(
|
|
143
162
|
{ tag: 'tr' },
|
|
144
|
-
|
|
163
|
+
columns.map(
|
|
145
164
|
column =>
|
|
146
165
|
new Component(
|
|
147
166
|
{
|
|
@@ -157,11 +176,11 @@ class Table extends Component {
|
|
|
157
176
|
),
|
|
158
177
|
);
|
|
159
178
|
|
|
160
|
-
if (
|
|
179
|
+
if (footer) {
|
|
161
180
|
this.tfoot.append(
|
|
162
181
|
new Component(
|
|
163
182
|
{ tag: 'tr' },
|
|
164
|
-
|
|
183
|
+
footer.map(footData => new Component({ tag: 'td', ...footData })),
|
|
165
184
|
),
|
|
166
185
|
);
|
|
167
186
|
}
|
|
@@ -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?
|
|
@@ -52,14 +52,23 @@ const StyledComponent = styled(
|
|
|
52
52
|
`,
|
|
53
53
|
);
|
|
54
54
|
|
|
55
|
-
const defaultOptions = { tag: 'li', readOnly: false };
|
|
56
|
-
|
|
57
55
|
class Tag extends StyledComponent {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
static schema = {
|
|
57
|
+
tag: { default: 'li' },
|
|
58
|
+
tabIndex: { default: 0 },
|
|
59
|
+
readOnly: {
|
|
60
|
+
default: false,
|
|
61
|
+
set(value) {
|
|
62
|
+
this.toggleClass('readOnly', !!value);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
textContent: {
|
|
66
|
+
set(value) {
|
|
67
|
+
this.elem.setAttribute('data-value', value);
|
|
68
|
+
this._textSpan.elem.textContent = value;
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
63
72
|
|
|
64
73
|
build() {
|
|
65
74
|
this._textSpan = new Elem({ tag: 'span', appendTo: this });
|
|
@@ -72,13 +81,6 @@ class Tag extends StyledComponent {
|
|
|
72
81
|
});
|
|
73
82
|
}
|
|
74
83
|
}
|
|
75
|
-
|
|
76
|
-
static handlers = {
|
|
77
|
-
textContent(value) {
|
|
78
|
-
this.elem.setAttribute('data-value', value);
|
|
79
|
-
this._textSpan.elem.textContent = value;
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
export default Tag;
|
|
@@ -32,8 +32,6 @@ const TagListInput = styled(
|
|
|
32
32
|
`,
|
|
33
33
|
);
|
|
34
34
|
|
|
35
|
-
const defaultOptions = { readOnly: false };
|
|
36
|
-
|
|
37
35
|
/**
|
|
38
36
|
* Interactive tag list component with add/remove functionality and input support.
|
|
39
37
|
*
|
|
@@ -47,11 +45,20 @@ const defaultOptions = { readOnly: false };
|
|
|
47
45
|
* @returns {TagList} TagList component instance
|
|
48
46
|
*/
|
|
49
47
|
class TagList extends StyledComponent {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
static schema = {
|
|
49
|
+
tag: { default: 'ul' },
|
|
50
|
+
readOnly: {
|
|
51
|
+
default: false,
|
|
52
|
+
set(value) {
|
|
53
|
+
this.toggleClass('readOnly', !!value);
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
tags: {
|
|
57
|
+
get default() {
|
|
58
|
+
return [];
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
55
62
|
|
|
56
63
|
_addTag(value) {
|
|
57
64
|
value = (value || '').trim();
|
|
@@ -91,7 +98,7 @@ class TagList extends StyledComponent {
|
|
|
91
98
|
}
|
|
92
99
|
|
|
93
100
|
this.append([
|
|
94
|
-
...
|
|
101
|
+
...this.options.tags.map(textContent => new Tag({ readOnly: this.options.readOnly, textContent })),
|
|
95
102
|
...(this.addTag ? [this.addTag] : []),
|
|
96
103
|
]);
|
|
97
104
|
}
|
|
@@ -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
|
|
|
@@ -80,8 +80,6 @@ const StyledPopover = styled(
|
|
|
80
80
|
`,
|
|
81
81
|
);
|
|
82
82
|
|
|
83
|
-
const defaultOptions = { position: 'topRight', autoOpen: false };
|
|
84
|
-
|
|
85
83
|
const position_enum = Object.freeze([
|
|
86
84
|
'center',
|
|
87
85
|
'top',
|
|
@@ -108,32 +106,21 @@ const position_enum = Object.freeze([
|
|
|
108
106
|
* @returns {Tooltip} Tooltip component instance
|
|
109
107
|
*/
|
|
110
108
|
class Tooltip extends StyledPopover {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
{
|
|
117
|
-
...
|
|
118
|
-
|
|
119
|
-
addClass: ['tooltip'].concat(options.addClass),
|
|
109
|
+
static schema = {
|
|
110
|
+
autoOpen: { default: false },
|
|
111
|
+
position: {
|
|
112
|
+
default: 'topRight',
|
|
113
|
+
enum: position_enum,
|
|
114
|
+
set(value) {
|
|
115
|
+
this.removeClass(...position_enum);
|
|
116
|
+
this.addClass(value);
|
|
120
117
|
},
|
|
121
|
-
...children,
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
static handlers = {
|
|
126
|
-
position(value) {
|
|
127
|
-
if (!position_enum.includes(value)) {
|
|
128
|
-
throw new Error(
|
|
129
|
-
`"${value}" is not a valid position. The position must be one of the following values: ${position_enum.join(', ')}`,
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
this.removeClass(...position_enum);
|
|
134
|
-
this.addClass(value);
|
|
135
118
|
},
|
|
136
119
|
};
|
|
120
|
+
|
|
121
|
+
static prepareOptions(options) {
|
|
122
|
+
return { ...options, addClass: ['tooltip'].concat(options.addClass) };
|
|
123
|
+
}
|
|
137
124
|
}
|
|
138
125
|
|
|
139
126
|
export default Tooltip;
|