tosijs-ui 1.0.1 → 1.0.2
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/README.md +4 -2
- package/dist/iife.js +70 -60
- package/dist/iife.js.map +42 -42
- package/dist/index.js +15 -37
- package/dist/index.js.map +39 -39
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
- package/dist/ab-test.js +0 -116
- package/dist/babylon-3d.js +0 -292
- package/dist/bodymovin-player.js +0 -172
- package/dist/bp-loader.js +0 -26
- package/dist/carousel.js +0 -308
- package/dist/code-editor.js +0 -102
- package/dist/color-input.js +0 -112
- package/dist/data-table.js +0 -774
- package/dist/drag-and-drop.js +0 -386
- package/dist/editable-rect.js +0 -450
- package/dist/filter-builder.js +0 -468
- package/dist/float.js +0 -170
- package/dist/form.js +0 -466
- package/dist/gamepad.js +0 -115
- package/dist/icon-data.js +0 -308
- package/dist/icon-types.js +0 -1
- package/dist/icons.js +0 -374
- package/dist/index-iife.js +0 -4
- package/dist/live-example.js +0 -611
- package/dist/localize.js +0 -381
- package/dist/make-sorter.js +0 -119
- package/dist/make-sorter.test.d.ts +0 -1
- package/dist/make-sorter.test.js +0 -48
- package/dist/mapbox.js +0 -161
- package/dist/markdown-viewer.js +0 -173
- package/dist/match-shortcut.js +0 -13
- package/dist/match-shortcut.test.d.ts +0 -1
- package/dist/match-shortcut.test.js +0 -194
- package/dist/menu.js +0 -614
- package/dist/notifications.js +0 -308
- package/dist/password-strength.js +0 -302
- package/dist/playwright.config.d.ts +0 -9
- package/dist/playwright.config.js +0 -73
- package/dist/pop-float.js +0 -231
- package/dist/rating.js +0 -192
- package/dist/rich-text.js +0 -296
- package/dist/segmented.js +0 -298
- package/dist/select.js +0 -427
- package/dist/side-nav.js +0 -106
- package/dist/size-break.js +0 -118
- package/dist/sizer.js +0 -92
- package/dist/src/ab-test.d.ts +0 -14
- package/dist/src/babylon-3d.d.ts +0 -53
- package/dist/src/bodymovin-player.d.ts +0 -32
- package/dist/src/bp-loader.d.ts +0 -0
- package/dist/src/carousel.d.ts +0 -113
- package/dist/src/code-editor.d.ts +0 -27
- package/dist/src/color-input.d.ts +0 -41
- package/dist/src/data-table.d.ts +0 -79
- package/dist/src/drag-and-drop.d.ts +0 -2
- package/dist/src/editable-rect.d.ts +0 -97
- package/dist/src/filter-builder.d.ts +0 -64
- package/dist/src/float.d.ts +0 -18
- package/dist/src/form.d.ts +0 -68
- package/dist/src/gamepad.d.ts +0 -34
- package/dist/src/icon-data.d.ts +0 -309
- package/dist/src/icon-types.d.ts +0 -7
- package/dist/src/icons.d.ts +0 -17
- package/dist/src/index.d.ts +0 -37
- package/dist/src/live-example.d.ts +0 -51
- package/dist/src/localize.d.ts +0 -30
- package/dist/src/make-sorter.d.ts +0 -3
- package/dist/src/mapbox.d.ts +0 -24
- package/dist/src/markdown-viewer.d.ts +0 -15
- package/dist/src/match-shortcut.d.ts +0 -9
- package/dist/src/menu.d.ts +0 -60
- package/dist/src/notifications.d.ts +0 -106
- package/dist/src/password-strength.d.ts +0 -35
- package/dist/src/pop-float.d.ts +0 -10
- package/dist/src/rating.d.ts +0 -62
- package/dist/src/rich-text.d.ts +0 -28
- package/dist/src/segmented.d.ts +0 -80
- package/dist/src/select.d.ts +0 -43
- package/dist/src/side-nav.d.ts +0 -36
- package/dist/src/size-break.d.ts +0 -18
- package/dist/src/sizer.d.ts +0 -34
- package/dist/src/tab-selector.d.ts +0 -91
- package/dist/src/tag-list.d.ts +0 -37
- package/dist/src/track-drag.d.ts +0 -5
- package/dist/src/version.d.ts +0 -1
- package/dist/src/via-tag.d.ts +0 -2
- package/dist/tab-selector.js +0 -326
- package/dist/tag-list.js +0 -375
- package/dist/track-drag.js +0 -143
- package/dist/version.js +0 -1
- package/dist/via-tag.js +0 -102
package/dist/select.js
DELETED
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
/*#
|
|
2
|
-
# select
|
|
3
|
-
|
|
4
|
-
`<xin-select>` (`xinSelect` is the `ElementCreator`) is a replacement for the lamentable
|
|
5
|
-
built in `<select>` element that addresses its various shortcomings.
|
|
6
|
-
|
|
7
|
-
- since `<xin-select>` is powered by `popMenu`, and supports separators and submenus.
|
|
8
|
-
- options can have icons.
|
|
9
|
-
- `<xin-select>` will retain and display a value even if the matching option is missing.
|
|
10
|
-
- its displayed value can be made `editable`, allowing use as a "combo box".
|
|
11
|
-
- options can have `async` callbacks that return a value.
|
|
12
|
-
- picking an item triggers an `action` event even if the value hasn't changed.
|
|
13
|
-
- available options are set via the `options` attribute or the element's `options` property (not `<option>` elements)
|
|
14
|
-
|
|
15
|
-
```html
|
|
16
|
-
<xin-select
|
|
17
|
-
title="simple select"
|
|
18
|
-
options="this,that,,the other"
|
|
19
|
-
value="not an option!"
|
|
20
|
-
></xin-select><br>
|
|
21
|
-
<xin-select
|
|
22
|
-
show-icon
|
|
23
|
-
title="has captions"
|
|
24
|
-
class="captions"
|
|
25
|
-
value="image"
|
|
26
|
-
></xin-select><br>
|
|
27
|
-
<xin-select
|
|
28
|
-
show-icon
|
|
29
|
-
title="combo select with icons"
|
|
30
|
-
class="icons"
|
|
31
|
-
editable
|
|
32
|
-
placeholder="pick an icon"
|
|
33
|
-
></xin-select><br>
|
|
34
|
-
<xin-select
|
|
35
|
-
show-icon
|
|
36
|
-
hide-caption
|
|
37
|
-
title="icons only"
|
|
38
|
-
class="icons-only"
|
|
39
|
-
placeholder="pick an icon"
|
|
40
|
-
></xin-select>
|
|
41
|
-
<pre contenteditable>Select some text in here…
|
|
42
|
-
…to check for focus stealing</pre>
|
|
43
|
-
```
|
|
44
|
-
```js
|
|
45
|
-
const { icons } = xinjsui
|
|
46
|
-
|
|
47
|
-
const captions = preview.querySelector('.captions')
|
|
48
|
-
|
|
49
|
-
captions.options = [
|
|
50
|
-
{
|
|
51
|
-
caption: 'a heading',
|
|
52
|
-
value: 'heading'
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
caption: 'a paragraph',
|
|
56
|
-
value: 'paragraph'
|
|
57
|
-
},
|
|
58
|
-
null,
|
|
59
|
-
{
|
|
60
|
-
caption: 'choose some other',
|
|
61
|
-
options: [
|
|
62
|
-
{
|
|
63
|
-
icon: 'image',
|
|
64
|
-
caption: 'an image',
|
|
65
|
-
value: 'image'
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
icon: 'fileText',
|
|
69
|
-
caption: 'a text file',
|
|
70
|
-
value: 'text',
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
icon: 'video',
|
|
74
|
-
caption: 'a video',
|
|
75
|
-
value: 'video'
|
|
76
|
-
},
|
|
77
|
-
null,
|
|
78
|
-
{
|
|
79
|
-
caption: 'anything goes…',
|
|
80
|
-
value: () => prompt('Enter your other', 'other') || undefined
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
caption: 'brother… (after 1s delay)',
|
|
84
|
-
value: async () => new Promise(resolve => {
|
|
85
|
-
setTimeout(() => resolve('brother'), 1000)
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
]
|
|
89
|
-
}
|
|
90
|
-
]
|
|
91
|
-
|
|
92
|
-
const iconsSelect = preview.querySelector('.icons')
|
|
93
|
-
const iconsOnly = preview.querySelector('.icons-only')
|
|
94
|
-
|
|
95
|
-
iconsSelect.options = iconsOnly.options = Object.keys(icons).sort().map(icon =>({
|
|
96
|
-
icon,
|
|
97
|
-
caption: icon,
|
|
98
|
-
value: icon
|
|
99
|
-
}))
|
|
100
|
-
|
|
101
|
-
preview.addEventListener('action', (event) => {
|
|
102
|
-
console.log(event.target.title, 'user picked', event.target.value)
|
|
103
|
-
}, true)
|
|
104
|
-
|
|
105
|
-
preview.addEventListener('change', (event) => {
|
|
106
|
-
console.log(event.target.title, 'changed to', event.target.value)
|
|
107
|
-
}, true)
|
|
108
|
-
```
|
|
109
|
-
<xin-css-var-editor element-selector="xin-select"></xin-css-var-editor>
|
|
110
|
-
|
|
111
|
-
## `options`
|
|
112
|
-
|
|
113
|
-
type OptionRequest = () => Promise<string | undefined>
|
|
114
|
-
|
|
115
|
-
export interface SelectOption {
|
|
116
|
-
icon?: string | HTMLElement
|
|
117
|
-
caption: string
|
|
118
|
-
value: string | OptionRequest
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export interface SelectOptionSubmenu {
|
|
122
|
-
icon?: string | HTMLElement
|
|
123
|
-
caption: string
|
|
124
|
-
options: SelectOptions
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export type SelectOptions = Array<string | null | SelectOption | SelectOptionSubmenu>
|
|
128
|
-
|
|
129
|
-
A `<xin-select>` can be assigned `options` as a string of comma-delimited choices,
|
|
130
|
-
or be provided a `SelectOptions` array (which allows for submenus, separators, etc.).
|
|
131
|
-
|
|
132
|
-
## Attributes
|
|
133
|
-
|
|
134
|
-
`<xin-select>` supports several attributes:
|
|
135
|
-
|
|
136
|
-
- `editable` lets the user directly edit the value (like a "combo box").
|
|
137
|
-
- `show-icon` displays the icon corresponding to the currently selected value.
|
|
138
|
-
- `hide-caption` hides the caption.
|
|
139
|
-
- `placeholder` allows you to set a placeholder.
|
|
140
|
-
- `options` allows you to assign options as a comma-delimited string attribute.
|
|
141
|
-
|
|
142
|
-
## Events
|
|
143
|
-
|
|
144
|
-
Picking an option triggers an `action` event (whether or not this changes the value).
|
|
145
|
-
|
|
146
|
-
Changing the value, either by typing in an editable `<xin-select>` or picking a new
|
|
147
|
-
value triggers a `change` event.
|
|
148
|
-
|
|
149
|
-
You can look at the console to see the events triggered by the second example.
|
|
150
|
-
|
|
151
|
-
## Localization
|
|
152
|
-
|
|
153
|
-
`<xin-select>` supports the `localized` attribute which automatically localizes
|
|
154
|
-
options.
|
|
155
|
-
|
|
156
|
-
```html
|
|
157
|
-
<xin-select
|
|
158
|
-
localized
|
|
159
|
-
placeholder="localized placeholder"
|
|
160
|
-
options="yes,no,,moderate"
|
|
161
|
-
></xin-select>
|
|
162
|
-
```
|
|
163
|
-
*/
|
|
164
|
-
import { Component as WebComponent, elements, vars, throttle, } from 'xinjs';
|
|
165
|
-
import { icons } from './icons';
|
|
166
|
-
import { popMenu, removeLastMenu } from './menu';
|
|
167
|
-
import { localize, XinLocalized } from './localize';
|
|
168
|
-
const { button, span, input } = elements;
|
|
169
|
-
const hasValue = (options, value) => {
|
|
170
|
-
return !!options.find((option) => {
|
|
171
|
-
if (option === null || value == null) {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
else if (Array.isArray(option)) {
|
|
175
|
-
return hasValue(option, value);
|
|
176
|
-
}
|
|
177
|
-
else if (option.value === value || option === value) {
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
};
|
|
182
|
-
export class XinSelect extends WebComponent {
|
|
183
|
-
editable = false;
|
|
184
|
-
showIcon = false;
|
|
185
|
-
hideCaption = false;
|
|
186
|
-
options = '';
|
|
187
|
-
value = '';
|
|
188
|
-
placeholder = '';
|
|
189
|
-
filter = '';
|
|
190
|
-
localized = false;
|
|
191
|
-
setValue = (value, triggerAction = false) => {
|
|
192
|
-
if (this.value !== value) {
|
|
193
|
-
this.value = value;
|
|
194
|
-
this.queueRender(true);
|
|
195
|
-
}
|
|
196
|
-
if (triggerAction) {
|
|
197
|
-
this.dispatchEvent(new Event('action'));
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
getValue = () => this.value;
|
|
201
|
-
get selectOptions() {
|
|
202
|
-
return typeof this.options === 'string'
|
|
203
|
-
? this.options.split(',').map((option) => option.trim() || null)
|
|
204
|
-
: this.options;
|
|
205
|
-
}
|
|
206
|
-
buildOptionMenuItem = (option) => {
|
|
207
|
-
if (option === null) {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
const { setValue, getValue } = this;
|
|
211
|
-
let icon;
|
|
212
|
-
let caption;
|
|
213
|
-
let value;
|
|
214
|
-
if (typeof option === 'string') {
|
|
215
|
-
caption = value = option;
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
;
|
|
219
|
-
({ icon, caption, value } = option);
|
|
220
|
-
}
|
|
221
|
-
if (this.localized) {
|
|
222
|
-
caption = localize(caption);
|
|
223
|
-
}
|
|
224
|
-
const { options } = option;
|
|
225
|
-
if (options) {
|
|
226
|
-
return {
|
|
227
|
-
icon,
|
|
228
|
-
caption,
|
|
229
|
-
checked: () => hasValue(options, getValue()),
|
|
230
|
-
menuItems: options.map(this.buildOptionMenuItem),
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
return {
|
|
234
|
-
icon,
|
|
235
|
-
caption,
|
|
236
|
-
checked: () => getValue() === value,
|
|
237
|
-
action: typeof value === 'function'
|
|
238
|
-
? async () => {
|
|
239
|
-
const newValue = await value();
|
|
240
|
-
if (newValue !== undefined) {
|
|
241
|
-
setValue(newValue, true);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
: () => {
|
|
245
|
-
if (typeof value === 'string') {
|
|
246
|
-
setValue(value, true);
|
|
247
|
-
}
|
|
248
|
-
},
|
|
249
|
-
};
|
|
250
|
-
};
|
|
251
|
-
get optionsMenu() {
|
|
252
|
-
const options = this.selectOptions.map(this.buildOptionMenuItem);
|
|
253
|
-
if (this.filter === '') {
|
|
254
|
-
return options;
|
|
255
|
-
}
|
|
256
|
-
const showOption = (option) => {
|
|
257
|
-
if (option === null) {
|
|
258
|
-
return true;
|
|
259
|
-
}
|
|
260
|
-
else if (option.menuItems) {
|
|
261
|
-
;
|
|
262
|
-
option.menuItems = option.menuItems.filter(showOption);
|
|
263
|
-
return option.menuItems.length > 0;
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
return option.caption.toLocaleLowerCase().includes(this.filter);
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
return options.filter(showOption);
|
|
270
|
-
}
|
|
271
|
-
handleChange = (event) => {
|
|
272
|
-
const { value } = this.parts;
|
|
273
|
-
const newValue = value.value || '';
|
|
274
|
-
if (this.value !== String(newValue)) {
|
|
275
|
-
this.value = newValue;
|
|
276
|
-
this.dispatchEvent(new Event('change'));
|
|
277
|
-
}
|
|
278
|
-
this.filter = '';
|
|
279
|
-
event.stopPropagation();
|
|
280
|
-
event.preventDefault();
|
|
281
|
-
};
|
|
282
|
-
handleKey = (event) => {
|
|
283
|
-
if (event.key === 'Enter') {
|
|
284
|
-
event.preventDefault();
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
filterMenu = throttle(() => {
|
|
288
|
-
this.filter = this.parts.value.value.toLocaleLowerCase();
|
|
289
|
-
removeLastMenu(0);
|
|
290
|
-
this.popOptions();
|
|
291
|
-
});
|
|
292
|
-
popOptions = (event) => {
|
|
293
|
-
if (event && event.type === 'click') {
|
|
294
|
-
this.filter = '';
|
|
295
|
-
}
|
|
296
|
-
this.poppedOptions = this.optionsMenu;
|
|
297
|
-
popMenu({
|
|
298
|
-
target: this,
|
|
299
|
-
menuItems: this.poppedOptions,
|
|
300
|
-
});
|
|
301
|
-
};
|
|
302
|
-
content = () => [
|
|
303
|
-
button({
|
|
304
|
-
onClick: this.popOptions,
|
|
305
|
-
}, span(), input({
|
|
306
|
-
part: 'value',
|
|
307
|
-
value: this.value,
|
|
308
|
-
tabindex: 0,
|
|
309
|
-
onKeydown: this.handleKey,
|
|
310
|
-
onInput: this.filterMenu,
|
|
311
|
-
onChange: this.handleChange,
|
|
312
|
-
}), icons.chevronDown()),
|
|
313
|
-
];
|
|
314
|
-
constructor() {
|
|
315
|
-
super();
|
|
316
|
-
this.initAttributes('options', 'editable', 'placeholder', 'showIcon', 'hideCaption', 'localized');
|
|
317
|
-
}
|
|
318
|
-
get allOptions() {
|
|
319
|
-
const all = [];
|
|
320
|
-
function flatten(some) {
|
|
321
|
-
for (const option of some) {
|
|
322
|
-
if (typeof option === 'string') {
|
|
323
|
-
all.push({ caption: option, value: option });
|
|
324
|
-
}
|
|
325
|
-
else if (option?.value) {
|
|
326
|
-
all.push(option);
|
|
327
|
-
}
|
|
328
|
-
else if (option?.options) {
|
|
329
|
-
flatten(option.options);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
flatten(this.selectOptions);
|
|
334
|
-
return all;
|
|
335
|
-
}
|
|
336
|
-
findOption() {
|
|
337
|
-
const found = this.allOptions.find((option) => option.value === this.value);
|
|
338
|
-
return found || { caption: this.value, value: this.value };
|
|
339
|
-
}
|
|
340
|
-
localeChanged = () => {
|
|
341
|
-
this.queueRender();
|
|
342
|
-
};
|
|
343
|
-
connectedCallback() {
|
|
344
|
-
super.connectedCallback();
|
|
345
|
-
if (this.localized) {
|
|
346
|
-
XinLocalized.allInstances.add(this);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
disconnectedCallback() {
|
|
350
|
-
super.disconnectedCallback();
|
|
351
|
-
if (this.localized) {
|
|
352
|
-
XinLocalized.allInstances.delete(this);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
render() {
|
|
356
|
-
super.render();
|
|
357
|
-
const { value } = this.parts;
|
|
358
|
-
const icon = value.previousElementSibling;
|
|
359
|
-
const option = this.findOption();
|
|
360
|
-
let newIcon = span();
|
|
361
|
-
value.value = this.localized ? localize(option.caption) : option.caption;
|
|
362
|
-
if (option.icon) {
|
|
363
|
-
if (option.icon instanceof HTMLElement) {
|
|
364
|
-
newIcon = option.icon.cloneNode(true);
|
|
365
|
-
}
|
|
366
|
-
else {
|
|
367
|
-
newIcon = icons[option.icon]();
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
icon.replaceWith(newIcon);
|
|
371
|
-
value.setAttribute('placeholder', this.localized ? localize(this.placeholder) : this.placeholder);
|
|
372
|
-
value.style.pointerEvents = this.editable ? '' : 'none';
|
|
373
|
-
value.readOnly = !this.editable;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
export const xinSelect = XinSelect.elementCreator({
|
|
377
|
-
tag: 'xin-select',
|
|
378
|
-
styleSpec: {
|
|
379
|
-
':host': {
|
|
380
|
-
'--gap': '8px',
|
|
381
|
-
'--touch-size': '44px',
|
|
382
|
-
'--padding': '0 8px',
|
|
383
|
-
'--value-padding': '0 8px',
|
|
384
|
-
'--icon-width': '24px',
|
|
385
|
-
'--fieldWidth': '140px',
|
|
386
|
-
display: 'inline-block',
|
|
387
|
-
position: 'relative',
|
|
388
|
-
},
|
|
389
|
-
':host button': {
|
|
390
|
-
display: 'grid',
|
|
391
|
-
alignItems: 'center',
|
|
392
|
-
gap: vars.gap,
|
|
393
|
-
textAlign: 'left',
|
|
394
|
-
height: vars.touchSize,
|
|
395
|
-
padding: vars.padding,
|
|
396
|
-
gridTemplateColumns: `auto ${vars.iconWidth}`,
|
|
397
|
-
position: 'relative',
|
|
398
|
-
},
|
|
399
|
-
':host[show-icon] button': {
|
|
400
|
-
gridTemplateColumns: `${vars.iconWidth} auto ${vars.iconWidth}`,
|
|
401
|
-
},
|
|
402
|
-
':host[hide-caption] button': {
|
|
403
|
-
gridTemplateColumns: `${vars.iconWidth} ${vars.iconWidth}`,
|
|
404
|
-
},
|
|
405
|
-
':host:not([show-icon]) button > :first-child': {
|
|
406
|
-
display: 'none',
|
|
407
|
-
},
|
|
408
|
-
':host[hide-caption] button > :nth-child(2)': {
|
|
409
|
-
display: 'none',
|
|
410
|
-
},
|
|
411
|
-
':host [part="value"]': {
|
|
412
|
-
width: vars.fieldWidth,
|
|
413
|
-
padding: vars.valuePadding,
|
|
414
|
-
height: vars.touchSize,
|
|
415
|
-
lineHeight: vars.touchSize,
|
|
416
|
-
boxShadow: 'none',
|
|
417
|
-
whiteSpace: 'nowrap',
|
|
418
|
-
outline: 'none',
|
|
419
|
-
background: 'transparent',
|
|
420
|
-
},
|
|
421
|
-
':host [part="value"]:not(:focus)': {
|
|
422
|
-
overflow: 'hidden',
|
|
423
|
-
textOverflow: 'ellipsis',
|
|
424
|
-
background: 'transparent',
|
|
425
|
-
},
|
|
426
|
-
},
|
|
427
|
-
});
|
package/dist/side-nav.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/*#
|
|
2
|
-
# sidebar
|
|
3
|
-
|
|
4
|
-
The default layout for iOS / iPadOS apps is to hide the sidebar when displaying content on small
|
|
5
|
-
screens, and display the sidebar when space is available (with the user able to explicitly hide
|
|
6
|
-
the sidebar if so desired). `<xin-sidenav>` provides this functionality.
|
|
7
|
-
|
|
8
|
-
`<xin-sidenav>` is used to handle the layout of the documentation tab panel.
|
|
9
|
-
|
|
10
|
-
`<xin-sidenav>`'s behavior is controlled by two attributes, `minSize` is the point at which it will toggle between showing the navigation
|
|
11
|
-
sidebar and content, while `navSize` is the width of the sidebar. You can interrogate its `compact` property to find out if it's
|
|
12
|
-
currently in `compact` form.
|
|
13
|
-
*/
|
|
14
|
-
import { Component, elements, varDefault } from 'xinjs';
|
|
15
|
-
const { slot } = elements;
|
|
16
|
-
export class SideNav extends Component {
|
|
17
|
-
minSize = 800;
|
|
18
|
-
navSize = 200;
|
|
19
|
-
compact = false;
|
|
20
|
-
content = [slot({ name: 'nav', part: 'nav' }), slot({ part: 'content' })];
|
|
21
|
-
_contentVisible = false;
|
|
22
|
-
get contentVisible() {
|
|
23
|
-
return this._contentVisible;
|
|
24
|
-
}
|
|
25
|
-
set contentVisible(visible) {
|
|
26
|
-
this._contentVisible = visible;
|
|
27
|
-
this.queueRender();
|
|
28
|
-
}
|
|
29
|
-
static styleSpec = {
|
|
30
|
-
':host': {
|
|
31
|
-
display: 'grid',
|
|
32
|
-
gridTemplateColumns: `${varDefault.navWidth('50%')} ${varDefault.contentWidth('50%')}`,
|
|
33
|
-
gridTemplateRows: '100%',
|
|
34
|
-
position: 'relative',
|
|
35
|
-
margin: varDefault.margin('0 0 0 -100%'),
|
|
36
|
-
transition: varDefault.sideNavTransition('0.25s ease-out'),
|
|
37
|
-
},
|
|
38
|
-
':host slot': {
|
|
39
|
-
position: 'relative',
|
|
40
|
-
},
|
|
41
|
-
':host slot:not([name])': {
|
|
42
|
-
display: 'block',
|
|
43
|
-
},
|
|
44
|
-
':host slot[name="nav"]': {
|
|
45
|
-
display: 'block',
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
onResize = () => {
|
|
49
|
-
const { content } = this.parts;
|
|
50
|
-
const parent = this.offsetParent;
|
|
51
|
-
if (parent === null) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
this.compact = parent.offsetWidth < this.minSize;
|
|
55
|
-
const empty = [...this.childNodes].find((node) => node instanceof Element ? node.getAttribute('slot') !== 'nav' : true) === undefined;
|
|
56
|
-
if (empty) {
|
|
57
|
-
this.style.setProperty('--nav-width', '100%');
|
|
58
|
-
this.style.setProperty('--content-width', '0%');
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (!this.compact) {
|
|
62
|
-
content.classList.add('-xin-sidenav-visible');
|
|
63
|
-
this.style.setProperty('--nav-width', `${this.navSize}px`);
|
|
64
|
-
this.style.setProperty('--content-width', `calc(100% - ${this.navSize}px)`);
|
|
65
|
-
this.style.setProperty('--margin', '0');
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
content.classList.remove('-xin-sidenav-visible');
|
|
69
|
-
this.style.setProperty('--nav-width', '50%');
|
|
70
|
-
this.style.setProperty('--content-width', '50%');
|
|
71
|
-
if (this.contentVisible) {
|
|
72
|
-
this.style.setProperty('--margin', '0 0 0 -100%');
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
this.style.setProperty('--margin', '0 -100% 0 0');
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
observer;
|
|
80
|
-
connectedCallback() {
|
|
81
|
-
super.connectedCallback();
|
|
82
|
-
this.contentVisible = this.parts.content.childNodes.length === 0;
|
|
83
|
-
globalThis.addEventListener('resize', this.onResize);
|
|
84
|
-
this.observer = new MutationObserver(this.onResize);
|
|
85
|
-
this.observer.observe(this, { childList: true });
|
|
86
|
-
this.style.setProperty('--side-nav-transition', '0s');
|
|
87
|
-
setTimeout(() => {
|
|
88
|
-
this.style.removeProperty('--side-nav-transition');
|
|
89
|
-
}, 250);
|
|
90
|
-
}
|
|
91
|
-
disconnectedCallback() {
|
|
92
|
-
super.disconnectedCallback();
|
|
93
|
-
this.observer.disconnect();
|
|
94
|
-
}
|
|
95
|
-
constructor() {
|
|
96
|
-
super();
|
|
97
|
-
this.initAttributes('minSize', 'navSize', 'compact');
|
|
98
|
-
}
|
|
99
|
-
render() {
|
|
100
|
-
super.render();
|
|
101
|
-
this.onResize();
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
export const sideNav = SideNav.elementCreator({
|
|
105
|
-
tag: 'xin-sidenav',
|
|
106
|
-
});
|
package/dist/size-break.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { Component as WebComponent, elements } from 'xinjs';
|
|
2
|
-
const { slot } = elements;
|
|
3
|
-
/*#
|
|
4
|
-
# size-break
|
|
5
|
-
|
|
6
|
-
While we wait for enough browsers to implement [container-queries](https://www.w3.org/TR/css-contain-3/),
|
|
7
|
-
and in any event when you simply want to do different things at different sizes (e.g. in the project I'm
|
|
8
|
-
working on right now, a row of buttons turns into a menu at narrow widths) there's `<xin-sizebreak>`.
|
|
9
|
-
|
|
10
|
-
Note that the sizes referred to are of the `<xin-sizebreak>`'s `.offsetParent`, and it watches for
|
|
11
|
-
the window's `resize` events and its own (via `ResizeObserver`).
|
|
12
|
-
|
|
13
|
-
```html
|
|
14
|
-
<div class="container">
|
|
15
|
-
<xin-sizebreak min-width="300" min-height="150">
|
|
16
|
-
<h1>BIG!</h1>
|
|
17
|
-
<i slot="small">little</i>
|
|
18
|
-
</xin-sizebreak>
|
|
19
|
-
<xin-sizer></xin-sizer>
|
|
20
|
-
</div>
|
|
21
|
-
```
|
|
22
|
-
```css
|
|
23
|
-
.preview {
|
|
24
|
-
touch-action: none;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.preview xin-sizebreak {
|
|
28
|
-
width: 100%;
|
|
29
|
-
height: 100%;
|
|
30
|
-
background: #fff8;
|
|
31
|
-
border: 1px solid #aaa;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.preview xin-sizebreak * {
|
|
35
|
-
position: absolute;
|
|
36
|
-
top: 50%;
|
|
37
|
-
left: 50%;
|
|
38
|
-
transform: translateX(-50%) translateY(-50%);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.preview .container {
|
|
42
|
-
position: relative;
|
|
43
|
-
min-width: 100px;
|
|
44
|
-
min-height: 40px;
|
|
45
|
-
max-height: 100%;
|
|
46
|
-
width: 400px;
|
|
47
|
-
height: 100px;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.preview .sizer {
|
|
51
|
-
position: absolute;
|
|
52
|
-
width: 24px;
|
|
53
|
-
height: 24px;
|
|
54
|
-
line-height: 24px;
|
|
55
|
-
text-align: center;
|
|
56
|
-
background: #0002;
|
|
57
|
-
bottom: 0;
|
|
58
|
-
right: 0;
|
|
59
|
-
cursor: nwse-resize;
|
|
60
|
-
opacity: 0.5;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.preview .sizer:hover {
|
|
64
|
-
opacity: 1.0;
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
`<xin-sizebreak>` supports both `min-width` and/or `min-height`, and you can of course target only one
|
|
69
|
-
of the slots if you like. The demo site uses them to hide the [bundlejs](https://bundlejs.com/) badge when
|
|
70
|
-
space is tight.
|
|
71
|
-
*/
|
|
72
|
-
export class SizeBreak extends WebComponent {
|
|
73
|
-
minWidth = 0;
|
|
74
|
-
minHeight = 0;
|
|
75
|
-
value = 'normal';
|
|
76
|
-
content = [slot({ part: 'normal' }), slot({ part: 'small', name: 'small' })];
|
|
77
|
-
static styleSpec = {
|
|
78
|
-
':host': {
|
|
79
|
-
display: 'inline-block',
|
|
80
|
-
position: 'relative',
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
constructor() {
|
|
84
|
-
super();
|
|
85
|
-
this.initAttributes('minWidth', 'minHeight');
|
|
86
|
-
}
|
|
87
|
-
onResize = () => {
|
|
88
|
-
const { normal, small } = this.parts;
|
|
89
|
-
const parent = this.offsetParent;
|
|
90
|
-
if (!(parent instanceof HTMLElement)) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
else if (parent.offsetWidth < this.minWidth ||
|
|
94
|
-
parent.offsetHeight < this.minHeight) {
|
|
95
|
-
normal.hidden = true;
|
|
96
|
-
small.hidden = false;
|
|
97
|
-
this.value = 'small';
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
normal.hidden = false;
|
|
101
|
-
small.hidden = true;
|
|
102
|
-
this.value = 'normal';
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
// TODO trigger a resize event when an ancestor element
|
|
106
|
-
// is inserted or moved into the DOM.
|
|
107
|
-
connectedCallback() {
|
|
108
|
-
super.connectedCallback();
|
|
109
|
-
globalThis.addEventListener('resize', this.onResize);
|
|
110
|
-
}
|
|
111
|
-
disconnectedCallback() {
|
|
112
|
-
super.disconnectedCallback();
|
|
113
|
-
globalThis.removeEventListener('resize', this.onResize);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
export const sizeBreak = SizeBreak.elementCreator({
|
|
117
|
-
tag: 'xin-sizebreak',
|
|
118
|
-
});
|