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/filter-builder.js
DELETED
|
@@ -1,468 +0,0 @@
|
|
|
1
|
-
/*#
|
|
2
|
-
# filter
|
|
3
|
-
|
|
4
|
-
Automatically creates `ArrayFilter` functions `(a: any[]) => any[]` based on the query you build using its
|
|
5
|
-
macOS Finder-inspired interface, using an easily customizable / extensible collection of `Filter` objects.
|
|
6
|
-
|
|
7
|
-
```js
|
|
8
|
-
const { elements } = xinjs
|
|
9
|
-
const { dataTable, filterBuilder, availableFilters } = xinjsui
|
|
10
|
-
|
|
11
|
-
const sourceWords = ['acorn', 'bubblegum', 'copper', 'daisy', 'ellipse', 'fabulous', 'gerund', 'hopscotch', 'idiom', 'joke']
|
|
12
|
-
function randomWords () {
|
|
13
|
-
let numWords = Math.random() * 4
|
|
14
|
-
const words = []
|
|
15
|
-
while (numWords > 0) {
|
|
16
|
-
numWords -= 1
|
|
17
|
-
words.push(sourceWords[Math.floor(Math.random() * 10)])
|
|
18
|
-
}
|
|
19
|
-
return [...new Set(words)]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const array = []
|
|
23
|
-
for(let i = 0; i < 1000; i++) {
|
|
24
|
-
array.push({
|
|
25
|
-
date: new Date(Math.random() * Date.now()).toISOString().split('T')[0],
|
|
26
|
-
isLucky: Math.random() < 0.1,
|
|
27
|
-
number: Math.floor(Math.random() * 200 - 100),
|
|
28
|
-
string: randomWords().join(' '),
|
|
29
|
-
tags: randomWords()
|
|
30
|
-
})
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const { span } = elements
|
|
34
|
-
const tagsBinding = {
|
|
35
|
-
value: '^.tags',
|
|
36
|
-
binding: {
|
|
37
|
-
toDOM(element, value) {
|
|
38
|
-
element.classList.add('tag-list')
|
|
39
|
-
element.textContent = ''
|
|
40
|
-
element.append(...value.map(tag => span(tag, {class: 'tag'})))
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const columns = [
|
|
46
|
-
{
|
|
47
|
-
prop: 'date',
|
|
48
|
-
width: 120
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
prop: 'isLucky',
|
|
52
|
-
type: 'boolean',
|
|
53
|
-
width: 90
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
prop: 'number',
|
|
57
|
-
align: 'right',
|
|
58
|
-
width: 90
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
prop: 'string',
|
|
62
|
-
width: 200
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
prop: 'tags',
|
|
66
|
-
width: 200,
|
|
67
|
-
dataCell() {
|
|
68
|
-
return elements.div({ bind: tagsBinding })
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
]
|
|
72
|
-
|
|
73
|
-
const table = dataTable({ array, columns })
|
|
74
|
-
const filter = filterBuilder({
|
|
75
|
-
fields: columns,
|
|
76
|
-
onChange(event) {
|
|
77
|
-
table.filter = filter.filter
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
preview.append(filter, table)
|
|
81
|
-
```
|
|
82
|
-
```css
|
|
83
|
-
.preview {
|
|
84
|
-
display: flex;
|
|
85
|
-
flex-direction: column;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.preview xin-table {
|
|
89
|
-
flex: 1 1 auto;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.preview .tag-list {
|
|
93
|
-
display: flex;
|
|
94
|
-
font-size: 80%;
|
|
95
|
-
align-items: center;
|
|
96
|
-
gap: 2px;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.preview .tag {
|
|
100
|
-
display: inline-block;
|
|
101
|
-
border-radius: 4px;
|
|
102
|
-
padding: 0 5px;
|
|
103
|
-
line-height: 20px;
|
|
104
|
-
height: 20px;
|
|
105
|
-
color: var(--brand-text-color);
|
|
106
|
-
background: var(--brand-color);
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## serialization
|
|
111
|
-
|
|
112
|
-
The current state of a `<xin-filter>` can be serialized as, and restored from, a Javascript object (which itself
|
|
113
|
-
can easily be converted into JSON or a URL component) via its `state` property. Obviously, a `<xin-filter>` can
|
|
114
|
-
only restore state if it has the necessary constituent `filters`.
|
|
115
|
-
|
|
116
|
-
## availableFilters
|
|
117
|
-
|
|
118
|
-
`<xin-filter>` has a default set of `FilterMaker` objects which it uses to construct filter function.
|
|
119
|
-
In the example above, the default collection of filters is reduced to `contains`, `equals`, `after`, and `isTrue`.
|
|
120
|
-
|
|
121
|
-
The full collection includes:
|
|
122
|
-
|
|
123
|
-
- **contains** * looks for fields containing a string (ignoring case)
|
|
124
|
-
- **equals** * looks for fields containing equivalent values (ignoring case)
|
|
125
|
-
- **after** * looks for fields with a date after a provided value
|
|
126
|
-
- **greaterThan** * looks for fields with a value greater than a provided value
|
|
127
|
-
- **truthy** * looks for fields that are true / non-zero / non-empty
|
|
128
|
-
- **true** looks for fields that are `true`
|
|
129
|
-
- **false** looks for fields that are `false`
|
|
130
|
-
- **hasTags** looks for fields that are arrays containing all the (space/comma) delimited strings
|
|
131
|
-
- **doesNotHaveTags** looks for fields that are arrays containing *none* of the strings
|
|
132
|
-
|
|
133
|
-
**Note**: the filters marked with an * have negative version (e.g. does not contain).
|
|
134
|
-
|
|
135
|
-
```
|
|
136
|
-
type ObjectTest (obj: any) => boolean
|
|
137
|
-
|
|
138
|
-
interface FilterMaker {
|
|
139
|
-
caption: string // describes the test condition
|
|
140
|
-
negative?: string // describes the negative test condition
|
|
141
|
-
needsValue?: boolean // if false, the filterMaker doesn't need a needle value
|
|
142
|
-
filterMaker(needle: any) => ObjectTest // builds an ObjectTest
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
*/
|
|
146
|
-
import { Component as WebComponent, elements } from 'xinjs';
|
|
147
|
-
import { icons } from '../src/';
|
|
148
|
-
const { div, input, select, option, button, span } = elements;
|
|
149
|
-
const passThru = (array) => array;
|
|
150
|
-
const NULL_FILTER_DESCRIPTION = 'null filter, everything matches';
|
|
151
|
-
export const availableFilters = {
|
|
152
|
-
contains: {
|
|
153
|
-
caption: 'contains',
|
|
154
|
-
negative: 'does not contain',
|
|
155
|
-
makeTest: (value) => {
|
|
156
|
-
value = value.toLocaleLowerCase();
|
|
157
|
-
return (obj) => String(obj).toLocaleLowerCase().includes(value);
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
hasTags: {
|
|
161
|
-
caption: 'has tags',
|
|
162
|
-
makeTest: (value) => {
|
|
163
|
-
const tags = value
|
|
164
|
-
.split(/[\s,]/)
|
|
165
|
-
.map((tag) => tag.trim().toLocaleLowerCase())
|
|
166
|
-
.filter((tag) => tag !== '');
|
|
167
|
-
console.log(tags);
|
|
168
|
-
return (obj) => Array.isArray(obj) &&
|
|
169
|
-
tags.find((tag) => !obj.includes(tag)) === undefined;
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
doesNotHaveTags: {
|
|
173
|
-
caption: 'does not have tags',
|
|
174
|
-
makeTest: (value) => {
|
|
175
|
-
const tags = value
|
|
176
|
-
.split(/[\s,]/)
|
|
177
|
-
.map((tag) => tag.trim().toLocaleLowerCase())
|
|
178
|
-
.filter((tag) => tag !== '');
|
|
179
|
-
console.log(tags);
|
|
180
|
-
return (obj) => Array.isArray(obj) &&
|
|
181
|
-
tags.find((tag) => obj.includes(tag)) === undefined;
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
equals: {
|
|
185
|
-
caption: '=',
|
|
186
|
-
negative: '≠',
|
|
187
|
-
makeTest: (value) => {
|
|
188
|
-
if (isNaN(Number(value))) {
|
|
189
|
-
value = String(value).toLocaleLowerCase();
|
|
190
|
-
return (obj) => String(obj).toLocaleLowerCase() === value;
|
|
191
|
-
}
|
|
192
|
-
const num = Number(value);
|
|
193
|
-
return (obj) => Number(obj) === num;
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
after: {
|
|
197
|
-
caption: 'is after',
|
|
198
|
-
negative: 'is before',
|
|
199
|
-
makeTest: (value) => {
|
|
200
|
-
const date = new Date(value);
|
|
201
|
-
return (obj) => new Date(obj) > date;
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
greaterThan: {
|
|
205
|
-
caption: '>',
|
|
206
|
-
negative: '≤',
|
|
207
|
-
makeTest: (value) => {
|
|
208
|
-
if (!isNaN(Number(value))) {
|
|
209
|
-
const num = Number(value);
|
|
210
|
-
return (obj) => Number(obj) > num;
|
|
211
|
-
}
|
|
212
|
-
value = value.toLocaleLowerCase();
|
|
213
|
-
return (obj) => String(obj).toLocaleLowerCase() > value;
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
truthy: {
|
|
217
|
-
caption: 'is true/non-empty/non-zero',
|
|
218
|
-
negative: 'is false/empty/zero',
|
|
219
|
-
needsValue: false,
|
|
220
|
-
makeTest: () => (obj) => !!obj,
|
|
221
|
-
},
|
|
222
|
-
isTrue: {
|
|
223
|
-
caption: '= true',
|
|
224
|
-
needsValue: false,
|
|
225
|
-
makeTest: () => (obj) => obj === true,
|
|
226
|
-
},
|
|
227
|
-
isFalse: {
|
|
228
|
-
caption: '= false',
|
|
229
|
-
needsValue: false,
|
|
230
|
-
makeTest: () => (obj) => obj === false,
|
|
231
|
-
},
|
|
232
|
-
};
|
|
233
|
-
const passAnything = {
|
|
234
|
-
description: 'anything',
|
|
235
|
-
test: () => true,
|
|
236
|
-
};
|
|
237
|
-
function getSelectText(select) {
|
|
238
|
-
return select.options[select.selectedIndex].text;
|
|
239
|
-
}
|
|
240
|
-
export class FilterPart extends WebComponent {
|
|
241
|
-
fields = [];
|
|
242
|
-
filters = availableFilters;
|
|
243
|
-
haystack = '*';
|
|
244
|
-
condition = '';
|
|
245
|
-
needle = '';
|
|
246
|
-
content = () => [
|
|
247
|
-
select({ part: 'haystack' }),
|
|
248
|
-
icons.chevronDown(),
|
|
249
|
-
select({ part: 'condition' }),
|
|
250
|
-
icons.chevronDown(),
|
|
251
|
-
input({ part: 'needle', type: 'search' }),
|
|
252
|
-
span({ part: 'padding' }),
|
|
253
|
-
button({ part: 'remove', title: 'delete' }, icons.trash()),
|
|
254
|
-
];
|
|
255
|
-
filter = passAnything;
|
|
256
|
-
constructor() {
|
|
257
|
-
super();
|
|
258
|
-
this.initAttributes('haystack', 'condition', 'needle');
|
|
259
|
-
}
|
|
260
|
-
get state() {
|
|
261
|
-
const { haystack, needle, condition } = this.parts;
|
|
262
|
-
return {
|
|
263
|
-
haystack: haystack.value,
|
|
264
|
-
needle: needle.value,
|
|
265
|
-
condition: condition.value,
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
set state(newState) {
|
|
269
|
-
Object.assign(this, newState);
|
|
270
|
-
}
|
|
271
|
-
buildFilter = ( /* event: Event */) => {
|
|
272
|
-
const { haystack, condition, needle } = this.parts;
|
|
273
|
-
const negative = condition.value.startsWith('~');
|
|
274
|
-
const key = negative ? condition.value.slice(1) : condition.value;
|
|
275
|
-
const filter = this.filters[key];
|
|
276
|
-
needle.hidden = filter.needsValue === false;
|
|
277
|
-
const baseTest = filter.needsValue === false
|
|
278
|
-
? filter.makeTest(undefined)
|
|
279
|
-
: filter.makeTest(needle.value);
|
|
280
|
-
const field = haystack.value;
|
|
281
|
-
let test;
|
|
282
|
-
if (field !== '*') {
|
|
283
|
-
test = negative
|
|
284
|
-
? (obj) => !baseTest(obj[field])
|
|
285
|
-
: (obj) => baseTest(obj[field]);
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
test = negative
|
|
289
|
-
? (obj) => Object.values(obj).find((v) => !baseTest(v)) !== undefined
|
|
290
|
-
: (obj) => Object.values(obj).find((v) => baseTest(v)) !== undefined;
|
|
291
|
-
}
|
|
292
|
-
const matchValue = filter.needsValue !== false ? ` "${needle.value}"` : '';
|
|
293
|
-
const description = `${getSelectText(haystack)} ${getSelectText(condition)}${matchValue}`;
|
|
294
|
-
this.filter = {
|
|
295
|
-
description,
|
|
296
|
-
test,
|
|
297
|
-
};
|
|
298
|
-
this.parentElement?.dispatchEvent(new Event('change'));
|
|
299
|
-
};
|
|
300
|
-
connectedCallback() {
|
|
301
|
-
super.connectedCallback();
|
|
302
|
-
const { haystack, condition, needle, remove } = this.parts;
|
|
303
|
-
haystack.addEventListener('change', this.buildFilter);
|
|
304
|
-
condition.addEventListener('change', this.buildFilter);
|
|
305
|
-
needle.addEventListener('input', this.buildFilter);
|
|
306
|
-
haystack.value = this.haystack;
|
|
307
|
-
condition.value = this.condition;
|
|
308
|
-
needle.value = this.needle;
|
|
309
|
-
remove.addEventListener('click', () => {
|
|
310
|
-
const { parentElement } = this;
|
|
311
|
-
this.remove();
|
|
312
|
-
parentElement?.dispatchEvent(new Event('change'));
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
render() {
|
|
316
|
-
super.render();
|
|
317
|
-
const { haystack, condition, needle } = this.parts;
|
|
318
|
-
haystack.textContent = '';
|
|
319
|
-
haystack.append(option('any field', { value: '*' }), ...this.fields.map((field) => {
|
|
320
|
-
const caption = field.name || field.prop;
|
|
321
|
-
return option(`${caption}`, { value: field.prop });
|
|
322
|
-
}));
|
|
323
|
-
condition.textContent = '';
|
|
324
|
-
const conditions = Object.keys(this.filters)
|
|
325
|
-
.map((key) => {
|
|
326
|
-
const filter = this.filters[key];
|
|
327
|
-
return filter.negative !== undefined
|
|
328
|
-
? [
|
|
329
|
-
option(filter.caption, { value: key }),
|
|
330
|
-
option(filter.negative, { value: '~' + key }),
|
|
331
|
-
]
|
|
332
|
-
: option(filter.caption, { value: key });
|
|
333
|
-
})
|
|
334
|
-
.flat();
|
|
335
|
-
condition.append(...conditions);
|
|
336
|
-
if (this.haystack !== '') {
|
|
337
|
-
haystack.value = this.haystack;
|
|
338
|
-
}
|
|
339
|
-
if (this.condition !== '') {
|
|
340
|
-
condition.value = this.condition;
|
|
341
|
-
}
|
|
342
|
-
if (this.needle !== '') {
|
|
343
|
-
needle.value = this.needle;
|
|
344
|
-
}
|
|
345
|
-
this.buildFilter();
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
export const filterPart = FilterPart.elementCreator({
|
|
349
|
-
tag: 'xin-filter-part',
|
|
350
|
-
styleSpec: {
|
|
351
|
-
':host': {
|
|
352
|
-
display: 'flex',
|
|
353
|
-
},
|
|
354
|
-
':host .xin-icon:': {
|
|
355
|
-
verticalAlign: 'middle',
|
|
356
|
-
pointerEvents: 'none',
|
|
357
|
-
},
|
|
358
|
-
':host [part="haystack"], :host [part="condition"]': {
|
|
359
|
-
flex: '1',
|
|
360
|
-
},
|
|
361
|
-
':host [part="needle"]': {
|
|
362
|
-
flex: 2,
|
|
363
|
-
},
|
|
364
|
-
':host [hidden]+[part="padding"]': {
|
|
365
|
-
display: 'block',
|
|
366
|
-
content: ' ',
|
|
367
|
-
flex: '1 1 auto',
|
|
368
|
-
},
|
|
369
|
-
},
|
|
370
|
-
});
|
|
371
|
-
export class FilterBuilder extends WebComponent {
|
|
372
|
-
_fields = [];
|
|
373
|
-
get fields() {
|
|
374
|
-
return this._fields;
|
|
375
|
-
}
|
|
376
|
-
set fields(_fields) {
|
|
377
|
-
this._fields = _fields;
|
|
378
|
-
this.queueRender();
|
|
379
|
-
}
|
|
380
|
-
get state() {
|
|
381
|
-
const { filterContainer } = this.parts;
|
|
382
|
-
return [...filterContainer.children].map((part) => part.state);
|
|
383
|
-
}
|
|
384
|
-
set state(parts) {
|
|
385
|
-
const { fields, filters } = this;
|
|
386
|
-
const { filterContainer } = this.parts;
|
|
387
|
-
filterContainer.textContent = '';
|
|
388
|
-
for (const state of parts) {
|
|
389
|
-
filterContainer.append(filterPart({ fields, filters, ...state }));
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
filter = passThru;
|
|
393
|
-
description = NULL_FILTER_DESCRIPTION;
|
|
394
|
-
addFilter = () => {
|
|
395
|
-
const { fields, filters } = this;
|
|
396
|
-
const { filterContainer } = this.parts;
|
|
397
|
-
filterContainer.append(filterPart({ fields, filters }));
|
|
398
|
-
};
|
|
399
|
-
content = () => [
|
|
400
|
-
button({
|
|
401
|
-
part: 'add',
|
|
402
|
-
title: 'add filter condition',
|
|
403
|
-
onClick: this.addFilter,
|
|
404
|
-
class: 'round',
|
|
405
|
-
}, icons.plus()),
|
|
406
|
-
div({ part: 'filterContainer' }),
|
|
407
|
-
button({ part: 'reset', title: 'reset filter', onClick: this.reset }, icons.x()),
|
|
408
|
-
];
|
|
409
|
-
filters = availableFilters;
|
|
410
|
-
reset = () => {
|
|
411
|
-
const { fields, filters } = this;
|
|
412
|
-
const { filterContainer } = this.parts;
|
|
413
|
-
this.description = NULL_FILTER_DESCRIPTION;
|
|
414
|
-
this.filter = passThru;
|
|
415
|
-
filterContainer.textContent = '';
|
|
416
|
-
filterContainer.append(filterPart({ fields, filters }));
|
|
417
|
-
this.dispatchEvent(new Event('change'));
|
|
418
|
-
};
|
|
419
|
-
buildFilter = ( /* event: Event */) => {
|
|
420
|
-
const { filterContainer } = this.parts;
|
|
421
|
-
if (filterContainer.children.length === 0) {
|
|
422
|
-
this.reset();
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
const filters = [...filterContainer.children].map((filterPart) => filterPart.filter);
|
|
426
|
-
const tests = filters.map((filter) => filter.test);
|
|
427
|
-
this.description = filters.map((filter) => filter.description).join(', ');
|
|
428
|
-
this.filter = (array) => array.filter((obj) => tests.find((f) => f(obj) === false) === undefined);
|
|
429
|
-
this.dispatchEvent(new Event('change'));
|
|
430
|
-
};
|
|
431
|
-
connectedCallback() {
|
|
432
|
-
super.connectedCallback();
|
|
433
|
-
const { filterContainer } = this.parts;
|
|
434
|
-
filterContainer.addEventListener('change', this.buildFilter);
|
|
435
|
-
this.reset();
|
|
436
|
-
}
|
|
437
|
-
render() {
|
|
438
|
-
super.render();
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
export const filterBuilder = FilterBuilder.elementCreator({
|
|
442
|
-
tag: 'xin-filter',
|
|
443
|
-
styleSpec: {
|
|
444
|
-
':host': {
|
|
445
|
-
height: 'auto',
|
|
446
|
-
display: 'grid',
|
|
447
|
-
gridTemplateColumns: '32px calc(100% - 64px) 32px',
|
|
448
|
-
alignItems: 'center',
|
|
449
|
-
},
|
|
450
|
-
':host [part="filterContainer"]': {
|
|
451
|
-
display: 'flex',
|
|
452
|
-
flexDirection: 'column',
|
|
453
|
-
alignItems: 'stretch',
|
|
454
|
-
flex: '1 1 auto',
|
|
455
|
-
},
|
|
456
|
-
':host [part="add"], :host [part="reset"]': {
|
|
457
|
-
'--button-size': 'var(--touch-size, 32px)',
|
|
458
|
-
borderRadius: '999px',
|
|
459
|
-
height: 'var(--button-size)',
|
|
460
|
-
lineHeight: 'var(--button-size)',
|
|
461
|
-
margin: '0',
|
|
462
|
-
padding: '0',
|
|
463
|
-
textAlign: 'center',
|
|
464
|
-
width: 'var(--button-size)',
|
|
465
|
-
flex: '0 0 var(--button-size)',
|
|
466
|
-
},
|
|
467
|
-
},
|
|
468
|
-
});
|
package/dist/float.js
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
/*#
|
|
2
|
-
# float
|
|
3
|
-
|
|
4
|
-
A floating, potentially draggable user interface element.
|
|
5
|
-
|
|
6
|
-
```html
|
|
7
|
-
<xin-float class="float" remain-on-resize="remain" remain-on-scroll="remain" drag>
|
|
8
|
-
<h4>Drag Me</h4>
|
|
9
|
-
<div class="no-drag balloon">🎈</div>
|
|
10
|
-
<div class="behavior">I ignore resizing and scrolling</div>
|
|
11
|
-
<footer style="font-size: 75%">neunundneunzig pixel-ballon</footer>
|
|
12
|
-
</xin-float>
|
|
13
|
-
|
|
14
|
-
<xin-float class="float" remain-on-scroll="remain" style="top: 50px; right: 20px;" drag>
|
|
15
|
-
<h4>Drag Me</h4>
|
|
16
|
-
<div class="no-drag balloon">🎈</div>
|
|
17
|
-
<div class="behavior">I disappear on resize</div>
|
|
18
|
-
<footer style="font-size: 75%">neunundneunzig pixel-ballon</footer>
|
|
19
|
-
</xin-float>
|
|
20
|
-
|
|
21
|
-
<xin-float class="float" remain-on-resize="remain" remain-on-scroll="remove" style="bottom: 20px; left: 50px;" drag>
|
|
22
|
-
<h4>Drag Me</h4>
|
|
23
|
-
<div class="no-drag balloon">🎈</div>
|
|
24
|
-
<div class="behavior">I disappear on scroll</div>
|
|
25
|
-
<footer style="font-size: 75%">neunundneunzig pixel-ballon</footer>
|
|
26
|
-
</xin-float>
|
|
27
|
-
```
|
|
28
|
-
```css
|
|
29
|
-
.preview .float {
|
|
30
|
-
width: 220px;
|
|
31
|
-
height: 180px;
|
|
32
|
-
padding: 0;
|
|
33
|
-
gap: 5px;
|
|
34
|
-
display: flex;
|
|
35
|
-
flex-direction: column;
|
|
36
|
-
border-radius: 5px;
|
|
37
|
-
background: #fff8;
|
|
38
|
-
box-shadow: 2px 10px 20px #0004;
|
|
39
|
-
overflow: hidden;
|
|
40
|
-
cursor: move;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.preview h4 {
|
|
44
|
-
margin: 0;
|
|
45
|
-
padding: 5px 10px;
|
|
46
|
-
color: white;
|
|
47
|
-
background: red;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.preview .balloon {
|
|
51
|
-
cursor: default;
|
|
52
|
-
flex: 1 1 auto;
|
|
53
|
-
font-size: 99px;
|
|
54
|
-
line-height: 120px;
|
|
55
|
-
text-align: center;
|
|
56
|
-
height: auto;
|
|
57
|
-
overflow: hidden;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
.preview .behavior {
|
|
61
|
-
position: absolute;
|
|
62
|
-
bottom: 16px;
|
|
63
|
-
left: 8px;
|
|
64
|
-
right: 8px;
|
|
65
|
-
background: #fffc;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.preview footer {
|
|
69
|
-
text-align: center;
|
|
70
|
-
background: #f008;
|
|
71
|
-
color: white;
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Styling
|
|
75
|
-
|
|
76
|
-
Note that the `<xin-float>` element has absolutely minimal styling. It's up to you to provide a drop
|
|
77
|
-
shadow and background and so on.
|
|
78
|
-
|
|
79
|
-
## Attributes
|
|
80
|
-
|
|
81
|
-
- `drag` false | true — to make a `<xin-float>` element draggable, simply set its `drag` attribute.
|
|
82
|
-
- `remain-on-resize` 'remove' | 'hide' | 'remain' — by default, floats will hide if the window is resized
|
|
83
|
-
- `remain-on-scroll` 'remain' | 'remove' | 'hide' — by default, floats will remain if the document is scrolled
|
|
84
|
-
|
|
85
|
-
Note that `remain-on-scroll` behavior applies to any scrolling in the document (including within the float) so
|
|
86
|
-
if you want finer-grained disappearing behavior triggered by scrolling, you might want to implement it yourself.
|
|
87
|
-
|
|
88
|
-
To prevent dragging for an interior element (e.g. if you want a floating palette with buttons or input fields)
|
|
89
|
-
just add the `no-drag` class to an element or its container.
|
|
90
|
-
*/
|
|
91
|
-
import { Component as WebComponent, elements } from 'xinjs';
|
|
92
|
-
import { trackDrag, bringToFront } from './track-drag';
|
|
93
|
-
const { slot } = elements;
|
|
94
|
-
export class XinFloat extends WebComponent {
|
|
95
|
-
static floats = new Set();
|
|
96
|
-
drag = false;
|
|
97
|
-
remainOnResize = 'remove';
|
|
98
|
-
remainOnScroll = 'remain';
|
|
99
|
-
content = slot();
|
|
100
|
-
static styleSpec = {
|
|
101
|
-
':host': {
|
|
102
|
-
position: 'fixed',
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
constructor() {
|
|
106
|
-
super();
|
|
107
|
-
this.initAttributes('drag', 'remainOnResize', 'remainOnScroll');
|
|
108
|
-
}
|
|
109
|
-
reposition = (event) => {
|
|
110
|
-
const target = event.target;
|
|
111
|
-
if (target?.closest('.no-drag')) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (this.drag) {
|
|
115
|
-
bringToFront(this);
|
|
116
|
-
const x = this.offsetLeft;
|
|
117
|
-
const y = this.offsetTop;
|
|
118
|
-
trackDrag(event, (dx, dy, pointerEvent) => {
|
|
119
|
-
this.style.left = `${x + dx}px`;
|
|
120
|
-
this.style.top = `${y + dy}px`;
|
|
121
|
-
this.style.right = 'auto';
|
|
122
|
-
this.style.bottom = 'auto';
|
|
123
|
-
if (pointerEvent.type === 'mouseup') {
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
connectedCallback() {
|
|
130
|
-
super.connectedCallback();
|
|
131
|
-
XinFloat.floats.add(this);
|
|
132
|
-
const PASSIVE = { passive: true };
|
|
133
|
-
this.addEventListener('touchstart', this.reposition, PASSIVE);
|
|
134
|
-
this.addEventListener('mousedown', this.reposition, PASSIVE);
|
|
135
|
-
bringToFront(this);
|
|
136
|
-
}
|
|
137
|
-
disconnectedCallback() {
|
|
138
|
-
super.disconnectedCallback();
|
|
139
|
-
XinFloat.floats.delete(this);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
export const xinFloat = XinFloat.elementCreator({
|
|
143
|
-
tag: 'xin-float',
|
|
144
|
-
});
|
|
145
|
-
window.addEventListener('resize', () => {
|
|
146
|
-
;
|
|
147
|
-
[...XinFloat.floats].forEach((float) => {
|
|
148
|
-
if (float.remainOnResize === 'hide') {
|
|
149
|
-
float.hidden = true;
|
|
150
|
-
}
|
|
151
|
-
else if (float.remainOnResize === 'remove') {
|
|
152
|
-
float.remove();
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
}, { passive: true });
|
|
156
|
-
document.addEventListener('scroll', (event) => {
|
|
157
|
-
if (event.target instanceof HTMLElement &&
|
|
158
|
-
event.target.closest(XinFloat.tagName)) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
;
|
|
162
|
-
[...XinFloat.floats].forEach((float) => {
|
|
163
|
-
if (float.remainOnScroll === 'hide') {
|
|
164
|
-
float.hidden = true;
|
|
165
|
-
}
|
|
166
|
-
else if (float.remainOnScroll === 'remove') {
|
|
167
|
-
float.remove();
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
}, { passive: true, capture: true });
|