@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
|
@@ -24,54 +24,46 @@ const KeyRow = styled(
|
|
|
24
24
|
* @returns {Keyboard} Keyboard component instance
|
|
25
25
|
*/
|
|
26
26
|
class Keyboard extends Component {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
},
|
|
59
|
-
|
|
27
|
+
static schema = {
|
|
28
|
+
layout: {
|
|
29
|
+
default: 'simple',
|
|
30
|
+
set(value) {
|
|
31
|
+
this.setLayout(value);
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
// Read directly from this.options in setLayout() and key press handling
|
|
35
|
+
layouts: {
|
|
36
|
+
default: {
|
|
37
|
+
simple: [
|
|
38
|
+
['simple', 'number'],
|
|
39
|
+
['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
|
|
40
|
+
['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'],
|
|
41
|
+
['shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm', 'backspace'],
|
|
42
|
+
[',', '.', 'space', '!', 'return'],
|
|
43
|
+
],
|
|
44
|
+
number: [
|
|
45
|
+
['simple', 'number'],
|
|
46
|
+
['1', '2', '3', 'backspace'],
|
|
47
|
+
['4', '5', '6', 'clear'],
|
|
48
|
+
['7', '8', '9', 'done'],
|
|
49
|
+
['.', '0', '-', 'exponent'],
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
keyDefinitions: {
|
|
54
|
+
default: {
|
|
55
|
+
simple: { text: 'ABC' },
|
|
56
|
+
number: { text: '123' },
|
|
57
|
+
shift: { class: 'u1_5' },
|
|
58
|
+
return: { class: 'u1_5' },
|
|
59
|
+
backspace: { class: 'u1_5' },
|
|
60
|
+
clear: { class: 'u1_5' },
|
|
61
|
+
done: { class: 'u1_5' },
|
|
62
|
+
exponent: { key: 'e', text: 'e', class: 'u1_5' },
|
|
63
|
+
space: { key: ' ', text: ' ', class: 'u6' },
|
|
60
64
|
},
|
|
61
|
-
...children,
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
this.setLayout(this.options.layout);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
static handlers = {
|
|
68
|
-
// Claim these keys so standard routing doesn't try to assign them as elem properties.
|
|
69
|
-
// Both are read directly from this.options in setLayout() and key resolution.
|
|
70
|
-
layouts() {},
|
|
71
|
-
keyDefinitions() {},
|
|
72
|
-
layout(value) {
|
|
73
|
-
this.setLayout(value);
|
|
74
65
|
},
|
|
66
|
+
tactileResponse: { default: true },
|
|
75
67
|
};
|
|
76
68
|
|
|
77
69
|
// todo native keyboard events https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent
|
|
@@ -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?
|
|
@@ -120,10 +120,6 @@ const StyledLabel = styled(
|
|
|
120
120
|
`,
|
|
121
121
|
);
|
|
122
122
|
|
|
123
|
-
const variant_enum = Object.freeze(['overlay', 'collapsible', 'inline', 'inline-after', 'simple']);
|
|
124
|
-
|
|
125
|
-
const defaultOptions = { variant: 'simple' };
|
|
126
|
-
|
|
127
123
|
/**
|
|
128
124
|
* Label component with multiple display variants and input association.
|
|
129
125
|
*
|
|
@@ -139,20 +135,61 @@ const defaultOptions = { variant: 'simple' };
|
|
|
139
135
|
* @returns {Label} Label component instance
|
|
140
136
|
*/
|
|
141
137
|
class Label extends StyledLabel {
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
static schema = {
|
|
139
|
+
variant: {
|
|
140
|
+
default: 'simple',
|
|
141
|
+
enum: ['overlay', 'collapsible', 'inline', 'inline-after', 'simple'],
|
|
142
|
+
set(value) {
|
|
143
|
+
this.removeClass(/\bvariant-\S+\b/g);
|
|
144
|
+
this.addClass(`variant-${value}`);
|
|
145
|
+
|
|
146
|
+
if (this._labelText) {
|
|
147
|
+
const shouldBeAfter = value === 'inline-after';
|
|
148
|
+
const isAfter = this._labelText.elem === this.elem.lastElementChild;
|
|
149
|
+
if (shouldBeAfter !== isAfter) this[shouldBeAfter ? 'append' : 'prepend'](this._labelText);
|
|
150
|
+
if (value === 'collapsible') {
|
|
151
|
+
this._labelText.elem.setAttribute('aria-expanded', this.hasClass('collapsed') ? 'false' : 'true');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
label: {
|
|
157
|
+
set(value) {
|
|
158
|
+
if (typeof value === 'object') this._labelText?.setOptions(value);
|
|
159
|
+
else if (this._labelText) this._labelText.options.content = value;
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
collapsed: {
|
|
163
|
+
set(value) {
|
|
164
|
+
this[value ? 'addClass' : 'removeClass']('collapsed');
|
|
165
|
+
if (this.options.variant === 'collapsible' && this._labelText) {
|
|
166
|
+
this._labelText.elem.setAttribute('aria-expanded', value ? 'false' : 'true');
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
for: {
|
|
171
|
+
set(value) {
|
|
172
|
+
let forId = typeof value === 'string' ? value : value?.id || value?.elem?.id;
|
|
144
173
|
|
|
145
|
-
|
|
146
|
-
|
|
174
|
+
if (!forId && value?._component) {
|
|
175
|
+
forId = value.elem.id = value.uniqueId;
|
|
176
|
+
}
|
|
147
177
|
|
|
148
|
-
|
|
149
|
-
{
|
|
150
|
-
...(children.length > 0 && { for: children[0] }),
|
|
151
|
-
...defaultOptions,
|
|
152
|
-
...options,
|
|
178
|
+
if (this._labelText) this._labelText.elem.htmlFor = forId ?? '';
|
|
153
179
|
},
|
|
154
|
-
|
|
155
|
-
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
static prepareOptions(options, children) {
|
|
184
|
+
return {
|
|
185
|
+
...(children.length > 0 && { for: children[0] }),
|
|
186
|
+
...options,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
constructor(options = {}, ...children) {
|
|
191
|
+
// String shorthand must resolve before Component spreads options
|
|
192
|
+
super(typeof options === 'string' ? { label: options } : options, ...children);
|
|
156
193
|
}
|
|
157
194
|
|
|
158
195
|
build() {
|
|
@@ -174,41 +211,6 @@ class Label extends StyledLabel {
|
|
|
174
211
|
|
|
175
212
|
this[this.options.variant === 'inline-after' ? 'append' : 'prepend'](this._labelText);
|
|
176
213
|
}
|
|
177
|
-
|
|
178
|
-
static handlers = {
|
|
179
|
-
label(value) {
|
|
180
|
-
if (typeof value === 'object') this._labelText?.setOptions(value);
|
|
181
|
-
else if (this._labelText) this._labelText.options.content = value;
|
|
182
|
-
},
|
|
183
|
-
collapsed(value) {
|
|
184
|
-
this[value ? 'addClass' : 'removeClass']('collapsed');
|
|
185
|
-
if (this.options.variant === 'collapsible' && this._labelText) {
|
|
186
|
-
this._labelText.elem.setAttribute('aria-expanded', value ? 'false' : 'true');
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
variant(value) {
|
|
190
|
-
this.removeClass(/\bvariant-\S+\b/g);
|
|
191
|
-
this.addClass(`variant-${value}`);
|
|
192
|
-
|
|
193
|
-
if (this._labelText) {
|
|
194
|
-
const shouldBeAfter = value === 'inline-after';
|
|
195
|
-
const isAfter = this._labelText.elem === this.elem.lastElementChild;
|
|
196
|
-
if (shouldBeAfter !== isAfter) this[shouldBeAfter ? 'append' : 'prepend'](this._labelText);
|
|
197
|
-
if (value === 'collapsible') {
|
|
198
|
-
this._labelText.elem.setAttribute('aria-expanded', this.hasClass('collapsed') ? 'false' : 'true');
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
for(value) {
|
|
203
|
-
let forId = typeof value === 'string' ? value : value?.id || value?.elem?.id;
|
|
204
|
-
|
|
205
|
-
if (!forId && value?._component) {
|
|
206
|
-
forId = value.elem.id = value.uniqueId;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (this._labelText) this._labelText.elem.htmlFor = forId ?? '';
|
|
210
|
-
},
|
|
211
|
-
};
|
|
212
214
|
}
|
|
213
215
|
|
|
214
216
|
export default Label;
|
package/components/Link/Link.js
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import { TooltipWrapper } from '../TooltipWrapper';
|
|
2
2
|
|
|
3
|
-
const defaultOptions = {
|
|
4
|
-
tag: 'a',
|
|
5
|
-
variant: 'link',
|
|
6
|
-
get tooltip() {
|
|
7
|
-
return { icon: 'link', style: { fontSize: '12px' } };
|
|
8
|
-
},
|
|
9
|
-
};
|
|
10
|
-
const variant_enum = Object.freeze(['link', 'button']);
|
|
11
|
-
|
|
12
3
|
/**
|
|
13
4
|
* Link component with tooltip support and display variants.
|
|
14
5
|
*
|
|
@@ -25,17 +16,20 @@ const variant_enum = Object.freeze(['link', 'button']);
|
|
|
25
16
|
* @returns {Link} Link component instance
|
|
26
17
|
*/
|
|
27
18
|
class Link extends TooltipWrapper {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
19
|
+
static schema = {
|
|
20
|
+
tag: { default: 'a' },
|
|
21
|
+
variant: {
|
|
22
|
+
default: 'link',
|
|
23
|
+
enum: ['link', 'button'],
|
|
24
|
+
set(value) {
|
|
25
|
+
this.removeClass(/\bvariant-\S+\b/g);
|
|
26
|
+
this.addClass(`variant-${value}`);
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
tooltip: {
|
|
30
|
+
get default() {
|
|
31
|
+
return { icon: 'link', style: { fontSize: '12px' } };
|
|
32
|
+
},
|
|
39
33
|
},
|
|
40
34
|
};
|
|
41
35
|
}
|
package/components/List/List.js
CHANGED
|
@@ -42,41 +42,39 @@ const ListItem = styled(
|
|
|
42
42
|
* @returns {List} List component instance
|
|
43
43
|
*/
|
|
44
44
|
export default class List extends StyledComponent {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
static schema = {
|
|
46
|
+
tag: { default: 'ul' },
|
|
47
|
+
noStyle: {
|
|
48
|
+
set(value) {
|
|
49
|
+
this.toggleClass('no-style', !!value);
|
|
50
50
|
},
|
|
51
|
-
...children,
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
static handlers = {
|
|
56
|
-
noStyle(value) {
|
|
57
|
-
this.toggleClass('no-style', !!value);
|
|
58
51
|
},
|
|
59
|
-
items
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
items: {
|
|
53
|
+
set(value) {
|
|
54
|
+
if (!value) {
|
|
55
|
+
this.empty();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
this.content(
|
|
60
|
+
value.map(item => {
|
|
61
|
+
const isContent = typeof item === 'string' || item?.elem || Array.isArray(item);
|
|
62
|
+
const listItem = new ListItem({ tag: 'li', ...item?.listItemOptions });
|
|
63
|
+
// listItemOptions belongs to the li wrapper - strip it before passing item options along.
|
|
64
|
+
const itemOptions = isContent ? item : { ...item };
|
|
65
|
+
if (!isContent) delete itemOptions.listItemOptions;
|
|
69
66
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
if (this.options.ListItemComponent) {
|
|
68
|
+
listItem.content(new this.options.ListItemComponent(isContent ? { content: item } : itemOptions));
|
|
69
|
+
} else if (item.ListItemComponent) {
|
|
70
|
+
listItem.content(new item.ListItemComponent(itemOptions));
|
|
71
|
+
} else if (isContent) listItem.content(item);
|
|
72
|
+
else listItem.setOptions(itemOptions);
|
|
76
73
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
return listItem;
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
},
|
|
80
78
|
},
|
|
81
79
|
};
|
|
82
80
|
}
|
package/components/Menu/Menu.js
CHANGED
|
@@ -37,23 +37,32 @@ const StyledList = styled(
|
|
|
37
37
|
* borders, and click handling. Provides consistent menu appearance and behavior.
|
|
38
38
|
* @param {object} [options={}] - Menu configuration options
|
|
39
39
|
* @param {Array<*>} [options.items] - Array of menu items to render
|
|
40
|
-
* @param {Function} [options.onSelect] - Handler
|
|
40
|
+
* @param {Function} [options.onSelect] - Handler for the "select" CustomEvent fired when a menu item is activated; the activation event is in event.detail
|
|
41
41
|
* @param {Component} [options.ListItemComponent] - Custom component class for rendering menu items
|
|
42
42
|
* @param {...(Component|HTMLElement|string)} children - Child elements to append
|
|
43
43
|
* @returns {Menu} Menu component instance
|
|
44
44
|
*/
|
|
45
45
|
export default class Menu extends StyledList {
|
|
46
|
-
static
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
static schema = {
|
|
47
|
+
role: { default: 'menu' },
|
|
48
|
+
items: {
|
|
49
|
+
set(value, next) {
|
|
50
|
+
next(value);
|
|
51
|
+
if (!value) return;
|
|
52
|
+
const items = Array.from(this.elem.children).filter(el => el.tagName === 'LI');
|
|
53
|
+
items.forEach((li, i) => li.setAttribute('tabindex', i === 0 ? '0' : '-1'));
|
|
54
|
+
},
|
|
52
55
|
},
|
|
53
56
|
};
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
static events = ['select'];
|
|
59
|
+
|
|
60
|
+
build() {
|
|
61
|
+
this.on({
|
|
62
|
+
targetEvent: 'pointerdown',
|
|
63
|
+
id: 'menuSelect',
|
|
64
|
+
callback: event => this.emit('select', event),
|
|
65
|
+
});
|
|
57
66
|
|
|
58
67
|
this.on({
|
|
59
68
|
targetEvent: 'keydown',
|
|
@@ -78,7 +87,7 @@ export default class Menu extends StyledList {
|
|
|
78
87
|
next = items[items.length - 1];
|
|
79
88
|
} else if ((e.key === 'Enter' || e.key === ' ') && currentIndex >= 0) {
|
|
80
89
|
e.preventDefault();
|
|
81
|
-
this.
|
|
90
|
+
this.emit('select', e);
|
|
82
91
|
return;
|
|
83
92
|
}
|
|
84
93
|
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
> ./Menu.js
|
|
4
4
|
|
|
5
|
-
Styled list whose items
|
|
5
|
+
Styled list whose items emit a registered `select` CustomEvent, with the activation event in `detail`. The design decision: Menu routes selection through the same unified pointer+keyboard activation path as Button; keyboard and pointer users get the same experience without separate handlers. `onSelect` is the option-form listener for the event.
|
|
6
6
|
|
|
7
7
|
## Selecting a menu item works the same way regardless of input method
|
|
8
8
|
|
|
9
|
-
- clicking, touching, Space/Enter, and screen reader activation all
|
|
9
|
+
- clicking, touching, Space/Enter, and screen reader activation all emit `select`; one handler, no separate keyboard path
|
|
10
10
|
- does clicking a menu item invoke onSelect?
|
|
11
11
|
|
|
12
12
|
## Menu renders any item format that List supports
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { styled } from '../../styled';
|
|
2
2
|
import { Popover } from '../Popover';
|
|
3
3
|
|
|
4
|
-
const type_enum = Object.freeze(['info', 'success', 'warning', 'error']);
|
|
5
|
-
|
|
6
4
|
const StyledPopover = styled(
|
|
7
5
|
Popover,
|
|
8
6
|
({ colors, fonts }) => `
|
|
@@ -68,29 +66,41 @@ const StyledPopover = styled(
|
|
|
68
66
|
* @returns {Notify} Notify component instance
|
|
69
67
|
*/
|
|
70
68
|
export default class Notify extends StyledPopover {
|
|
71
|
-
|
|
69
|
+
static schema = {
|
|
70
|
+
type: { default: 'success', enum: ['info', 'success', 'warning', 'error'] },
|
|
71
|
+
timeout: {
|
|
72
|
+
set(value) {
|
|
73
|
+
clearTimeout(this.timeout);
|
|
74
|
+
if (!value) return;
|
|
75
|
+
this.timeout = setTimeout(() => this.destroy(), value);
|
|
76
|
+
this.replaceCleanup('timeout', () => clearTimeout(this.timeout));
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
72
80
|
|
|
73
|
-
|
|
74
|
-
const {
|
|
75
|
-
const icon =
|
|
76
|
-
options.icon ||
|
|
77
|
-
{ info: 'circle-info', success: 'check', warning: 'triangle-exclamation', error: 'skull-crossbones' }[type];
|
|
81
|
+
static prepareOptions(options) {
|
|
82
|
+
const { type } = options;
|
|
78
83
|
|
|
79
|
-
|
|
84
|
+
return {
|
|
80
85
|
role: type === 'error' || type === 'warning' ? 'alert' : 'status',
|
|
81
86
|
'aria-atomic': 'true',
|
|
82
|
-
onPointerPress: e => {
|
|
83
|
-
if (!e.target.closest('button, [role="button"]')) this.destroy();
|
|
84
|
-
},
|
|
85
|
-
state: 'manual',
|
|
86
87
|
...options,
|
|
87
88
|
addClass: [type].concat(options.addClass),
|
|
88
|
-
icon
|
|
89
|
-
|
|
89
|
+
icon:
|
|
90
|
+
options.icon ||
|
|
91
|
+
{ info: 'circle-info', success: 'check', warning: 'triangle-exclamation', error: 'skull-crossbones' }[type],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
90
94
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
build() {
|
|
96
|
+
super.build();
|
|
97
|
+
|
|
98
|
+
this.on({
|
|
99
|
+
targetEvent: 'pointerdown',
|
|
100
|
+
id: 'notifyDismiss',
|
|
101
|
+
callback: e => {
|
|
102
|
+
if (!e.target.closest('button, [role="button"]')) this.destroy();
|
|
103
|
+
},
|
|
104
|
+
});
|
|
95
105
|
}
|
|
96
106
|
}
|
|
@@ -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?
|
|
@@ -18,19 +18,6 @@ const StyledIcon = styled(
|
|
|
18
18
|
`,
|
|
19
19
|
);
|
|
20
20
|
|
|
21
|
-
const defaultOptions = {
|
|
22
|
-
uniqueId: true,
|
|
23
|
-
state: 'manual',
|
|
24
|
-
outsideClose: false,
|
|
25
|
-
get viewport() {
|
|
26
|
-
return document.documentElement;
|
|
27
|
-
},
|
|
28
|
-
get appendTo() {
|
|
29
|
-
return document.body;
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
const state_enum = Object.freeze(['auto', 'manual']);
|
|
33
|
-
|
|
34
21
|
/**
|
|
35
22
|
* Popover component using native HTML popover API with edge-aware positioning.
|
|
36
23
|
*
|
|
@@ -50,45 +37,63 @@ const state_enum = Object.freeze(['auto', 'manual']);
|
|
|
50
37
|
* @returns {Popover} Popover component instance
|
|
51
38
|
*/
|
|
52
39
|
export default class Popover extends StyledIcon {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
40
|
+
static schema = {
|
|
41
|
+
uniqueId: { default: true },
|
|
42
|
+
state: {
|
|
43
|
+
default: 'manual',
|
|
44
|
+
enum: ['auto', 'manual'],
|
|
45
|
+
set(value) {
|
|
46
|
+
this.elem.popover = value;
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
autoOpen: { default: true },
|
|
50
|
+
// Positioning and dismissal config read from this.options by show()/edgeAwarePlacement()
|
|
51
|
+
outsideClose: { default: false },
|
|
52
|
+
viewport: {
|
|
53
|
+
get default() {
|
|
54
|
+
return document.documentElement;
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
appendTo: {
|
|
58
|
+
get default() {
|
|
59
|
+
return document.body;
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
maxWidth: { default: 264 },
|
|
63
|
+
maxHeight: { default: 132 },
|
|
64
|
+
x: {
|
|
65
|
+
set() {
|
|
66
|
+
this._placeAtPoint();
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
y: {
|
|
70
|
+
set() {
|
|
71
|
+
this._placeAtPoint();
|
|
68
72
|
},
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
},
|
|
74
|
+
};
|
|
71
75
|
|
|
72
|
-
|
|
76
|
+
build() {
|
|
77
|
+
this.on({
|
|
78
|
+
targetEvent: 'connected',
|
|
79
|
+
id: 'popoverAutoOpen',
|
|
80
|
+
callback: () => {
|
|
81
|
+
if (!this.options.autoOpen) return;
|
|
82
|
+
const timeoutId = setTimeout(() => this.show(), 200);
|
|
83
|
+
this.replaceCleanup('autoOpen', () => clearTimeout(timeoutId));
|
|
84
|
+
},
|
|
85
|
+
});
|
|
73
86
|
}
|
|
74
87
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
},
|
|
79
|
-
outsideClose() {},
|
|
80
|
-
viewport() {},
|
|
81
|
-
maxWidth() {},
|
|
82
|
-
maxHeight() {},
|
|
83
|
-
x() {},
|
|
84
|
-
y() {},
|
|
85
|
-
};
|
|
88
|
+
_placeAtPoint() {
|
|
89
|
+
if (this.options.x !== undefined && this.options.y !== undefined) this.edgeAwarePlacement(this.options);
|
|
90
|
+
}
|
|
86
91
|
|
|
87
92
|
edgeAwarePlacement({
|
|
88
93
|
x,
|
|
89
94
|
y,
|
|
90
|
-
maxHeight = this.options.maxHeight
|
|
91
|
-
maxWidth = this.options.maxWidth
|
|
95
|
+
maxHeight = this.options.maxHeight,
|
|
96
|
+
maxWidth = this.options.maxWidth,
|
|
92
97
|
padding = 24,
|
|
93
98
|
viewport = this.options.viewport || this.options.appendTo,
|
|
94
99
|
}) {
|