@fusion.dev/helix-headless 0.143.0 → 0.145.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/custom-elements.json +16990 -1383
- package/dist/accordion/item/accordion-item.d.ts +48 -12
- package/dist/accordion/item/accordion-item.js +26 -5
- package/dist/accordion/root/accordion-root.d.ts +6 -0
- package/dist/accordion/root/accordion-root.js +7 -1
- package/dist/announcement/root/announcement-root.d.ts +21 -1
- package/dist/announcement/root/announcement-root.js +36 -1
- package/dist/app-header/root/app-header-root.d.ts +7 -0
- package/dist/app-header/root/app-header-root.js +5 -1
- package/dist/app-header/sectioning.d.ts +7 -0
- package/dist/app-header/sectioning.js +26 -0
- package/dist/area-chart/root/area-chart-root.d.ts +12 -4
- package/dist/area-chart/root/area-chart-root.js +24 -7
- package/dist/avatar/root/avatar-root.d.ts +14 -0
- package/dist/avatar/root/avatar-root.js +23 -9
- package/dist/badge/root/badge-root.d.ts +11 -7
- package/dist/badge/root/badge-root.js +1 -1
- package/dist/bar-chart/root/bar-chart-root.d.ts +12 -3
- package/dist/bar-chart/root/bar-chart-root.js +17 -10
- package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +32 -1
- package/dist/breadcrumbs/root/breadcrumbs-root.js +25 -2
- package/dist/button/root/button-root.d.ts +47 -1
- package/dist/button/root/button-root.js +38 -4
- package/dist/callout/root/callout-root.d.ts +18 -1
- package/dist/callout/root/callout-root.js +15 -4
- package/dist/canvas/root/canvas-root.d.ts +38 -3
- package/dist/canvas/root/canvas-root.js +121 -40
- package/dist/card/root/card-root.d.ts +30 -1
- package/dist/card/root/card-root.js +38 -5
- package/dist/carousel/root/carousel-root.d.ts +90 -0
- package/dist/carousel/root/carousel-root.js +134 -12
- package/dist/chat/composer/chat-composer.d.ts +38 -6
- package/dist/chat/composer/chat-composer.js +42 -9
- package/dist/chat/message/chat-message.d.ts +32 -1
- package/dist/chat/message/chat-message.js +25 -9
- package/dist/chat/naming.d.ts +22 -0
- package/dist/chat/naming.js +12 -0
- package/dist/chat/relative-clock.d.ts +39 -0
- package/dist/chat/relative-clock.js +31 -0
- package/dist/chat/root/chat-root.d.ts +36 -0
- package/dist/chat/root/chat-root.js +24 -1
- package/dist/checkbox/root/checkbox-root.js +2 -0
- package/dist/checkbox-group/root/checkbox-group-root.d.ts +53 -3
- package/dist/checkbox-group/root/checkbox-group-root.js +71 -22
- package/dist/code-block/lines.d.ts +13 -0
- package/dist/code-block/lines.js +2 -0
- package/dist/code-block/root/code-block-root.d.ts +38 -1
- package/dist/code-block/root/code-block-root.js +28 -7
- package/dist/color-input/root/color-input-root.d.ts +34 -1
- package/dist/color-input/root/color-input-root.js +23 -4
- package/dist/color-picker/channels.d.ts +26 -0
- package/dist/color-picker/channels.js +84 -0
- package/dist/color-picker/root/color-picker-root.d.ts +81 -5
- package/dist/color-picker/root/color-picker-root.js +103 -10
- package/dist/combobox/root/combobox-root.d.ts +38 -3
- package/dist/combobox/root/combobox-root.js +64 -2
- package/dist/command/root/command-root.d.ts +30 -4
- package/dist/command/root/command-root.js +33 -3
- package/dist/copy-button/from.d.ts +22 -0
- package/dist/copy-button/from.js +28 -0
- package/dist/copy-button/root/copy-button-root.d.ts +47 -1
- package/dist/copy-button/root/copy-button-root.js +44 -8
- package/dist/data-table/root/data-table-root.d.ts +150 -2
- package/dist/data-table/root/data-table-root.js +303 -29
- package/dist/data-table/tree.d.ts +40 -0
- package/dist/data-table/tree.js +55 -0
- package/dist/date-input/root/date-input-root.d.ts +73 -4
- package/dist/date-input/root/date-input-root.js +93 -43
- package/dist/date-picker/root/date-picker-root.d.ts +145 -1
- package/dist/date-picker/root/date-picker-root.js +239 -48
- package/dist/description-list/item/description-list-item.d.ts +17 -2
- package/dist/description-list/item/description-list-item.js +15 -2
- package/dist/description-list/root/description-list-root.d.ts +27 -1
- package/dist/description-list/root/description-list-root.js +26 -1
- package/dist/dialog/description.d.ts +9 -0
- package/dist/dialog/description.js +14 -0
- package/dist/dialog/root/dialog-root.d.ts +64 -1
- package/dist/dialog/root/dialog-root.js +49 -23
- package/dist/donut-chart/root/donut-chart-root.d.ts +25 -4
- package/dist/donut-chart/root/donut-chart-root.js +21 -6
- package/dist/drawer/root/drawer-root.d.ts +63 -2
- package/dist/drawer/root/drawer-root.js +95 -19
- package/dist/drawer/swipe-start.d.ts +9 -0
- package/dist/drawer/swipe-start.js +12 -0
- package/dist/drilldown/item/drilldown-item.d.ts +11 -6
- package/dist/drilldown/item/drilldown-item.js +8 -5
- package/dist/drilldown/root/drilldown-root.d.ts +11 -0
- package/dist/drilldown/root/drilldown-root.js +8 -3
- package/dist/element-events.generated.js +61 -13
- package/dist/empty/root/empty-root.d.ts +1 -1
- package/dist/file-input/root/file-input-root.d.ts +47 -0
- package/dist/file-input/root/file-input-root.js +60 -22
- package/dist/file-input/screen.d.ts +35 -0
- package/dist/file-input/screen.js +49 -0
- package/dist/filter-input/menu/filter-menu.d.ts +3 -0
- package/dist/filter-input/menu/filter-menu.js +8 -4
- package/dist/filter-input/root/filter-input-root.d.ts +38 -4
- package/dist/filter-input/root/filter-input-root.js +67 -3
- package/dist/filter-input/suggest.d.ts +17 -0
- package/dist/filter-input/suggest.js +23 -0
- package/dist/format-date/options.d.ts +33 -0
- package/dist/format-date/options.js +42 -0
- package/dist/format-date/root/format-date-root.d.ts +51 -0
- package/dist/format-date/root/format-date-root.js +58 -38
- package/dist/heatmap/root/heatmap-root.d.ts +23 -6
- package/dist/heatmap/root/heatmap-root.js +24 -13
- package/dist/icon/index.d.ts +2 -0
- package/dist/icon/index.js +2 -0
- package/dist/icon/registry.d.ts +37 -0
- package/dist/icon/registry.js +83 -0
- package/dist/icon/root/icon-root.d.ts +30 -4
- package/dist/icon/root/icon-root.js +41 -6
- package/dist/icon/root/index.d.ts +2 -0
- package/dist/icon/root/index.js +2 -0
- package/dist/icon/set.d.ts +1 -0
- package/dist/icon/set.js +3 -0
- package/dist/image/root/image-root.d.ts +14 -0
- package/dist/image/root/image-root.js +23 -2
- package/dist/input/root/input-root.d.ts +11 -0
- package/dist/input/root/input-root.js +16 -1
- package/dist/kbd/root/kbd-root.d.ts +20 -13
- package/dist/kbd/root/kbd-root.js +35 -37
- package/dist/kbd/shortcut.d.ts +18 -0
- package/dist/kbd/shortcut.js +35 -0
- package/dist/legend/hidden-series.d.ts +8 -0
- package/dist/legend/hidden-series.js +13 -0
- package/dist/legend/root/legend-root.d.ts +56 -0
- package/dist/legend/root/legend-root.js +54 -5
- package/dist/lightbox/item/lightbox-item.d.ts +15 -0
- package/dist/lightbox/item/lightbox-item.js +10 -1
- package/dist/lightbox/root/lightbox-root.d.ts +61 -1
- package/dist/lightbox/root/lightbox-root.js +94 -4
- package/dist/line-chart/root/line-chart-root.d.ts +11 -3
- package/dist/line-chart/root/line-chart-root.js +19 -5
- package/dist/list/item/list-item.d.ts +68 -1
- package/dist/list/item/list-item.js +66 -4
- package/dist/list/numbering.d.ts +14 -0
- package/dist/list/numbering.js +8 -0
- package/dist/list/root/list-root.d.ts +106 -9
- package/dist/list/root/list-root.js +189 -4
- package/dist/list/selection.d.ts +19 -0
- package/dist/list/selection.js +21 -0
- package/dist/listbox/root/listbox-root.d.ts +74 -3
- package/dist/listbox/root/listbox-root.js +86 -9
- package/dist/loader/busy.d.ts +17 -0
- package/dist/loader/busy.js +24 -0
- package/dist/loader/root/loader-root.d.ts +37 -0
- package/dist/loader/root/loader-root.js +52 -3
- package/dist/map/root/map-root.d.ts +6 -2
- package/dist/map/root/map-root.js +5 -2
- package/dist/megamenu/root/megamenu-root.d.ts +21 -0
- package/dist/megamenu/root/megamenu-root.js +16 -2
- package/dist/menu/index.d.ts +1 -0
- package/dist/menu/index.js +1 -0
- package/dist/menu/item/menu-item.d.ts +38 -11
- package/dist/menu/item/menu-item.js +33 -1
- package/dist/menu/root/menu-root.d.ts +25 -1
- package/dist/menu/root/menu-root.js +63 -6
- package/dist/menu/section/menu-section.d.ts +55 -0
- package/dist/menu/section/menu-section.js +60 -0
- package/dist/meter/root/meter-root.d.ts +7 -0
- package/dist/meter/root/meter-root.js +6 -3
- package/dist/navigation/root/navigation-root.d.ts +16 -1
- package/dist/navigation/root/navigation-root.js +27 -1
- package/dist/notifications/feed-keys.d.ts +10 -0
- package/dist/notifications/feed-keys.js +14 -0
- package/dist/notifications/notification/notification.d.ts +17 -0
- package/dist/notifications/notification/notification.js +21 -2
- package/dist/notifications/root/notifications-root.d.ts +60 -0
- package/dist/notifications/root/notifications-root.js +76 -1
- package/dist/number-input/root/number-input-root.d.ts +47 -0
- package/dist/number-input/root/number-input-root.js +58 -22
- package/dist/object-inspector/root/object-inspector-root.d.ts +98 -16
- package/dist/object-inspector/root/object-inspector-root.js +180 -80
- package/dist/object-inspector/rows.d.ts +57 -0
- package/dist/object-inspector/rows.js +86 -0
- package/dist/option/root/option-root.d.ts +54 -5
- package/dist/option/root/option-root.js +34 -5
- package/dist/outline/levels.d.ts +31 -0
- package/dist/outline/levels.js +23 -0
- package/dist/outline/root/outline-root.d.ts +68 -3
- package/dist/outline/root/outline-root.js +73 -8
- package/dist/pagination/size/pagination-size.d.ts +11 -2
- package/dist/pagination/size/pagination-size.js +19 -4
- package/dist/pin-input/format.d.ts +7 -0
- package/dist/pin-input/format.js +16 -0
- package/dist/pin-input/root/pin-input-root.d.ts +21 -0
- package/dist/pin-input/root/pin-input-root.js +29 -2
- package/dist/popover/root/popover-root.d.ts +62 -4
- package/dist/popover/root/popover-root.js +53 -5
- package/dist/progress/root/progress-root.d.ts +7 -0
- package/dist/progress/root/progress-root.js +9 -3
- package/dist/qr-code/export.d.ts +31 -0
- package/dist/qr-code/export.js +10 -0
- package/dist/qr-code/root/qr-code-root.d.ts +26 -0
- package/dist/qr-code/root/qr-code-root.js +49 -2
- package/dist/radio/group/radio-group.d.ts +17 -0
- package/dist/radio/group/radio-group.js +14 -1
- package/dist/rating/root/rating-root.d.ts +25 -0
- package/dist/rating/root/rating-root.js +46 -4
- package/dist/scatter-chart/root/scatter-chart-root.d.ts +17 -3
- package/dist/scatter-chart/root/scatter-chart-root.js +25 -7
- package/dist/scroll-area/root/scroll-area-root.d.ts +113 -0
- package/dist/scroll-area/root/scroll-area-root.js +218 -3
- package/dist/scroll-area/scrollbar.d.ts +65 -0
- package/dist/scroll-area/scrollbar.js +24 -0
- package/dist/select/root/select-root.d.ts +24 -0
- package/dist/select/root/select-root.js +21 -7
- package/dist/skeleton/root/skeleton-root.d.ts +25 -0
- package/dist/skeleton/root/skeleton-root.js +17 -2
- package/dist/slider/root/slider-root.d.ts +39 -0
- package/dist/slider/root/slider-root.js +45 -9
- package/dist/splitter/root/splitter-root.d.ts +13 -0
- package/dist/splitter/root/splitter-root.js +20 -2
- package/dist/splitter/snap.d.ts +9 -0
- package/dist/splitter/snap.js +12 -0
- package/dist/stat/root/stat-root.d.ts +2 -0
- package/dist/stat/root/stat-root.js +1 -1
- package/dist/steps/reach.d.ts +15 -0
- package/dist/steps/reach.js +12 -0
- package/dist/steps/root/steps-root.d.ts +30 -0
- package/dist/steps/root/steps-root.js +53 -0
- package/dist/steps/step/step.d.ts +39 -2
- package/dist/steps/step/step.js +29 -7
- package/dist/switch/root/switch-root.d.ts +12 -2
- package/dist/switch/root/switch-root.js +6 -1
- package/dist/table/column/table-column.d.ts +11 -0
- package/dist/table/column/table-column.js +6 -1
- package/dist/table/root/table-root.d.ts +101 -0
- package/dist/table/root/table-root.js +166 -5
- package/dist/table/row/table-row.d.ts +47 -1
- package/dist/table/row/table-row.js +39 -2
- package/dist/tabs/panel/tab-panel.d.ts +9 -5
- package/dist/tabs/panel/tab-panel.js +3 -4
- package/dist/tabs/root/tabs-root.d.ts +25 -1
- package/dist/tabs/root/tabs-root.js +21 -3
- package/dist/tabs/tab/tab.d.ts +36 -4
- package/dist/tabs/tab/tab.js +46 -3
- package/dist/tag/group/tag-group.d.ts +107 -0
- package/dist/tag/group/tag-group.js +157 -0
- package/dist/tag/index.d.ts +1 -0
- package/dist/tag/index.js +1 -0
- package/dist/tag/root/tag-root.d.ts +60 -0
- package/dist/tag/root/tag-root.js +74 -18
- package/dist/tag/selection.d.ts +10 -0
- package/dist/tag/selection.js +12 -0
- package/dist/textarea/grow.d.ts +22 -0
- package/dist/textarea/grow.js +20 -0
- package/dist/textarea/root/textarea-root.d.ts +9 -0
- package/dist/textarea/root/textarea-root.js +9 -1
- package/dist/time-input/root/time-input-root.d.ts +44 -5
- package/dist/time-input/root/time-input-root.js +69 -39
- package/dist/time-picker/root/time-picker-root.d.ts +54 -8
- package/dist/time-picker/root/time-picker-root.js +121 -91
- package/dist/timeline/group/timeline-group.d.ts +46 -0
- package/dist/timeline/group/timeline-group.js +37 -0
- package/dist/timeline/index.d.ts +1 -0
- package/dist/timeline/index.js +1 -0
- package/dist/timeline/root/timeline-root.d.ts +10 -3
- package/dist/toast/hotkey.d.ts +22 -0
- package/dist/toast/hotkey.js +23 -0
- package/dist/toast/item/toast-item.d.ts +26 -18
- package/dist/toast/item/toast-item.js +55 -48
- package/dist/toast/root/toast-root.d.ts +42 -8
- package/dist/toast/root/toast-root.js +64 -4
- package/dist/toggle/group/toggle-group.d.ts +57 -15
- package/dist/toggle/group/toggle-group.js +69 -9
- package/dist/toggle/root/toggle-root.d.ts +12 -1
- package/dist/toggle/root/toggle-root.js +11 -3
- package/dist/toolbar/root/toolbar-root.d.ts +13 -0
- package/dist/toolbar/root/toolbar-root.js +15 -5
- package/dist/tooltip/focus-target.d.ts +6 -0
- package/dist/tooltip/focus-target.js +9 -0
- package/dist/tooltip/root/tooltip-root.d.ts +42 -17
- package/dist/tooltip/root/tooltip-root.js +50 -9
- package/dist/tour/naming.d.ts +30 -0
- package/dist/tour/naming.js +10 -0
- package/dist/tour/root/tour-root.d.ts +35 -0
- package/dist/tour/root/tour-root.js +56 -5
- package/dist/tour/stop/tour-stop.d.ts +53 -8
- package/dist/tour/stop/tour-stop.js +45 -15
- package/dist/tour/wait.d.ts +11 -0
- package/dist/tour/wait.js +19 -0
- package/dist/tree/item/tree-item.d.ts +54 -1
- package/dist/tree/item/tree-item.js +89 -20
- package/dist/tree/root/tree-root.d.ts +37 -0
- package/dist/tree/root/tree-root.js +51 -6
- package/dist/tree/selection.d.ts +24 -0
- package/dist/tree/selection.js +26 -0
- package/dist/trend/root/trend-root.d.ts +13 -0
- package/dist/trend/root/trend-root.js +14 -9
- package/dist/video/media-events.d.ts +49 -0
- package/dist/video/media-events.js +39 -0
- package/dist/video/root/video-root.d.ts +99 -13
- package/dist/video/root/video-root.js +84 -47
- package/dist/video-playlist/menu/video-playlist-menu.d.ts +3 -0
- package/dist/video-playlist/menu/video-playlist-menu.js +2 -1
- package/dist/video-playlist/order.d.ts +41 -0
- package/dist/video-playlist/order.js +34 -0
- package/dist/video-playlist/root/video-playlist-root.d.ts +70 -2
- package/dist/video-playlist/root/video-playlist-root.js +60 -16
- package/dist/virtual-list/controls.d.ts +4 -0
- package/dist/virtual-list/controls.js +30 -0
- package/dist/virtual-list/fill.d.ts +30 -0
- package/dist/virtual-list/fill.js +39 -0
- package/dist/virtual-list/keys.d.ts +15 -0
- package/dist/virtual-list/keys.js +24 -0
- package/dist/virtual-list/root/virtual-list-root.d.ts +155 -23
- package/dist/virtual-list/root/virtual-list-root.js +175 -34
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +8871 -1286
- package/types/svelte.d.ts +3 -0
- package/types/vue.d.ts +1918 -269
|
@@ -4,11 +4,12 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { AriaForwardController, customElement, drawIcon, fillTemplate, HelixBeforeChangeEvent, HelixFormElement, MixinField, requestChange, restoreValue, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, customElement, drawIcon, drawStatus, fillTemplate, formatNumbers, HelixBeforeChangeEvent, HelixFormElement, HelixRejectEvent, MixinField, requestChange, restoreValue, StatusController, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { trash } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
11
|
import { html } from 'lit/static-html.js';
|
|
12
|
+
import { screenFiles } from '../screen.js';
|
|
12
13
|
import '../../tag/index.js';
|
|
13
14
|
const named = (label, name) => label.includes('{name}') ? fillTemplate(label, { name }) : `${label} ${name}`;
|
|
14
15
|
const size = (bytes, locale) => {
|
|
@@ -33,22 +34,6 @@ const kind = (file) => {
|
|
|
33
34
|
const subtype = file.type.split('/')[1]?.replace(/^x-/, '').split('+')[0] ?? '';
|
|
34
35
|
return /^[a-z0-9]{1,6}$/i.test(subtype) ? subtype.toUpperCase() : 'File';
|
|
35
36
|
};
|
|
36
|
-
const accepts = (file, accept) => {
|
|
37
|
-
const tokens = accept
|
|
38
|
-
.split(',')
|
|
39
|
-
.map((token) => token.trim().toLowerCase())
|
|
40
|
-
.filter(Boolean);
|
|
41
|
-
if (tokens.length === 0) {
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
const name = file.name.toLowerCase();
|
|
45
|
-
const type = file.type.toLowerCase();
|
|
46
|
-
return tokens.some((token) => token.startsWith('.')
|
|
47
|
-
? name.endsWith(token)
|
|
48
|
-
: token.endsWith('/*')
|
|
49
|
-
? type.startsWith(token.slice(0, -1))
|
|
50
|
-
: type === token);
|
|
51
|
-
};
|
|
52
37
|
let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(HelixFormElement) {
|
|
53
38
|
constructor() {
|
|
54
39
|
super(...arguments);
|
|
@@ -57,15 +42,26 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
|
|
|
57
42
|
target: () => this.partNamed('control'),
|
|
58
43
|
});
|
|
59
44
|
this.accept = '';
|
|
45
|
+
this.directory = false;
|
|
60
46
|
this.locale = '';
|
|
61
47
|
this.multiple = false;
|
|
48
|
+
this.rejectedText = 'Not added: {names}';
|
|
62
49
|
this.removeLabel = 'Remove {name}';
|
|
63
50
|
this.triggerLabel = 'Choose files';
|
|
51
|
+
this.status = new StatusController(this);
|
|
52
|
+
this.handlePaste = (event) => {
|
|
53
|
+
const pasted = [...(event.clipboardData?.files ?? [])];
|
|
54
|
+
if (this.isDisabled || pasted.length === 0) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
this.take(pasted, this.multiple ? this.files : []);
|
|
59
|
+
};
|
|
64
60
|
this.dropping = false;
|
|
65
61
|
this.committed = [];
|
|
66
62
|
this.handleChange = (event) => {
|
|
67
63
|
event.stopPropagation();
|
|
68
|
-
this.
|
|
64
|
+
this.take(this.files, [], true);
|
|
69
65
|
};
|
|
70
66
|
this.handleDragOver = (event) => {
|
|
71
67
|
if (this.isDisabled) {
|
|
@@ -84,11 +80,11 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
|
|
|
84
80
|
return;
|
|
85
81
|
}
|
|
86
82
|
event.preventDefault();
|
|
87
|
-
const dropped = [...event.dataTransfer.files]
|
|
83
|
+
const dropped = [...event.dataTransfer.files];
|
|
88
84
|
if (dropped.length === 0) {
|
|
89
85
|
return;
|
|
90
86
|
}
|
|
91
|
-
this.
|
|
87
|
+
this.take(dropped, this.multiple ? this.files : []);
|
|
92
88
|
};
|
|
93
89
|
this.handleRemove = (event) => {
|
|
94
90
|
const at = Number(event.currentTarget.dataset.at);
|
|
@@ -102,6 +98,30 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
|
|
|
102
98
|
});
|
|
103
99
|
};
|
|
104
100
|
}
|
|
101
|
+
take(arriving, keeping, picked = false) {
|
|
102
|
+
const { kept, refused } = screenFiles(arriving, keeping, {
|
|
103
|
+
accept: this.accept,
|
|
104
|
+
maxFileSize: this.maxFileSize,
|
|
105
|
+
maxFiles: this.maxFiles,
|
|
106
|
+
multiple: this.multiple || picked,
|
|
107
|
+
validate: this.validate,
|
|
108
|
+
});
|
|
109
|
+
const same = (one, two) => one.length === two.length && one.every((file, at) => file === two[at]);
|
|
110
|
+
if (picked || !(same(kept, this.committed) && same(kept, this.files))) {
|
|
111
|
+
this.commit(kept);
|
|
112
|
+
}
|
|
113
|
+
if (refused.length > 0) {
|
|
114
|
+
this.refuse(refused);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
refuse(refused) {
|
|
118
|
+
this.dispatchEvent(new HelixRejectEvent({ files: refused }));
|
|
119
|
+
const words = fillTemplate(this.rejectedText, {
|
|
120
|
+
count: formatNumbers(this.lang)(refused.length),
|
|
121
|
+
names: new Intl.ListFormat(this.lang || undefined, { style: 'short', type: 'unit' }).format(refused.map((one) => one.file.name)),
|
|
122
|
+
});
|
|
123
|
+
this.status.say(words);
|
|
124
|
+
}
|
|
105
125
|
get files() {
|
|
106
126
|
return [...(this.control?.files ?? [])];
|
|
107
127
|
}
|
|
@@ -190,10 +210,10 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
|
|
|
190
210
|
}
|
|
191
211
|
renderMeta(file) {
|
|
192
212
|
return html `<${this.tag('tag')} part="size"
|
|
193
|
-
exportparts="dismiss: size-dismiss, dismiss-mark: size-dismiss-mark, label: size-label, start: size-start">${size(file.size, this.locale)}</${this.tag('tag')}>
|
|
213
|
+
exportparts="cell: size-cell, dismiss: size-dismiss, dismiss-mark: size-dismiss-mark, label: size-label, link: size-link, start: size-start">${size(file.size, this.locale)}</${this.tag('tag')}>
|
|
194
214
|
<${this.tag('tag')}
|
|
195
215
|
part="kind"
|
|
196
|
-
exportparts="dismiss: kind-dismiss, dismiss-mark: kind-dismiss-mark, label: kind-label, start: kind-start">${kind(file)}</${this.tag('tag')}>`;
|
|
216
|
+
exportparts="cell: kind-cell, dismiss: kind-dismiss, dismiss-mark: kind-dismiss-mark, label: kind-label, link: kind-link, start: kind-start">${kind(file)}</${this.tag('tag')}>`;
|
|
197
217
|
}
|
|
198
218
|
render() {
|
|
199
219
|
const files = this.files;
|
|
@@ -204,6 +224,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
|
|
|
204
224
|
@dragleave=${this.handleDragLeave}
|
|
205
225
|
@dragover=${this.handleDragOver}
|
|
206
226
|
@drop=${this.handleDrop}
|
|
227
|
+
@paste=${this.handlePaste}
|
|
207
228
|
>
|
|
208
229
|
<input
|
|
209
230
|
part="control"
|
|
@@ -217,6 +238,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
|
|
|
217
238
|
?disabled=${this.isDisabled}
|
|
218
239
|
?multiple=${this.multiple}
|
|
219
240
|
?required=${this.required}
|
|
241
|
+
?webkitdirectory=${this.directory}
|
|
220
242
|
@change=${this.handleChange}
|
|
221
243
|
@input=${this.stopInner}
|
|
222
244
|
/>
|
|
@@ -228,6 +250,7 @@ let HelixHeadlessFileInput = class HelixHeadlessFileInput extends MixinField(Hel
|
|
|
228
250
|
one ordering decision this component makes differently from the way it
|
|
229
251
|
reads. Reasoning above renderMeta. -->
|
|
230
252
|
${this.renderHint()}${this.renderFeedback()}
|
|
253
|
+
${drawStatus(this.status)}
|
|
231
254
|
${files.length === 0
|
|
232
255
|
? nothing
|
|
233
256
|
: html `<ul part="list">
|
|
@@ -258,18 +281,33 @@ __decorate([
|
|
|
258
281
|
__decorate([
|
|
259
282
|
property()
|
|
260
283
|
], HelixHeadlessFileInput.prototype, "capture", void 0);
|
|
284
|
+
__decorate([
|
|
285
|
+
property({ type: Boolean })
|
|
286
|
+
], HelixHeadlessFileInput.prototype, "directory", void 0);
|
|
261
287
|
__decorate([
|
|
262
288
|
property()
|
|
263
289
|
], HelixHeadlessFileInput.prototype, "locale", void 0);
|
|
290
|
+
__decorate([
|
|
291
|
+
property({ attribute: 'max-files', type: Number })
|
|
292
|
+
], HelixHeadlessFileInput.prototype, "maxFiles", void 0);
|
|
293
|
+
__decorate([
|
|
294
|
+
property({ attribute: 'max-file-size', type: Number })
|
|
295
|
+
], HelixHeadlessFileInput.prototype, "maxFileSize", void 0);
|
|
264
296
|
__decorate([
|
|
265
297
|
property({ reflect: true, type: Boolean })
|
|
266
298
|
], HelixHeadlessFileInput.prototype, "multiple", void 0);
|
|
299
|
+
__decorate([
|
|
300
|
+
property({ attribute: 'rejected-text' })
|
|
301
|
+
], HelixHeadlessFileInput.prototype, "rejectedText", void 0);
|
|
267
302
|
__decorate([
|
|
268
303
|
property({ attribute: 'remove-label' })
|
|
269
304
|
], HelixHeadlessFileInput.prototype, "removeLabel", void 0);
|
|
270
305
|
__decorate([
|
|
271
306
|
property({ attribute: 'trigger-label' })
|
|
272
307
|
], HelixHeadlessFileInput.prototype, "triggerLabel", void 0);
|
|
308
|
+
__decorate([
|
|
309
|
+
property({ attribute: false })
|
|
310
|
+
], HelixHeadlessFileInput.prototype, "validate", void 0);
|
|
273
311
|
__decorate([
|
|
274
312
|
state()
|
|
275
313
|
], HelixHeadlessFileInput.prototype, "dropping", void 0);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type RefusedFile } from '@fusion.dev/helix-core';
|
|
2
|
+
/**
|
|
3
|
+
* Which of the files arriving at a file field it takes, and why it refuses the
|
|
4
|
+
* rest: the rules `accept`, `max-file-size`, `max-files` and `validate` set.
|
|
5
|
+
* Zag's file upload screens a drop the same way and reports each refused file
|
|
6
|
+
* with its reason.
|
|
7
|
+
*/
|
|
8
|
+
export type { Refusal, RefusedFile } from '@fusion.dev/helix-core';
|
|
9
|
+
/** The rules a field screens arriving files by. */
|
|
10
|
+
export interface ScreenRules {
|
|
11
|
+
/** A native `accept` list. Empty takes every type. */
|
|
12
|
+
readonly accept: string;
|
|
13
|
+
/** The most bytes one file may have. */
|
|
14
|
+
readonly maxFileSize?: number | undefined;
|
|
15
|
+
/** The most files the field holds, counting the ones it has. */
|
|
16
|
+
readonly maxFiles?: number | undefined;
|
|
17
|
+
/** Whether the field holds more than one file. */
|
|
18
|
+
readonly multiple: boolean;
|
|
19
|
+
/** Your own check, which returns why a file is refused, or nothing. */
|
|
20
|
+
readonly validate?: ((file: File) => string | null | undefined) | undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Whether a file is one `accept` lists: an extension, a type, or a family
|
|
24
|
+
* such as `image/*`, as the platform reads it.
|
|
25
|
+
*/
|
|
26
|
+
export declare const accepts: (file: File, accept: string) => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Screens arriving files against the rules, given the files the field keeps.
|
|
29
|
+
* `kept` is what the field holds afterward: the files it keeps and the
|
|
30
|
+
* arrivals it took, or for a single-file field the last arrival it took.
|
|
31
|
+
*/
|
|
32
|
+
export declare const screenFiles: (arriving: File[], keeping: File[], rules: ScreenRules) => {
|
|
33
|
+
kept: File[];
|
|
34
|
+
refused: RefusedFile[];
|
|
35
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { commaList } from '@fusion.dev/helix-core';
|
|
2
|
+
export const accepts = (file, accept) => {
|
|
3
|
+
const tokens = commaList(accept).map((token) => token.toLowerCase());
|
|
4
|
+
if (tokens.length === 0) {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
const name = file.name.toLowerCase();
|
|
8
|
+
const type = file.type.toLowerCase();
|
|
9
|
+
return tokens.some((token) => token.startsWith('.')
|
|
10
|
+
? name.endsWith(token)
|
|
11
|
+
: token.endsWith('/*')
|
|
12
|
+
? type.startsWith(token.slice(0, -1))
|
|
13
|
+
: type === token);
|
|
14
|
+
};
|
|
15
|
+
const refusal = (file, rules) => {
|
|
16
|
+
if (!accepts(file, rules.accept)) {
|
|
17
|
+
return { file, reason: 'type' };
|
|
18
|
+
}
|
|
19
|
+
if (rules.maxFileSize !== undefined && file.size > rules.maxFileSize) {
|
|
20
|
+
return { file, reason: 'size' };
|
|
21
|
+
}
|
|
22
|
+
const message = rules.validate?.(file);
|
|
23
|
+
return message ? { file, message, reason: 'invalid' } : undefined;
|
|
24
|
+
};
|
|
25
|
+
export const screenFiles = (arriving, keeping, rules) => {
|
|
26
|
+
const refused = [];
|
|
27
|
+
const taken = [];
|
|
28
|
+
for (const file of arriving) {
|
|
29
|
+
const refusedFor = refusal(file, rules);
|
|
30
|
+
if (refusedFor) {
|
|
31
|
+
refused.push(refusedFor);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
taken.push(file);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!rules.multiple) {
|
|
38
|
+
const last = taken.at(-1);
|
|
39
|
+
for (const file of taken.slice(0, -1)) {
|
|
40
|
+
refused.push({ file, reason: 'count' });
|
|
41
|
+
}
|
|
42
|
+
return { kept: last ? [last] : keeping.slice(0, 1), refused };
|
|
43
|
+
}
|
|
44
|
+
const room = rules.maxFiles === undefined ? taken.length : Math.max(0, rules.maxFiles - keeping.length);
|
|
45
|
+
for (const file of taken.slice(room)) {
|
|
46
|
+
refused.push({ file, reason: 'count' });
|
|
47
|
+
}
|
|
48
|
+
return { kept: [...keeping, ...taken.slice(0, room)], refused };
|
|
49
|
+
};
|
|
@@ -66,8 +66,11 @@ import '../../menu/index.js';
|
|
|
66
66
|
* @csspart divider-content - The content set into the line.
|
|
67
67
|
* @csspart divider-line - The line itself.
|
|
68
68
|
* @csspart entry - One row: a field, its comparison, an operator, or a value.
|
|
69
|
+
* @csspart entry-end - The trailing column of a row, which holds its shortcut.
|
|
69
70
|
* @csspart entry-item - The row itself.
|
|
70
71
|
* @csspart entry-mark - The mark at the end of a row: a check mark on a chosen value, and a caret on a field.
|
|
72
|
+
* @csspart entry-shortcut - The key drawn for a row's shortcut. The filter menu sets none, so it renders only where a subclass does.
|
|
73
|
+
* @csspart entry-shortcut-kbd - The key inside it.
|
|
71
74
|
* @csspart entry-start - The leading column.
|
|
72
75
|
* @csspart menu - The menu the fields are offered in.
|
|
73
76
|
* @csspart menu-back - The button that goes back from a field's page.
|
|
@@ -137,7 +137,8 @@ let HelixHeadlessFilterMenu = class HelixHeadlessFilterMenu extends HelixElement
|
|
|
137
137
|
const offered = using?.arity === 'none' ? [] : values;
|
|
138
138
|
return html `<${item}
|
|
139
139
|
part="entry"
|
|
140
|
-
exportparts="item: entry-item, mark: entry-mark,
|
|
140
|
+
exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
|
|
141
|
+
shortcut-kbd: entry-shortcut-kbd, start: entry-start"
|
|
141
142
|
data-facet="field"
|
|
142
143
|
data-field=${name}
|
|
143
144
|
>
|
|
@@ -145,7 +146,8 @@ let HelixHeadlessFilterMenu = class HelixHeadlessFilterMenu extends HelixElement
|
|
|
145
146
|
${operators.length > 1
|
|
146
147
|
? html `<${item}
|
|
147
148
|
part="entry"
|
|
148
|
-
exportparts="item: entry-item, mark: entry-mark,
|
|
149
|
+
exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
|
|
150
|
+
shortcut-kbd: entry-shortcut-kbd, start: entry-start"
|
|
149
151
|
slot="submenu"
|
|
150
152
|
data-facet="comparison"
|
|
151
153
|
data-field=${name}
|
|
@@ -153,7 +155,8 @@ let HelixHeadlessFilterMenu = class HelixHeadlessFilterMenu extends HelixElement
|
|
|
153
155
|
${using?.label ?? ''}
|
|
154
156
|
${operators.map((operator) => html `<${item}
|
|
155
157
|
part="entry"
|
|
156
|
-
exportparts="item: entry-item, mark: entry-mark,
|
|
158
|
+
exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
|
|
159
|
+
shortcut-kbd: entry-shortcut-kbd, start: entry-start"
|
|
157
160
|
slot="submenu"
|
|
158
161
|
checkable
|
|
159
162
|
exclusive
|
|
@@ -175,7 +178,8 @@ let HelixHeadlessFilterMenu = class HelixHeadlessFilterMenu extends HelixElement
|
|
|
175
178
|
: nothing}
|
|
176
179
|
${offered.map((option) => html `<${item}
|
|
177
180
|
part="entry"
|
|
178
|
-
exportparts="item: entry-item, mark: entry-mark,
|
|
181
|
+
exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
|
|
182
|
+
shortcut-kbd: entry-shortcut-kbd, start: entry-start"
|
|
179
183
|
slot="submenu"
|
|
180
184
|
checkable
|
|
181
185
|
?exclusive=${using?.arity === 'one'}
|
|
@@ -13,8 +13,10 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
|
|
|
13
13
|
* its tokens show a selection it owns, drawn from the options it was given.
|
|
14
14
|
*
|
|
15
15
|
* `value` is the typed text. Every keystroke fires `input` with the text on the
|
|
16
|
-
* element, so a table can narrow as the user types.
|
|
17
|
-
*
|
|
16
|
+
* element, so a table can narrow as the user types. A form receives the text
|
|
17
|
+
* under `name`, and then one more entry under `name` for each token: its
|
|
18
|
+
* `value` attribute, or for a `FilterChip`, its field and values written
|
|
19
|
+
* `field:values` as the field reads them typed. Backspace in an empty field fires
|
|
18
20
|
* `helix-dismiss` on the last token, the same event its remove control fires,
|
|
19
21
|
* so one handler covers the pointer and the keyboard.
|
|
20
22
|
*
|
|
@@ -38,8 +40,11 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
|
|
|
38
40
|
* moves through them with the arrow keys, and presses one on Enter. The option
|
|
39
41
|
* fires its own `helix-select`, and you decide what choosing it means. Keys
|
|
40
42
|
* before a colon and values after it use the same panel with different
|
|
41
|
-
* contents. `
|
|
42
|
-
*
|
|
43
|
+
* contents. `filter-suggestions` narrows the panel to the options whose words
|
|
44
|
+
* hold the term at the caret, after its colon, ignoring case and accents, and
|
|
45
|
+
* hides the rest. `readFilterText` is exported so you can narrow it yourself
|
|
46
|
+
* against partly typed text. While the panel is open, a reader hears how many
|
|
47
|
+
* suggestions it offers. The panel opens when it has options and closes when it has
|
|
43
48
|
* none, and a choice does not close it, so after picking a key you see its
|
|
44
49
|
* values next. Use `selectionStart` and `setSelectionRange` to write a
|
|
45
50
|
* completion back and put the caret after it, or the next parse reads the wrong
|
|
@@ -93,6 +98,7 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
|
|
|
93
98
|
* @csspart hint - The help text under the field, shown while there is no error.
|
|
94
99
|
* @csspart label - The label above the field.
|
|
95
100
|
* @csspart lead - The box before the text that holds the `start` slot.
|
|
101
|
+
* @csspart live - The live region that says how many suggestions the open panel offers. It is rendered from the first time the panel opens with suggestions.
|
|
96
102
|
* @csspart rest - The count of tokens that did not fit. It is a `<button>` with `expandable` and a `<span>` without it.
|
|
97
103
|
* @csspart surface - The floating panel the suggestions sit in.
|
|
98
104
|
* @csspart tags - The row the tokens sit in.
|
|
@@ -118,12 +124,40 @@ export declare class HelixHeadlessFilterInput extends HelixHeadlessFilterInput_b
|
|
|
118
124
|
* an icon.
|
|
119
125
|
*/
|
|
120
126
|
clearLabel: string;
|
|
127
|
+
/**
|
|
128
|
+
* Whether the panel narrows itself to the suggestions that hold the term
|
|
129
|
+
* being typed, as React Aria's Autocomplete filters. Off, the panel shows
|
|
130
|
+
* what you slot and you narrow it.
|
|
131
|
+
*/
|
|
132
|
+
filterSuggestions: boolean;
|
|
121
133
|
/**
|
|
122
134
|
* The text shown while the field is empty.
|
|
123
135
|
*
|
|
124
136
|
* Not a label: it goes when typing starts.
|
|
125
137
|
*/
|
|
126
138
|
placeholder: string;
|
|
139
|
+
/** What a reader hears for one suggestion, with `{count}` filled in. */
|
|
140
|
+
suggestionLabel: string;
|
|
141
|
+
/** What a reader hears for any other number of suggestions, with `{count}` filled in. */
|
|
142
|
+
suggestionsLabel: string;
|
|
143
|
+
/** The live region, rendered from the first count said. */
|
|
144
|
+
private readonly status;
|
|
145
|
+
/** The count last said, so the same panel is said once. */
|
|
146
|
+
private saidCount;
|
|
147
|
+
/** The options this element hid while narrowing, so only those are shown again. */
|
|
148
|
+
private readonly narrowed;
|
|
149
|
+
/**
|
|
150
|
+
* Hides the slotted options whose words do not hold the term at the caret,
|
|
151
|
+
* under `filter-suggestions`, and shows again the ones it hid before.
|
|
152
|
+
*/
|
|
153
|
+
private narrow;
|
|
154
|
+
/** Says how many suggestions the open panel offers, when that changes. */
|
|
155
|
+
private sayCount;
|
|
156
|
+
/**
|
|
157
|
+
* What the form receives: the text, then one entry for each token that
|
|
158
|
+
* says what it filters. See the class description.
|
|
159
|
+
*/
|
|
160
|
+
private formValue;
|
|
127
161
|
/** How many tokens did not fit, which is what the row's count renders. */
|
|
128
162
|
private over;
|
|
129
163
|
/**
|
|
@@ -4,13 +4,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { AriaForwardController, customElement, drawIcon, HelixFacetEvent, HelixFormElement, holdCaret, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, PopoverController, restoreValue, scrollItemIntoView, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, countLabel, customElement, drawIcon, drawStatus, HelixFacetEvent, HelixFormElement, holdCaret, MixinControllerOpen, MixinControllerValue, MixinField, MixinTokens, PopoverController, restoreValue, StatusController, scrollItemIntoView, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { x } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
11
|
import { HelixHeadlessFilterField } from '../field/filter-field.js';
|
|
12
12
|
import { readFilterText } from '../grammar.js';
|
|
13
13
|
import { HelixHeadlessFilterMenu } from '../menu/filter-menu.js';
|
|
14
|
+
import { currentTerm, suggests, tokenEntry } from '../suggest.js';
|
|
14
15
|
const asksToGo = (node) => typeof node?.dismiss === 'function';
|
|
15
16
|
const isOption = (node) => 'active' in node && 'value' in node;
|
|
16
17
|
let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinControllerOpen(MixinTokens(MixinControllerValue(MixinField(HelixFormElement)))) {
|
|
@@ -22,7 +23,13 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
22
23
|
this.announcesInput = true;
|
|
23
24
|
this.clearable = false;
|
|
24
25
|
this.clearLabel = 'Clear';
|
|
26
|
+
this.filterSuggestions = false;
|
|
25
27
|
this.placeholder = '';
|
|
28
|
+
this.suggestionLabel = '{count} suggestion';
|
|
29
|
+
this.suggestionsLabel = '{count} suggestions';
|
|
30
|
+
this.status = new StatusController(this);
|
|
31
|
+
this.saidCount = -1;
|
|
32
|
+
this.narrowed = new Set();
|
|
26
33
|
this.over = 0;
|
|
27
34
|
this.activeAt = -1;
|
|
28
35
|
this.suggesting = false;
|
|
@@ -154,6 +161,51 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
154
161
|
this.over = this.hideOverflow(this.tokens);
|
|
155
162
|
};
|
|
156
163
|
}
|
|
164
|
+
narrow() {
|
|
165
|
+
const slot = this.renderRoot?.querySelector('slot[name="suggestions"]');
|
|
166
|
+
const term = this.filterSuggestions
|
|
167
|
+
? currentTerm(this.value, this.control?.selectionStart ?? this.value.length)
|
|
168
|
+
: '';
|
|
169
|
+
for (const node of slot?.assignedElements({ flatten: true }) ?? []) {
|
|
170
|
+
const options = isOption(node) ? [node] : [...node.children].filter(isOption);
|
|
171
|
+
for (const option of options) {
|
|
172
|
+
const hide = !suggests(option.textContent ?? '', term);
|
|
173
|
+
if (hide && !option.hidden) {
|
|
174
|
+
option.hidden = true;
|
|
175
|
+
this.narrowed.add(option);
|
|
176
|
+
}
|
|
177
|
+
else if (!hide && this.narrowed.has(option)) {
|
|
178
|
+
option.hidden = false;
|
|
179
|
+
this.narrowed.delete(option);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
sayCount() {
|
|
185
|
+
const count = this.open && this.suggesting ? this.suggestions.length : -1;
|
|
186
|
+
if (count === this.saidCount) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
this.saidCount = count;
|
|
190
|
+
if (count < 0) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
this.status.sayNow(countLabel(count, this.suggestionLabel, this.suggestionsLabel, this.lang));
|
|
194
|
+
}
|
|
195
|
+
formValue() {
|
|
196
|
+
const entries = (this.tokens?.assignedElements({ flatten: true }) ?? [])
|
|
197
|
+
.map(tokenEntry)
|
|
198
|
+
.filter((entry) => entry !== null);
|
|
199
|
+
if (this.name === '' || entries.length === 0) {
|
|
200
|
+
return this.value;
|
|
201
|
+
}
|
|
202
|
+
const data = new FormData();
|
|
203
|
+
data.append(this.name, this.value);
|
|
204
|
+
for (const entry of entries) {
|
|
205
|
+
data.append(this.name, entry);
|
|
206
|
+
}
|
|
207
|
+
return data;
|
|
208
|
+
}
|
|
157
209
|
static { this.shadowRootOptions = {
|
|
158
210
|
...HelixFormElement.shadowRootOptions,
|
|
159
211
|
delegatesFocus: true,
|
|
@@ -181,7 +233,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
181
233
|
}
|
|
182
234
|
}
|
|
183
235
|
}
|
|
184
|
-
return found.filter((option) => !option.disabled);
|
|
236
|
+
return found.filter((option) => !option.disabled && !option.hidden);
|
|
185
237
|
}
|
|
186
238
|
get selectionDirection() {
|
|
187
239
|
return this.control?.selectionDirection ?? 'none';
|
|
@@ -264,6 +316,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
264
316
|
from < 0 ? (by > 0 ? 0 : options.length - 1) : (from + by + options.length) % options.length;
|
|
265
317
|
}
|
|
266
318
|
refreshSuggesting() {
|
|
319
|
+
this.narrow();
|
|
267
320
|
const options = this.suggestions;
|
|
268
321
|
this.suggesting = options.length > 0;
|
|
269
322
|
this.activeAt = this.activeAt < options.length ? this.activeAt : -1;
|
|
@@ -311,7 +364,8 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
311
364
|
super.updated(changed);
|
|
312
365
|
this.releaseCaret?.();
|
|
313
366
|
this.releaseCaret = undefined;
|
|
314
|
-
this.internals.setFormValue(this.
|
|
367
|
+
this.internals.setFormValue(this.formValue());
|
|
368
|
+
this.sayCount();
|
|
315
369
|
const control = this.control;
|
|
316
370
|
if (control) {
|
|
317
371
|
this.copyValidity(control);
|
|
@@ -391,6 +445,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinContr
|
|
|
391
445
|
>
|
|
392
446
|
<slot name="suggestions" @slotchange=${this.handleSuggestions}></slot>
|
|
393
447
|
</div>
|
|
448
|
+
${drawStatus(this.status)}
|
|
394
449
|
${this.renderHint()}${this.renderFeedback()}`;
|
|
395
450
|
}
|
|
396
451
|
};
|
|
@@ -400,9 +455,18 @@ __decorate([
|
|
|
400
455
|
__decorate([
|
|
401
456
|
property({ attribute: 'clear-label' })
|
|
402
457
|
], HelixHeadlessFilterInput.prototype, "clearLabel", void 0);
|
|
458
|
+
__decorate([
|
|
459
|
+
property({ attribute: 'filter-suggestions', type: Boolean })
|
|
460
|
+
], HelixHeadlessFilterInput.prototype, "filterSuggestions", void 0);
|
|
403
461
|
__decorate([
|
|
404
462
|
property()
|
|
405
463
|
], HelixHeadlessFilterInput.prototype, "placeholder", void 0);
|
|
464
|
+
__decorate([
|
|
465
|
+
property({ attribute: 'suggestion-label' })
|
|
466
|
+
], HelixHeadlessFilterInput.prototype, "suggestionLabel", void 0);
|
|
467
|
+
__decorate([
|
|
468
|
+
property({ attribute: 'suggestions-label' })
|
|
469
|
+
], HelixHeadlessFilterInput.prototype, "suggestionsLabel", void 0);
|
|
406
470
|
__decorate([
|
|
407
471
|
state()
|
|
408
472
|
], HelixHeadlessFilterInput.prototype, "over", void 0);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a filter field narrows its suggestions by, and what it sends a form,
|
|
3
|
+
* kept apart from the element so each can be read and tested alone.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The term being typed at the caret: the run of text since the last space,
|
|
7
|
+
* and after its colon where it has one, so `status:ope` narrows by `ope`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const currentTerm: (text: string, caret?: number) => string;
|
|
10
|
+
/** Whether a suggestion's words hold the term, ignoring case and accents. */
|
|
11
|
+
export declare const suggests: (words: string, term: string) => boolean;
|
|
12
|
+
/**
|
|
13
|
+
* What a token sends a form: its `value` attribute, or for a token naming a
|
|
14
|
+
* field, the field and the values it shows, written `field:values` the way the
|
|
15
|
+
* field reads them typed. Nothing for a token that says neither.
|
|
16
|
+
*/
|
|
17
|
+
export declare const tokenEntry: (token: Element) => string | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const currentTerm = (text, caret = text.length) => {
|
|
2
|
+
const before = text.slice(0, caret);
|
|
3
|
+
const word = before.slice(before.search(/\S*$/u));
|
|
4
|
+
const colon = word.indexOf(':');
|
|
5
|
+
return (colon === -1 ? word : word.slice(colon + 1)).replace(/^-/u, '');
|
|
6
|
+
};
|
|
7
|
+
export const suggests = (words, term) => {
|
|
8
|
+
const plain = (text) => text.normalize('NFD').replace(/\p{M}/gu, '').toLowerCase();
|
|
9
|
+
return term === '' || plain(words).includes(plain(term));
|
|
10
|
+
};
|
|
11
|
+
export const tokenEntry = (token) => {
|
|
12
|
+
const written = token.getAttribute('value');
|
|
13
|
+
if (written !== null) {
|
|
14
|
+
return written;
|
|
15
|
+
}
|
|
16
|
+
const field = token.getAttribute('name');
|
|
17
|
+
const values = (token.textContent ?? '').replace(/\s+/gu, ' ').trim();
|
|
18
|
+
if (!field || values === '') {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const negated = token.getAttribute('operator')?.startsWith('not') ? '-' : '';
|
|
22
|
+
return `${negated}${field}:${values.replace(/\s*,\s*/gu, ',')}`;
|
|
23
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a Format Date was asked for, in its own spelling: the `Intl` options a
|
|
3
|
+
* consumer set, and whether the timestamp is a day with no time of day.
|
|
4
|
+
*/
|
|
5
|
+
export interface Asked {
|
|
6
|
+
dateOnly: boolean;
|
|
7
|
+
dateStyle?: 'full' | 'long' | 'medium' | 'short' | undefined;
|
|
8
|
+
day?: 'numeric' | '2-digit' | undefined;
|
|
9
|
+
era?: 'long' | 'narrow' | 'short' | undefined;
|
|
10
|
+
hour?: 'numeric' | '2-digit' | undefined;
|
|
11
|
+
hourCycle?: 'h11' | 'h12' | 'h23' | 'h24' | undefined;
|
|
12
|
+
minute?: 'numeric' | '2-digit' | undefined;
|
|
13
|
+
month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow' | undefined;
|
|
14
|
+
second?: 'numeric' | '2-digit' | undefined;
|
|
15
|
+
timeStyle?: 'full' | 'long' | 'medium' | 'short' | undefined;
|
|
16
|
+
timeZone?: string | undefined;
|
|
17
|
+
timeZoneName?: 'long' | 'short' | undefined;
|
|
18
|
+
weekday?: 'long' | 'short' | 'narrow' | undefined;
|
|
19
|
+
year?: 'numeric' | '2-digit' | undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The `Intl.DateTimeFormat` options for what was asked, and none that was not.
|
|
23
|
+
*
|
|
24
|
+
* **A style wins over the fields.** `dateStyle` and `timeStyle` are each
|
|
25
|
+
* locale's own whole pattern, and `Intl` throws a `TypeError` when one is
|
|
26
|
+
* given beside `month` or `hour`, so the fields are left out wherever a style
|
|
27
|
+
* is set. `hourCycle` and `timeZone` go with either.
|
|
28
|
+
*
|
|
29
|
+
* **A day with no time is read in UTC** unless a zone is given. Parsed, it is
|
|
30
|
+
* midnight UTC, and read in a zone west of it that is the day before: measured
|
|
31
|
+
* before this, `2026-08-04` read "August 3, 2026" in Los Angeles.
|
|
32
|
+
*/
|
|
33
|
+
export declare const formatOptions: (asked: Asked) => Intl.DateTimeFormatOptions;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const FIELDS = [
|
|
2
|
+
'day',
|
|
3
|
+
'era',
|
|
4
|
+
'hour',
|
|
5
|
+
'minute',
|
|
6
|
+
'month',
|
|
7
|
+
'second',
|
|
8
|
+
'timeZoneName',
|
|
9
|
+
'weekday',
|
|
10
|
+
'year',
|
|
11
|
+
];
|
|
12
|
+
export const formatOptions = (asked) => {
|
|
13
|
+
const options = {};
|
|
14
|
+
const styled = asked.dateStyle !== undefined || asked.timeStyle !== undefined;
|
|
15
|
+
if (styled) {
|
|
16
|
+
if (asked.dateStyle) {
|
|
17
|
+
options.dateStyle = asked.dateStyle;
|
|
18
|
+
}
|
|
19
|
+
if (asked.timeStyle) {
|
|
20
|
+
options.timeStyle = asked.timeStyle;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
for (const field of FIELDS) {
|
|
25
|
+
const value = asked[field];
|
|
26
|
+
if (value) {
|
|
27
|
+
;
|
|
28
|
+
options[field] = value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (asked.hourCycle) {
|
|
33
|
+
options.hourCycle = asked.hourCycle;
|
|
34
|
+
}
|
|
35
|
+
if (asked.timeZone) {
|
|
36
|
+
options.timeZone = asked.timeZone;
|
|
37
|
+
}
|
|
38
|
+
else if (asked.dateOnly) {
|
|
39
|
+
options.timeZone = 'UTC';
|
|
40
|
+
}
|
|
41
|
+
return options;
|
|
42
|
+
};
|