tosijs-ui 1.0.0 → 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.d.ts +1 -1
- 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/tab-selector.js
DELETED
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
/*#
|
|
2
|
-
# tabs
|
|
3
|
-
|
|
4
|
-
`<xin-tabs>` creates a `tabpanel` for its children, creating a `tab` for each based on its
|
|
5
|
-
`name` attribute.
|
|
6
|
-
|
|
7
|
-
```js
|
|
8
|
-
[...preview.querySelectorAll('div[name]')].forEach(div => {
|
|
9
|
-
div.style.color = `hsl(${(Math.random() * 360).toFixed(0)} 50% 50%)`
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
const { div, button } = xinjs.elements
|
|
13
|
-
const tabSelector = preview.querySelector('xin-tabs')
|
|
14
|
-
|
|
15
|
-
tabSelector.onCloseTab = body => {
|
|
16
|
-
if (!confirm(`Are you sure you want to close the ${body.getAttribute('name')} tab?`)) {
|
|
17
|
-
return false
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
let bodycount = 0
|
|
22
|
-
preview.querySelector('.add').addEventListener('click', () => {
|
|
23
|
-
const name = `new tab ${++bodycount}`
|
|
24
|
-
const body = div(
|
|
25
|
-
{name, dataClose: true},
|
|
26
|
-
name,
|
|
27
|
-
)
|
|
28
|
-
tabSelector.addTabBody(body, true)
|
|
29
|
-
})
|
|
30
|
-
```
|
|
31
|
-
```html
|
|
32
|
-
<xin-tabs>
|
|
33
|
-
<div name="first">first body</div>
|
|
34
|
-
<div name="second" data-close>
|
|
35
|
-
<template role="tab">
|
|
36
|
-
<xin-icon
|
|
37
|
-
style="
|
|
38
|
-
display: inline-block;
|
|
39
|
-
width: 16px;
|
|
40
|
-
height: 16px;
|
|
41
|
-
transform: translateY(2px);
|
|
42
|
-
margin-right: 4px;
|
|
43
|
-
stroke: var(--brand-color);
|
|
44
|
-
"
|
|
45
|
-
icon="eye"
|
|
46
|
-
></xin-icon>
|
|
47
|
-
<span>Ooooh!!!</span>
|
|
48
|
-
</template>
|
|
49
|
-
look at the html…
|
|
50
|
-
</div>
|
|
51
|
-
<div name="third">third body</div>
|
|
52
|
-
<button class="add" slot="after-tabs">
|
|
53
|
-
<xin-icon icon="plus"></xin-icon>
|
|
54
|
-
</button>
|
|
55
|
-
</xin-tabs>
|
|
56
|
-
```
|
|
57
|
-
```css
|
|
58
|
-
.preview xin-tabs {
|
|
59
|
-
height: 100%;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.preview div[name] {
|
|
63
|
-
padding: 20px;
|
|
64
|
-
text-align: center;
|
|
65
|
-
height: 100%;
|
|
66
|
-
font-size: 200%;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.preview .add {
|
|
70
|
-
width: 38px;
|
|
71
|
-
line-height: 38px;
|
|
72
|
-
height: 38px;
|
|
73
|
-
padding: 0;
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
The `<xin-tabs>`s `value` is the index of its active body.
|
|
78
|
-
|
|
79
|
-
A `<xin-tabs>` has `addTabBody(body: HTMLElement, select?: boolean)` and
|
|
80
|
-
`removeTabBody(body: number | HTMLElement)` methods for updating its content.
|
|
81
|
-
|
|
82
|
-
You can also just insert or remove tab bodies directly and call `setupTabs()`.
|
|
83
|
-
|
|
84
|
-
## Closeable Tabs
|
|
85
|
-
|
|
86
|
-
Adding the `data-close` attribute to a tab will make it closeable.
|
|
87
|
-
|
|
88
|
-
When a tab is closed, the `<xin-tabs>` element's `onCloseTab: (tabBody: Element) => boolean | undefined | void`
|
|
89
|
-
will be called. If you override this method and return `false`, the tab will
|
|
90
|
-
not be closed (e.g. if you want to implement save/cancel behavior).
|
|
91
|
-
|
|
92
|
-
## Custom Tab Content
|
|
93
|
-
|
|
94
|
-
You can specify the exact content of the tab for a given body by
|
|
95
|
-
adding a `<template role="tab">` to that body. The contents of that
|
|
96
|
-
template will be cloned into the tab.
|
|
97
|
-
|
|
98
|
-
## Localized Support
|
|
99
|
-
|
|
100
|
-
```html
|
|
101
|
-
<xin-tabs localized>
|
|
102
|
-
<div name="localize"><h2>localize!</h2></div>
|
|
103
|
-
<div name="tabs"><h2>tabs</h2></div>
|
|
104
|
-
</xin-tabs>
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
`<xin-tabs>` supports the `localized` attribute. It will automatically localize
|
|
108
|
-
tab names (but it won't override custom tab content, so localizing that is on you).
|
|
109
|
-
*/
|
|
110
|
-
import { Component as WebComponent, elements, vars, } from 'xinjs';
|
|
111
|
-
import { xinLocalized, XinLocalized } from './localize';
|
|
112
|
-
import { icons } from '../src';
|
|
113
|
-
const { div, slot, span, button } = elements;
|
|
114
|
-
export class TabSelector extends WebComponent {
|
|
115
|
-
value = 0;
|
|
116
|
-
localized = false;
|
|
117
|
-
makeTab(tabs, tabBody, bodyId) {
|
|
118
|
-
const tabName = tabBody.getAttribute('name');
|
|
119
|
-
const tabContent = tabBody.querySelector('template[role="tab"]')?.content.cloneNode(true) ||
|
|
120
|
-
(this.localized ? xinLocalized(tabName) : span(tabName));
|
|
121
|
-
const tab = div(tabContent, {
|
|
122
|
-
part: 'tab',
|
|
123
|
-
tabindex: 0,
|
|
124
|
-
role: 'tab',
|
|
125
|
-
ariaControls: bodyId,
|
|
126
|
-
}, tabBody.hasAttribute('data-close')
|
|
127
|
-
? button({
|
|
128
|
-
title: 'close',
|
|
129
|
-
class: 'close',
|
|
130
|
-
}, icons.x())
|
|
131
|
-
: {});
|
|
132
|
-
return tab;
|
|
133
|
-
}
|
|
134
|
-
static styleSpec = {
|
|
135
|
-
':host': {
|
|
136
|
-
display: 'flex',
|
|
137
|
-
flexDirection: 'column',
|
|
138
|
-
position: 'relative',
|
|
139
|
-
overflow: 'hidden',
|
|
140
|
-
boxShadow: 'none !important',
|
|
141
|
-
},
|
|
142
|
-
slot: {
|
|
143
|
-
position: 'relative',
|
|
144
|
-
display: 'block',
|
|
145
|
-
flex: '1',
|
|
146
|
-
overflow: 'hidden',
|
|
147
|
-
overflowY: 'auto',
|
|
148
|
-
},
|
|
149
|
-
'slot[name="after-tabs"]': {
|
|
150
|
-
flex: '0 0 auto',
|
|
151
|
-
},
|
|
152
|
-
'::slotted([hidden])': {
|
|
153
|
-
display: 'none !important',
|
|
154
|
-
},
|
|
155
|
-
':host::part(tabpanel)': {
|
|
156
|
-
display: 'flex',
|
|
157
|
-
flexDirection: 'column',
|
|
158
|
-
overflowX: 'auto',
|
|
159
|
-
},
|
|
160
|
-
':host::part(tabrow)': {
|
|
161
|
-
display: 'flex',
|
|
162
|
-
},
|
|
163
|
-
':host .tabs': {
|
|
164
|
-
display: 'flex',
|
|
165
|
-
userSelect: 'none',
|
|
166
|
-
whiteSpace: 'nowrap',
|
|
167
|
-
},
|
|
168
|
-
':host .tabs > div': {
|
|
169
|
-
padding: `${vars.spacing50} ${vars.spacing}`,
|
|
170
|
-
cursor: 'default',
|
|
171
|
-
display: 'flex',
|
|
172
|
-
alignItems: 'baseline',
|
|
173
|
-
},
|
|
174
|
-
':host .tabs > [aria-selected="true"]': {
|
|
175
|
-
'--text-color': vars.xinTabsSelectedColor,
|
|
176
|
-
color: vars.textColor,
|
|
177
|
-
},
|
|
178
|
-
':host .elastic': {
|
|
179
|
-
flex: '1',
|
|
180
|
-
},
|
|
181
|
-
':host .border': {
|
|
182
|
-
background: 'var(--xin-tabs-bar-color, #ccc)',
|
|
183
|
-
},
|
|
184
|
-
':host .border > .selected': {
|
|
185
|
-
content: ' ',
|
|
186
|
-
width: 0,
|
|
187
|
-
height: 'var(--xin-tabs-bar-height, 2px)',
|
|
188
|
-
background: vars.xinTabsSelectedColor,
|
|
189
|
-
transition: 'ease-out 0.2s',
|
|
190
|
-
},
|
|
191
|
-
':host button.close': {
|
|
192
|
-
border: 0,
|
|
193
|
-
background: 'transparent',
|
|
194
|
-
textAlign: 'center',
|
|
195
|
-
marginLeft: vars.spacing50,
|
|
196
|
-
padding: 0,
|
|
197
|
-
},
|
|
198
|
-
':host button.close > svg': {
|
|
199
|
-
height: '12px',
|
|
200
|
-
},
|
|
201
|
-
};
|
|
202
|
-
onCloseTab = null;
|
|
203
|
-
content = [
|
|
204
|
-
div({ role: 'tabpanel', part: 'tabpanel' }, div({ part: 'tabrow' }, div({ class: 'tabs', part: 'tabs' }), div({ class: 'elastic' }), slot({ name: 'after-tabs' })), div({ class: 'border' }, div({ class: 'selected', part: 'selected' }))),
|
|
205
|
-
slot(),
|
|
206
|
-
];
|
|
207
|
-
constructor() {
|
|
208
|
-
super();
|
|
209
|
-
this.initAttributes('localized');
|
|
210
|
-
}
|
|
211
|
-
addTabBody(body, selectTab = false) {
|
|
212
|
-
if (!body.hasAttribute('name')) {
|
|
213
|
-
console.error('element has no name attribute', body);
|
|
214
|
-
throw new Error('element has no name attribute');
|
|
215
|
-
}
|
|
216
|
-
this.append(body);
|
|
217
|
-
this.setupTabs();
|
|
218
|
-
if (selectTab) {
|
|
219
|
-
this.value = this.bodies.length - 1;
|
|
220
|
-
}
|
|
221
|
-
this.queueRender();
|
|
222
|
-
}
|
|
223
|
-
removeTabBody(body) {
|
|
224
|
-
body.remove();
|
|
225
|
-
this.setupTabs();
|
|
226
|
-
this.queueRender();
|
|
227
|
-
}
|
|
228
|
-
keyTab = (event) => {
|
|
229
|
-
const { tabs } = this.parts;
|
|
230
|
-
const tabIndex = [...tabs.children].indexOf(event.target);
|
|
231
|
-
switch (event.key) {
|
|
232
|
-
case 'ArrowLeft':
|
|
233
|
-
this.value =
|
|
234
|
-
(tabIndex + Number(tabs.children.length) - 1) % tabs.children.length;
|
|
235
|
-
tabs.children[this.value].focus();
|
|
236
|
-
event.preventDefault();
|
|
237
|
-
break;
|
|
238
|
-
case 'ArrowRight':
|
|
239
|
-
this.value = (tabIndex + 1) % tabs.children.length;
|
|
240
|
-
tabs.children[this.value].focus();
|
|
241
|
-
event.preventDefault();
|
|
242
|
-
break;
|
|
243
|
-
case ' ':
|
|
244
|
-
this.pickTab(event);
|
|
245
|
-
event.preventDefault();
|
|
246
|
-
break;
|
|
247
|
-
default:
|
|
248
|
-
// console.log(event.key)
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
get bodies() {
|
|
252
|
-
return [...this.children].filter((elt) => elt.hasAttribute('name'));
|
|
253
|
-
}
|
|
254
|
-
pickTab = (event) => {
|
|
255
|
-
const { tabs } = this.parts;
|
|
256
|
-
const target = event.target;
|
|
257
|
-
const isCloseEvent = target.closest('button.close') !== null;
|
|
258
|
-
const tab = target.closest('.tabs > div');
|
|
259
|
-
const tabIndex = [...tabs.children].indexOf(tab);
|
|
260
|
-
if (isCloseEvent) {
|
|
261
|
-
const body = this.bodies[tabIndex];
|
|
262
|
-
if (!this.onCloseTab || this.onCloseTab(body) !== false) {
|
|
263
|
-
this.removeTabBody(this.bodies[tabIndex]);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
if (tabIndex > -1) {
|
|
268
|
-
this.value = tabIndex;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
setupTabs = () => {
|
|
273
|
-
const { tabs } = this.parts;
|
|
274
|
-
const tabBodies = [...this.children].filter((child) => !child.hasAttribute('slot') && child.hasAttribute('name'));
|
|
275
|
-
tabs.textContent = '';
|
|
276
|
-
if (this.value >= tabBodies.length) {
|
|
277
|
-
this.value = tabBodies.length - 1;
|
|
278
|
-
}
|
|
279
|
-
for (const index in tabBodies) {
|
|
280
|
-
const tabBody = tabBodies[index];
|
|
281
|
-
const bodyId = `${this.instanceId}-${index}`;
|
|
282
|
-
tabBody.id = bodyId;
|
|
283
|
-
const tab = this.makeTab(this, tabBody, bodyId);
|
|
284
|
-
tabs.append(tab);
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
connectedCallback() {
|
|
288
|
-
super.connectedCallback();
|
|
289
|
-
const { tabs } = this.parts;
|
|
290
|
-
tabs.addEventListener('click', this.pickTab);
|
|
291
|
-
tabs.addEventListener('keydown', this.keyTab);
|
|
292
|
-
this.setupTabs();
|
|
293
|
-
XinLocalized.allInstances.add(this);
|
|
294
|
-
}
|
|
295
|
-
disconnectedCallback() {
|
|
296
|
-
super.disconnectedCallback();
|
|
297
|
-
XinLocalized.allInstances.delete(this);
|
|
298
|
-
}
|
|
299
|
-
localeChanged = () => {
|
|
300
|
-
this.queueRender();
|
|
301
|
-
};
|
|
302
|
-
onResize() {
|
|
303
|
-
this.queueRender();
|
|
304
|
-
}
|
|
305
|
-
render() {
|
|
306
|
-
const { tabs, selected } = this.parts;
|
|
307
|
-
const tabBodies = this.bodies;
|
|
308
|
-
for (let i = 0; i < tabBodies.length; i++) {
|
|
309
|
-
const tabBody = tabBodies[i];
|
|
310
|
-
const tab = tabs.children[i];
|
|
311
|
-
if (this.value === Number(i)) {
|
|
312
|
-
tab.setAttribute('aria-selected', 'true');
|
|
313
|
-
selected.style.marginLeft = `${tab.offsetLeft - tabs.offsetLeft}px`;
|
|
314
|
-
selected.style.width = `${tab.offsetWidth}px`;
|
|
315
|
-
tabBody.toggleAttribute('hidden', false);
|
|
316
|
-
}
|
|
317
|
-
else {
|
|
318
|
-
tab.toggleAttribute('aria-selected', false);
|
|
319
|
-
tabBody.toggleAttribute('hidden', true);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
export const tabSelector = TabSelector.elementCreator({
|
|
325
|
-
tag: 'xin-tabs',
|
|
326
|
-
});
|
package/dist/tag-list.js
DELETED
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
/*#
|
|
2
|
-
# tag-list
|
|
3
|
-
|
|
4
|
-
Building a tag-list from standard HTML elements is a bit of a nightmare.
|
|
5
|
-
|
|
6
|
-
`<xin-tag-list>` allows you to display an editable or read-only tag list (represented either
|
|
7
|
-
as a comma-delimited string or an array of strings).
|
|
8
|
-
|
|
9
|
-
```html
|
|
10
|
-
<label style="position: absolute; right: 10px; top: 10px; display: block">
|
|
11
|
-
<input type="checkbox" class="disable-toggle">
|
|
12
|
-
<b>Disable All</b>
|
|
13
|
-
</label>
|
|
14
|
-
<label>
|
|
15
|
-
<b>Display Only</b>
|
|
16
|
-
<xin-tag-list
|
|
17
|
-
value="this,that,,the-other"
|
|
18
|
-
></xin-tag-list>
|
|
19
|
-
</label>
|
|
20
|
-
<xin-tag-list
|
|
21
|
-
class="compact"
|
|
22
|
-
value="this,that,,the-other"
|
|
23
|
-
></xin-tag-list>
|
|
24
|
-
<br>
|
|
25
|
-
<label>
|
|
26
|
-
<b>Editable</b>
|
|
27
|
-
<xin-tag-list
|
|
28
|
-
class="editable-tag-list"
|
|
29
|
-
value="belongs,also belongs,custom"
|
|
30
|
-
editable
|
|
31
|
-
available-tags="belongs,also belongs,not initially chosen"
|
|
32
|
-
></xin-tag-list>
|
|
33
|
-
</label>
|
|
34
|
-
<br>
|
|
35
|
-
<b>Text-Entry</b>
|
|
36
|
-
<xin-tag-list
|
|
37
|
-
value="this,that,the-other,not,enough,space"
|
|
38
|
-
editable
|
|
39
|
-
text-entry
|
|
40
|
-
available-tags="tomasina,dick,,harriet"
|
|
41
|
-
></xin-tag-list>
|
|
42
|
-
```
|
|
43
|
-
```css
|
|
44
|
-
.preview .compact {
|
|
45
|
-
--spacing: 8px;
|
|
46
|
-
--font-size: 12px;
|
|
47
|
-
--line-height: 18px;
|
|
48
|
-
}
|
|
49
|
-
.preview label {
|
|
50
|
-
display: flex;
|
|
51
|
-
flex-direction: column;
|
|
52
|
-
align-items: flex-start;
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
```js
|
|
56
|
-
preview.addEventListener('change', (event) => {
|
|
57
|
-
if (event.target.matches('xin-tag-list')) {
|
|
58
|
-
console.log(event.target, event.target.value)
|
|
59
|
-
}
|
|
60
|
-
}, true)
|
|
61
|
-
preview.querySelector('.disable-toggle').addEventListener('change', (event) => {
|
|
62
|
-
const tagLists = Array.from(preview.querySelectorAll('xin-tag-list'))
|
|
63
|
-
for(const tagList of tagLists) {
|
|
64
|
-
tagList.disabled = event.target.checked
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Properties
|
|
70
|
-
|
|
71
|
-
### `value`: string | string[]
|
|
72
|
-
|
|
73
|
-
A list of tags
|
|
74
|
-
|
|
75
|
-
### `tags`: string[]
|
|
76
|
-
|
|
77
|
-
## `popSelectMenu`: () => void
|
|
78
|
-
|
|
79
|
-
This is the method called when the user clicks the menu button. By default is displays a
|
|
80
|
-
pick list of tags, but if you wish to customize the behavior, just replace this method.
|
|
81
|
-
|
|
82
|
-
A read-only property giving the value as an array.
|
|
83
|
-
|
|
84
|
-
### `available-tags`: string | string[]
|
|
85
|
-
|
|
86
|
-
A list of tags that will be displayed in the popup menu by default. The popup menu
|
|
87
|
-
will always display custom tags (allowing their removal).
|
|
88
|
-
|
|
89
|
-
### `editable`: boolean
|
|
90
|
-
|
|
91
|
-
Allows the tag list to be modified via menu and removing tags.
|
|
92
|
-
|
|
93
|
-
### `text-entry`: boolean
|
|
94
|
-
|
|
95
|
-
If `editable`, an input field is provided for entering tags directly.
|
|
96
|
-
|
|
97
|
-
### `placeholder`: string = 'enter tags'
|
|
98
|
-
|
|
99
|
-
Placeholder shown on input field.
|
|
100
|
-
*/
|
|
101
|
-
import { Component as WebComponent, elements, vars, varDefault, } from 'xinjs';
|
|
102
|
-
import { popMenu } from './menu';
|
|
103
|
-
import { icons } from './icons';
|
|
104
|
-
const { div, input, span, button } = elements;
|
|
105
|
-
export class XinTag extends WebComponent {
|
|
106
|
-
caption = '';
|
|
107
|
-
removeable = false;
|
|
108
|
-
removeCallback = () => {
|
|
109
|
-
this.remove();
|
|
110
|
-
};
|
|
111
|
-
content = () => [
|
|
112
|
-
span({ part: 'caption' }, this.caption),
|
|
113
|
-
button(icons.x(), {
|
|
114
|
-
part: 'remove',
|
|
115
|
-
hidden: !this.removeable,
|
|
116
|
-
onClick: this.removeCallback,
|
|
117
|
-
}),
|
|
118
|
-
];
|
|
119
|
-
constructor() {
|
|
120
|
-
super();
|
|
121
|
-
this.initAttributes('caption', 'removeable');
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
export const xinTag = XinTag.elementCreator({
|
|
125
|
-
tag: 'xin-tag',
|
|
126
|
-
styleSpec: {
|
|
127
|
-
':host': {
|
|
128
|
-
'--tag-close-button-color': '#000c',
|
|
129
|
-
'--tag-close-button-bg': '#fffc',
|
|
130
|
-
'--tag-button-opacity': '0.5',
|
|
131
|
-
'--tag-button-hover-opacity': '0.75',
|
|
132
|
-
'--tag-bg': varDefault.brandColor('blue'),
|
|
133
|
-
'--tag-text-color': varDefault.brandTextColor('white'),
|
|
134
|
-
display: 'inline-flex',
|
|
135
|
-
borderRadius: varDefault.tagRoundedRadius(vars.spacing50),
|
|
136
|
-
color: vars.tagTextColor,
|
|
137
|
-
background: vars.tagBg,
|
|
138
|
-
padding: `0 ${vars.spacing75} 0 ${vars.spacing75}`,
|
|
139
|
-
height: `calc(${vars.lineHeight} + ${vars.spacing50})`,
|
|
140
|
-
lineHeight: `calc(${vars.lineHeight} + ${vars.spacing50})`,
|
|
141
|
-
},
|
|
142
|
-
':host > [part="caption"]': {
|
|
143
|
-
position: 'relative',
|
|
144
|
-
whiteSpace: 'nowrap',
|
|
145
|
-
overflow: 'hidden',
|
|
146
|
-
flex: '1 1 auto',
|
|
147
|
-
fontSize: varDefault.fontSize('16px'),
|
|
148
|
-
color: vars.tagTextColor,
|
|
149
|
-
textOverflow: 'ellipsis',
|
|
150
|
-
},
|
|
151
|
-
':host [part="remove"]': {
|
|
152
|
-
boxShadow: 'none',
|
|
153
|
-
margin: `0 ${vars.spacing_50} 0 ${vars.spacing25}`,
|
|
154
|
-
padding: 0,
|
|
155
|
-
display: 'inline-flex',
|
|
156
|
-
alignItems: 'center',
|
|
157
|
-
alignSelf: 'center',
|
|
158
|
-
justifyContent: 'center',
|
|
159
|
-
height: vars.spacing150,
|
|
160
|
-
width: vars.spacing150,
|
|
161
|
-
'--text-color': vars.tagCloseButtonColor,
|
|
162
|
-
background: vars.tagCloseButtonBg,
|
|
163
|
-
borderRadius: varDefault.tagCloseButtonRadius('99px'),
|
|
164
|
-
opacity: vars.tagButtonOpacity,
|
|
165
|
-
},
|
|
166
|
-
':host [part="remove"]:hover': {
|
|
167
|
-
background: vars.tagCloseButtonBg,
|
|
168
|
-
opacity: vars.tagButtonHoverOpacity,
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
});
|
|
172
|
-
export class XinTagList extends WebComponent {
|
|
173
|
-
disabled = false;
|
|
174
|
-
name = '';
|
|
175
|
-
availableTags = [];
|
|
176
|
-
value = [];
|
|
177
|
-
textEntry = false;
|
|
178
|
-
editable = false;
|
|
179
|
-
placeholder = 'enter tags';
|
|
180
|
-
get tags() {
|
|
181
|
-
return typeof this.value === 'string'
|
|
182
|
-
? this.value
|
|
183
|
-
.split(',')
|
|
184
|
-
.map((tag) => tag.trim())
|
|
185
|
-
.filter((tag) => tag !== '')
|
|
186
|
-
: this.value;
|
|
187
|
-
}
|
|
188
|
-
constructor() {
|
|
189
|
-
super();
|
|
190
|
-
this.initAttributes('name', 'value', 'textEntry', 'availableTags', 'editable', 'placeholder', 'disabled');
|
|
191
|
-
}
|
|
192
|
-
addTag = (tag) => {
|
|
193
|
-
if (tag.trim() === '') {
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
const { tags } = this;
|
|
197
|
-
if (!tags.includes(tag)) {
|
|
198
|
-
tags.push(tag);
|
|
199
|
-
}
|
|
200
|
-
this.value = tags;
|
|
201
|
-
this.queueRender(true);
|
|
202
|
-
};
|
|
203
|
-
toggleTag = (toggled) => {
|
|
204
|
-
if (this.tags.includes(toggled)) {
|
|
205
|
-
this.value = this.tags.filter((tag) => tag !== toggled);
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
this.addTag(toggled);
|
|
209
|
-
}
|
|
210
|
-
this.queueRender(true);
|
|
211
|
-
};
|
|
212
|
-
enterTag = (event) => {
|
|
213
|
-
const { tagInput } = this.parts;
|
|
214
|
-
switch (event.key) {
|
|
215
|
-
case ',':
|
|
216
|
-
{
|
|
217
|
-
const tag = tagInput.value.split(',')[0];
|
|
218
|
-
this.addTag(tag);
|
|
219
|
-
}
|
|
220
|
-
break;
|
|
221
|
-
case 'Enter':
|
|
222
|
-
{
|
|
223
|
-
const tag = tagInput.value.split(',')[0];
|
|
224
|
-
this.addTag(tag);
|
|
225
|
-
}
|
|
226
|
-
event.stopPropagation();
|
|
227
|
-
event.preventDefault();
|
|
228
|
-
break;
|
|
229
|
-
default:
|
|
230
|
-
// do nothing
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
popSelectMenu = () => {
|
|
234
|
-
const { toggleTag } = this;
|
|
235
|
-
const { tagMenu } = this.parts;
|
|
236
|
-
const tags = typeof this.availableTags === 'string'
|
|
237
|
-
? this.availableTags.split(',')
|
|
238
|
-
: this.availableTags;
|
|
239
|
-
const extraTags = this.tags.filter((tag) => !tags.includes(tag));
|
|
240
|
-
if (extraTags.length) {
|
|
241
|
-
tags.push(null, ...extraTags);
|
|
242
|
-
}
|
|
243
|
-
const menuItems = tags.map((tag) => {
|
|
244
|
-
if (tag === '' || tag === null) {
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
else if (typeof tag === 'object') {
|
|
248
|
-
return {
|
|
249
|
-
checked: () => this.tags.includes(tag.value),
|
|
250
|
-
caption: tag.caption,
|
|
251
|
-
action() {
|
|
252
|
-
toggleTag(tag.value);
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
return {
|
|
258
|
-
checked: () => this.tags.includes(tag),
|
|
259
|
-
caption: tag,
|
|
260
|
-
action() {
|
|
261
|
-
toggleTag(tag);
|
|
262
|
-
},
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
popMenu({
|
|
267
|
-
target: tagMenu,
|
|
268
|
-
width: 'auto',
|
|
269
|
-
menuItems,
|
|
270
|
-
});
|
|
271
|
-
};
|
|
272
|
-
content = () => [
|
|
273
|
-
// this button is simply here to eat click events sent via a label
|
|
274
|
-
button({ style: { visibility: 'hidden' }, tabindex: -1 }),
|
|
275
|
-
div({
|
|
276
|
-
part: 'tagContainer',
|
|
277
|
-
class: 'row',
|
|
278
|
-
}),
|
|
279
|
-
input({
|
|
280
|
-
part: 'tagInput',
|
|
281
|
-
class: 'elastic',
|
|
282
|
-
onKeydown: this.enterTag,
|
|
283
|
-
}),
|
|
284
|
-
button({
|
|
285
|
-
title: 'add tag',
|
|
286
|
-
part: 'tagMenu',
|
|
287
|
-
onClick: this.popSelectMenu,
|
|
288
|
-
}, icons.chevronDown()),
|
|
289
|
-
];
|
|
290
|
-
removeTag = (event) => {
|
|
291
|
-
if (this.editable && !this.disabled) {
|
|
292
|
-
const tag = event.target.closest(XinTag.tagName);
|
|
293
|
-
this.value = this.tags.filter((value) => value !== tag.caption);
|
|
294
|
-
tag.remove();
|
|
295
|
-
this.queueRender(true);
|
|
296
|
-
}
|
|
297
|
-
event.stopPropagation();
|
|
298
|
-
event.preventDefault();
|
|
299
|
-
};
|
|
300
|
-
render() {
|
|
301
|
-
super.render();
|
|
302
|
-
const { tagContainer, tagMenu, tagInput } = this.parts;
|
|
303
|
-
tagMenu.disabled = this.disabled;
|
|
304
|
-
tagInput.value = '';
|
|
305
|
-
tagInput.setAttribute('placeholder', this.placeholder);
|
|
306
|
-
if (this.editable && !this.disabled) {
|
|
307
|
-
tagMenu.toggleAttribute('hidden', false);
|
|
308
|
-
tagInput.toggleAttribute('hidden', !this.textEntry);
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
tagMenu.toggleAttribute('hidden', true);
|
|
312
|
-
tagInput.toggleAttribute('hidden', true);
|
|
313
|
-
}
|
|
314
|
-
tagContainer.textContent = '';
|
|
315
|
-
const { tags } = this;
|
|
316
|
-
for (const tag of tags) {
|
|
317
|
-
tagContainer.append(xinTag({
|
|
318
|
-
caption: tag,
|
|
319
|
-
removeable: this.editable && !this.disabled,
|
|
320
|
-
removeCallback: this.removeTag,
|
|
321
|
-
}));
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
export const xinTagList = XinTagList.elementCreator({
|
|
326
|
-
tag: 'xin-tag-list',
|
|
327
|
-
styleSpec: {
|
|
328
|
-
':host': {
|
|
329
|
-
'--tag-list-bg': '#f8f8f8',
|
|
330
|
-
'--touch-size': '44px',
|
|
331
|
-
'--spacing': '16px',
|
|
332
|
-
display: 'grid',
|
|
333
|
-
gridTemplateColumns: 'auto',
|
|
334
|
-
alignItems: 'center',
|
|
335
|
-
background: vars.tagListBg,
|
|
336
|
-
gap: vars.spacing25,
|
|
337
|
-
borderRadius: varDefault.taglistRoundedRadius(vars.spacing50),
|
|
338
|
-
overflow: 'hidden',
|
|
339
|
-
},
|
|
340
|
-
':host[editable]': {
|
|
341
|
-
gridTemplateColumns: `0px auto ${vars.touchSize}`,
|
|
342
|
-
},
|
|
343
|
-
':host[editable][text-entry]': {
|
|
344
|
-
gridTemplateColumns: `0px 2fr 1fr ${vars.touchSize}`,
|
|
345
|
-
},
|
|
346
|
-
':host [part="tagContainer"]': {
|
|
347
|
-
display: 'flex',
|
|
348
|
-
content: '" "',
|
|
349
|
-
alignItems: 'center',
|
|
350
|
-
background: vars.inputBg,
|
|
351
|
-
borderRadius: varDefault.tagContainerRadius(vars.spacing50),
|
|
352
|
-
boxShadow: vars.borderShadow,
|
|
353
|
-
flexWrap: 'nowrap',
|
|
354
|
-
overflow: 'auto hidden',
|
|
355
|
-
gap: vars.spacing25,
|
|
356
|
-
minHeight: `calc(${vars.lineHeight} + ${vars.spacing})`,
|
|
357
|
-
padding: vars.spacing25,
|
|
358
|
-
},
|
|
359
|
-
':host [part="tagMenu"]': {
|
|
360
|
-
width: vars.touchSize,
|
|
361
|
-
height: vars.touchSize,
|
|
362
|
-
lineHeight: vars.touchSize,
|
|
363
|
-
textAlign: 'center',
|
|
364
|
-
padding: 0,
|
|
365
|
-
margin: 0,
|
|
366
|
-
},
|
|
367
|
-
':host [hidden]': {
|
|
368
|
-
display: 'none !important',
|
|
369
|
-
},
|
|
370
|
-
':host button[part="tagMenu"]': {
|
|
371
|
-
background: vars.brandColor,
|
|
372
|
-
color: vars.brandTextColor,
|
|
373
|
-
},
|
|
374
|
-
},
|
|
375
|
-
});
|