tosijs-ui 1.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/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/ab-test.d.ts +14 -0
- package/dist/ab-test.js +116 -0
- package/dist/babylon-3d.d.ts +53 -0
- package/dist/babylon-3d.js +292 -0
- package/dist/bodymovin-player.d.ts +32 -0
- package/dist/bodymovin-player.js +172 -0
- package/dist/bp-loader.d.ts +1 -0
- package/dist/bp-loader.js +26 -0
- package/dist/carousel.d.ts +113 -0
- package/dist/carousel.js +308 -0
- package/dist/code-editor.d.ts +27 -0
- package/dist/code-editor.js +102 -0
- package/dist/color-input.d.ts +41 -0
- package/dist/color-input.js +112 -0
- package/dist/data-table.d.ts +79 -0
- package/dist/data-table.js +774 -0
- package/dist/drag-and-drop.d.ts +2 -0
- package/dist/drag-and-drop.js +386 -0
- package/dist/editable-rect.d.ts +97 -0
- package/dist/editable-rect.js +450 -0
- package/dist/filter-builder.d.ts +64 -0
- package/dist/filter-builder.js +468 -0
- package/dist/float.d.ts +18 -0
- package/dist/float.js +170 -0
- package/dist/form.d.ts +68 -0
- package/dist/form.js +466 -0
- package/dist/gamepad.d.ts +34 -0
- package/dist/gamepad.js +115 -0
- package/dist/icon-data.d.ts +312 -0
- package/dist/icon-data.js +308 -0
- package/dist/icon-types.d.ts +7 -0
- package/dist/icon-types.js +1 -0
- package/dist/icons.d.ts +17 -0
- package/dist/icons.js +374 -0
- package/dist/iife.js +69 -0
- package/dist/iife.js.map +49 -0
- package/dist/index-iife.d.ts +1 -0
- package/dist/index-iife.js +4 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +47 -0
- package/dist/live-example.d.ts +63 -0
- package/dist/live-example.js +611 -0
- package/dist/localize.d.ts +46 -0
- package/dist/localize.js +381 -0
- package/dist/make-sorter.d.ts +3 -0
- package/dist/make-sorter.js +119 -0
- package/dist/make-sorter.test.d.ts +1 -0
- package/dist/make-sorter.test.js +48 -0
- package/dist/mapbox.d.ts +24 -0
- package/dist/mapbox.js +161 -0
- package/dist/markdown-viewer.d.ts +17 -0
- package/dist/markdown-viewer.js +173 -0
- package/dist/match-shortcut.d.ts +9 -0
- package/dist/match-shortcut.js +13 -0
- package/dist/match-shortcut.test.d.ts +1 -0
- package/dist/match-shortcut.test.js +194 -0
- package/dist/menu.d.ts +60 -0
- package/dist/menu.js +614 -0
- package/dist/notifications.d.ts +106 -0
- package/dist/notifications.js +308 -0
- package/dist/password-strength.d.ts +35 -0
- package/dist/password-strength.js +302 -0
- package/dist/playwright.config.d.ts +9 -0
- package/dist/playwright.config.js +73 -0
- package/dist/pop-float.d.ts +10 -0
- package/dist/pop-float.js +231 -0
- package/dist/rating.d.ts +62 -0
- package/dist/rating.js +192 -0
- package/dist/rich-text.d.ts +35 -0
- package/dist/rich-text.js +296 -0
- package/dist/segmented.d.ts +80 -0
- package/dist/segmented.js +298 -0
- package/dist/select.d.ts +43 -0
- package/dist/select.js +427 -0
- package/dist/side-nav.d.ts +36 -0
- package/dist/side-nav.js +106 -0
- package/dist/size-break.d.ts +18 -0
- package/dist/size-break.js +118 -0
- package/dist/sizer.d.ts +34 -0
- package/dist/sizer.js +92 -0
- package/dist/src/ab-test.d.ts +14 -0
- package/dist/src/babylon-3d.d.ts +53 -0
- package/dist/src/bodymovin-player.d.ts +32 -0
- package/dist/src/bp-loader.d.ts +0 -0
- package/dist/src/carousel.d.ts +113 -0
- package/dist/src/code-editor.d.ts +27 -0
- package/dist/src/color-input.d.ts +41 -0
- package/dist/src/data-table.d.ts +79 -0
- package/dist/src/drag-and-drop.d.ts +2 -0
- package/dist/src/editable-rect.d.ts +97 -0
- package/dist/src/filter-builder.d.ts +64 -0
- package/dist/src/float.d.ts +18 -0
- package/dist/src/form.d.ts +68 -0
- package/dist/src/gamepad.d.ts +34 -0
- package/dist/src/icon-data.d.ts +309 -0
- package/dist/src/icon-types.d.ts +7 -0
- package/dist/src/icons.d.ts +17 -0
- package/dist/src/index.d.ts +37 -0
- package/dist/src/live-example.d.ts +51 -0
- package/dist/src/localize.d.ts +30 -0
- package/dist/src/make-sorter.d.ts +3 -0
- package/dist/src/mapbox.d.ts +24 -0
- package/dist/src/markdown-viewer.d.ts +15 -0
- package/dist/src/match-shortcut.d.ts +9 -0
- package/dist/src/menu.d.ts +60 -0
- package/dist/src/notifications.d.ts +106 -0
- package/dist/src/password-strength.d.ts +35 -0
- package/dist/src/pop-float.d.ts +10 -0
- package/dist/src/rating.d.ts +62 -0
- package/dist/src/rich-text.d.ts +28 -0
- package/dist/src/segmented.d.ts +80 -0
- package/dist/src/select.d.ts +43 -0
- package/dist/src/side-nav.d.ts +36 -0
- package/dist/src/size-break.d.ts +18 -0
- package/dist/src/sizer.d.ts +34 -0
- package/dist/src/tab-selector.d.ts +91 -0
- package/dist/src/tag-list.d.ts +37 -0
- package/dist/src/track-drag.d.ts +5 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/via-tag.d.ts +2 -0
- package/dist/tab-selector.d.ts +91 -0
- package/dist/tab-selector.js +326 -0
- package/dist/tag-list.d.ts +37 -0
- package/dist/tag-list.js +375 -0
- package/dist/track-drag.d.ts +5 -0
- package/dist/track-drag.js +143 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/dist/via-tag.d.ts +2 -0
- package/dist/via-tag.js +102 -0
- package/package.json +58 -0
package/dist/tag-list.js
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
type TrackerCallback = (dx: number, dy: number, event: PointerEvent) => boolean | undefined;
|
|
2
|
+
export declare const trackDrag: (event: PointerEvent, callback: TrackerCallback, cursor?: string) => void;
|
|
3
|
+
export declare const findHighestZ: (selector?: string) => number;
|
|
4
|
+
export declare const bringToFront: (element: HTMLElement, selector?: string) => void;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { elements } from 'xinjs';
|
|
2
|
+
/*#
|
|
3
|
+
# trackDrag
|
|
4
|
+
|
|
5
|
+
Sometimes you want to track a mouse-drag or touch-drag operation without messing around.
|
|
6
|
+
This is how the resizeable columns in `<xin-table>` work.
|
|
7
|
+
|
|
8
|
+
Just call `trackDrag(event, (dx, dy, event) => { ... })` and you'll get updates on corresponding events until
|
|
9
|
+
you return `true` from the event-handler (or, in the case of `touch` events, the last `touch` ends).
|
|
10
|
+
For mouse events, a "tracker" element is thrown up in front of everything for the event.
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<p>
|
|
14
|
+
Try dragging the squares…<br>
|
|
15
|
+
(You can drag them separately with multi-touch!)
|
|
16
|
+
</p>
|
|
17
|
+
<div class="draggable" style="top: 20px; left: 40px; background: #f008"></div>
|
|
18
|
+
<div class="draggable" style="left: 40%; bottom: 30%; background: #0f08"></div>
|
|
19
|
+
<div class="draggable" style="bottom: 30px; right: 10px; background: #00f8"></div>
|
|
20
|
+
```
|
|
21
|
+
```css
|
|
22
|
+
.preview {
|
|
23
|
+
touch-action: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.draggable {
|
|
27
|
+
content: ' ';
|
|
28
|
+
position: absolute;
|
|
29
|
+
width: 50px;
|
|
30
|
+
height: 50px;
|
|
31
|
+
cursor: move;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.preview p {
|
|
35
|
+
position: absolute;
|
|
36
|
+
left: 50%;
|
|
37
|
+
top: 50%;
|
|
38
|
+
transform: translateX(-50%) translateY(-50%);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
```js
|
|
42
|
+
const { trackDrag } = xinjsui
|
|
43
|
+
|
|
44
|
+
function dragItem(event) {
|
|
45
|
+
const draggable = event.target
|
|
46
|
+
if (draggable.classList.contains('draggable')) {
|
|
47
|
+
const x = draggable.offsetLeft
|
|
48
|
+
const y = draggable.offsetTop
|
|
49
|
+
trackDrag(event, (dx, dy, event) => {
|
|
50
|
+
draggable.style.left = (x + dx) + 'px'
|
|
51
|
+
draggable.style.top = (y + dy) + 'px'
|
|
52
|
+
draggable.style.bottom = 'auto'
|
|
53
|
+
draggable.style.right = 'auto'
|
|
54
|
+
return event.type === 'mouseup'
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
preview.addEventListener('mousedown', dragItem )
|
|
60
|
+
preview.addEventListener('touchstart', dragItem, { passive: true } )
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For `touch` events, `dx` and `dy` are based on tracking `event.changedTouches[0]` which
|
|
64
|
+
is almost certainly what you want.
|
|
65
|
+
|
|
66
|
+
To handle multi-touch gestures you will need to track the touches yourself.
|
|
67
|
+
|
|
68
|
+
## bringToFront
|
|
69
|
+
|
|
70
|
+
`bringToFront(element: HTMLElement, selector = 'body *')` gives the element the highest
|
|
71
|
+
`z-index` of any element matching the selector (which is passed to findHighestZ).
|
|
72
|
+
|
|
73
|
+
## findHighestZ
|
|
74
|
+
|
|
75
|
+
`findHighestZ(selector = 'body *'): number` returns the the highest `z-index` of any element
|
|
76
|
+
matching `selector`.
|
|
77
|
+
*/
|
|
78
|
+
const TRACKER = elements.div({
|
|
79
|
+
style: {
|
|
80
|
+
content: ' ',
|
|
81
|
+
position: 'fixed',
|
|
82
|
+
top: 0,
|
|
83
|
+
left: 0,
|
|
84
|
+
right: 0,
|
|
85
|
+
bottom: 0,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const PASSIVE = { passive: true };
|
|
89
|
+
export const trackDrag = (event, callback, cursor = 'move') => {
|
|
90
|
+
const isTouchEvent = event.type.startsWith('touch');
|
|
91
|
+
if (!isTouchEvent) {
|
|
92
|
+
const origX = event.clientX;
|
|
93
|
+
const origY = event.clientY;
|
|
94
|
+
TRACKER.style.cursor = cursor;
|
|
95
|
+
bringToFront(TRACKER);
|
|
96
|
+
document.body.append(TRACKER);
|
|
97
|
+
const wrappedCallback = (event) => {
|
|
98
|
+
const dx = event.clientX - origX;
|
|
99
|
+
const dy = event.clientY - origY;
|
|
100
|
+
if (callback(dx, dy, event) === true) {
|
|
101
|
+
TRACKER.removeEventListener('mousemove', wrappedCallback);
|
|
102
|
+
TRACKER.removeEventListener('mouseup', wrappedCallback);
|
|
103
|
+
TRACKER.remove();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
TRACKER.addEventListener('mousemove', wrappedCallback, PASSIVE);
|
|
107
|
+
TRACKER.addEventListener('mouseup', wrappedCallback, PASSIVE);
|
|
108
|
+
}
|
|
109
|
+
else if (event instanceof TouchEvent) {
|
|
110
|
+
const touch = event.changedTouches[0];
|
|
111
|
+
const touchId = touch.identifier;
|
|
112
|
+
const origX = touch.clientX;
|
|
113
|
+
const origY = touch.clientY;
|
|
114
|
+
const target = event.target;
|
|
115
|
+
let dx = 0;
|
|
116
|
+
let dy = 0;
|
|
117
|
+
const wrappedCallback = (event) => {
|
|
118
|
+
const touch = [...event.touches].find((touch) => touch.identifier === touchId);
|
|
119
|
+
if (touch !== undefined) {
|
|
120
|
+
dx = touch.clientX - origX;
|
|
121
|
+
dy = touch.clientY - origY;
|
|
122
|
+
}
|
|
123
|
+
if (event.type === 'touchmove') {
|
|
124
|
+
event.stopPropagation();
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
}
|
|
127
|
+
if (callback(dx, dy, event) === true || touch === undefined) {
|
|
128
|
+
target.removeEventListener('touchmove', wrappedCallback);
|
|
129
|
+
target.removeEventListener('touchend', wrappedCallback);
|
|
130
|
+
target.removeEventListener('touchcancel', wrappedCallback);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
target.addEventListener('touchmove', wrappedCallback);
|
|
134
|
+
target.addEventListener('touchend', wrappedCallback, PASSIVE);
|
|
135
|
+
target.addEventListener('touchcancel', wrappedCallback, PASSIVE);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
export const findHighestZ = (selector = 'body *') => [...document.querySelectorAll(selector)]
|
|
139
|
+
.map((elt) => parseFloat(getComputedStyle(elt).zIndex))
|
|
140
|
+
.reduce((z, highest) => (isNaN(z) || Number(z) < highest ? highest : Number(z)), 0);
|
|
141
|
+
export const bringToFront = (element, selector = 'body *') => {
|
|
142
|
+
element.style.zIndex = String(findHighestZ(selector) + 1);
|
|
143
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const version = "1.0.0";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const version = '0.9.15';
|
package/dist/via-tag.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/*#
|
|
2
|
+
# scriptTag & styleSheet
|
|
3
|
+
|
|
4
|
+
## scriptTag
|
|
5
|
+
|
|
6
|
+
If you need to load an old school (cjs) javascript or css library via cdn then use these two functions.
|
|
7
|
+
|
|
8
|
+
`xinjs-ui` uses this library to implement the `<xin-code>`, `<xin-lottie>`, and `<xin-map>`
|
|
9
|
+
elements.
|
|
10
|
+
|
|
11
|
+
`scriptTag()` and `styleSheet()` return promises that resolve `globalThis` when the module in question
|
|
12
|
+
has loaded and otherwise behave as much like `import()` as possible.
|
|
13
|
+
|
|
14
|
+
This example uses `scriptTag` and `styleSheet` to load [quilljs](https://quilljs.com) on-the-fly.
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
const { elements } = xinjs
|
|
18
|
+
const { scriptTag, styleSheet } = xinjsui
|
|
19
|
+
|
|
20
|
+
const toolbarOptions = [
|
|
21
|
+
[{ header: [1, 2, 3, 4, false] }],
|
|
22
|
+
['blockquote', 'code-block'],
|
|
23
|
+
[{ 'align': [] }],
|
|
24
|
+
['bold', 'italic', 'strike'],
|
|
25
|
+
['link', 'image', 'video'],
|
|
26
|
+
[{ 'list': 'ordered'}, { 'list': 'bullet' }, { 'list': 'check' }],
|
|
27
|
+
[{ 'indent': '-1'}, { 'indent': '+1' }],
|
|
28
|
+
['clean']
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
;(async () => {
|
|
32
|
+
await Promise.all([
|
|
33
|
+
styleSheet('https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.core.css'),
|
|
34
|
+
styleSheet('https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css'),
|
|
35
|
+
])
|
|
36
|
+
|
|
37
|
+
const container = elements.div()
|
|
38
|
+
const { Quill } = await scriptTag('https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js')
|
|
39
|
+
preview.append(container)
|
|
40
|
+
|
|
41
|
+
const quill = new Quill(container, {
|
|
42
|
+
debug: 'info',
|
|
43
|
+
modules: {
|
|
44
|
+
toolbar: toolbarOptions,
|
|
45
|
+
},
|
|
46
|
+
theme: 'snow',
|
|
47
|
+
})
|
|
48
|
+
})()
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Note that `scriptTag` will resolve `globalThis` so it behaves as much like async `import()`
|
|
52
|
+
as possible.
|
|
53
|
+
|
|
54
|
+
As an aside:
|
|
55
|
+
|
|
56
|
+
`<xin-lottie>` is implemented in such a way that if you've preloaded the module
|
|
57
|
+
(e.g. via a script tag or packaging) it won't load it again, which affords offline
|
|
58
|
+
use.
|
|
59
|
+
|
|
60
|
+
There's no point for `<xin-map>` since it won't work without connectivity anyway.
|
|
61
|
+
|
|
62
|
+
## styleSheet
|
|
63
|
+
|
|
64
|
+
styleSheet creates a `<link>` tag for a specified css file.
|
|
65
|
+
|
|
66
|
+
Using `styleSheet`:
|
|
67
|
+
|
|
68
|
+
styleSheet('../path/to/style.css')
|
|
69
|
+
|
|
70
|
+
This is awaitable, if you care. The stylesheet `<link>` will only be inserted _once_.
|
|
71
|
+
*/
|
|
72
|
+
import { elements } from 'xinjs';
|
|
73
|
+
const loadedScripts = {};
|
|
74
|
+
export function scriptTag(src, existingSymbolName) {
|
|
75
|
+
if (loadedScripts[src] === undefined) {
|
|
76
|
+
if (existingSymbolName !== undefined) {
|
|
77
|
+
const existing = globalThis[existingSymbolName];
|
|
78
|
+
loadedScripts[src] = Promise.resolve({ [existingSymbolName]: existing });
|
|
79
|
+
}
|
|
80
|
+
const scriptElt = elements.script({ src });
|
|
81
|
+
document.head.append(scriptElt);
|
|
82
|
+
loadedScripts[src] = new Promise((resolve) => {
|
|
83
|
+
scriptElt.onload = () => resolve(globalThis);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return loadedScripts[src];
|
|
87
|
+
}
|
|
88
|
+
const loadedStyleSheets = {};
|
|
89
|
+
export function styleSheet(href) {
|
|
90
|
+
if (loadedStyleSheets[href] === undefined) {
|
|
91
|
+
const linkElement = elements.link({
|
|
92
|
+
rel: 'stylesheet',
|
|
93
|
+
type: 'text/css',
|
|
94
|
+
href,
|
|
95
|
+
});
|
|
96
|
+
document.head.append(linkElement);
|
|
97
|
+
loadedStyleSheets[href] = new Promise((resolve) => {
|
|
98
|
+
linkElement.onload = resolve;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return loadedStyleSheets[href];
|
|
102
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tosijs-ui",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "simple robust web-components for use with xinjs or anything else",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "bun --watch bin/dev.ts",
|
|
7
|
+
"format": "bun eslint src demo --fix && bun prettier --write .",
|
|
8
|
+
"tests": "bun test && bun playwright test",
|
|
9
|
+
"latest": "rm -rf node_modules && bun update"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"tosijs",
|
|
13
|
+
"ui",
|
|
14
|
+
"ux",
|
|
15
|
+
"user interface",
|
|
16
|
+
"gui",
|
|
17
|
+
"widgets",
|
|
18
|
+
"web-components",
|
|
19
|
+
"component library"
|
|
20
|
+
],
|
|
21
|
+
"author": "Tonio Loewald",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/tonioloewald/xinjs-ui.git"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@playwright/test": "^1.54.1",
|
|
29
|
+
"@types/jsdom": "^21.1.7",
|
|
30
|
+
"@types/node": "^22.16.3",
|
|
31
|
+
"@types/react": "^19.1.8",
|
|
32
|
+
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
|
33
|
+
"@typescript-eslint/parser": "^5.62.0",
|
|
34
|
+
"bun-types": "latest",
|
|
35
|
+
"caniuse-lite": "^1.0.30001727",
|
|
36
|
+
"chokidar": "^4.0.3",
|
|
37
|
+
"eslint": "^8.57.1",
|
|
38
|
+
"prettier": "^2.8.8",
|
|
39
|
+
"typescript": "^5.8.3"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"marked": "^16.0.0",
|
|
43
|
+
"tosijs": "^1.0.3"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"/dist",
|
|
47
|
+
"/LICENSE",
|
|
48
|
+
"/README.md"
|
|
49
|
+
],
|
|
50
|
+
"source": "src/index.ts",
|
|
51
|
+
"types": "dist/index.d.ts",
|
|
52
|
+
"exports": {
|
|
53
|
+
"import": "./dist/index.js",
|
|
54
|
+
"browser": "./dist/iife.js",
|
|
55
|
+
"default": "./dist/index.js",
|
|
56
|
+
"types": "./dist/index.d.ts"
|
|
57
|
+
}
|
|
58
|
+
}
|