@zeyos/zx 2.1.0 → 2.2.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/CHANGELOG.md +39 -0
- package/README.md +15 -0
- package/dist/types/compat/base.d.ts +156 -0
- package/dist/types/compat/globals.d.ts +20 -0
- package/dist/types/compat/index.d.ts +128 -0
- package/dist/types/compat/map/basic.d.ts +175 -0
- package/dist/types/compat/map/data.d.ts +102 -0
- package/dist/types/compat/map/date-time.d.ts +80 -0
- package/dist/types/compat/map/factory.d.ts +9 -0
- package/dist/types/compat/map/form.d.ts +98 -0
- package/dist/types/compat/map/helpers.d.ts +12 -0
- package/dist/types/compat/map/http.d.ts +36 -0
- package/dist/types/compat/map/layout.d.ts +106 -0
- package/dist/types/compat/map/options.d.ts +80 -0
- package/dist/types/compat/map/select.d.ts +95 -0
- package/dist/types/compat/map/stubs.d.ts +32 -0
- package/dist/types/compat/map/utilities.d.ts +37 -0
- package/dist/types/compat-entry.d.ts +5 -0
- package/dist/types/components/badge/badge.d.ts +46 -0
- package/dist/types/components/breadcrumb/breadcrumb.d.ts +200 -0
- package/dist/types/components/button/button.d.ts +42 -0
- package/dist/types/components/check-button/check-button.d.ts +98 -0
- package/dist/types/components/checklist/checklist.d.ts +143 -0
- package/dist/types/components/checklist/field-adapter.d.ts +5 -0
- package/dist/types/components/context-menu/context-menu.d.ts +130 -0
- package/dist/types/components/copy/copy-button.d.ts +48 -0
- package/dist/types/components/copy/copy-input.d.ts +91 -0
- package/dist/types/components/data-filter/data-filter.d.ts +250 -0
- package/dist/types/components/data-filter/filter-core.d.ts +52 -0
- package/dist/types/components/date-picker/date-picker.d.ts +137 -0
- package/dist/types/components/date-picker/date-range-picker.d.ts +350 -0
- package/dist/types/components/date-picker/field-adapter.d.ts +6 -0
- package/dist/types/components/date-picker/month-picker.d.ts +75 -0
- package/dist/types/components/date-picker/range-field-adapter.d.ts +7 -0
- package/dist/types/components/date-picker/time-picker.d.ts +127 -0
- package/dist/types/components/datebox/date-range-box.d.ts +218 -0
- package/dist/types/components/datebox/datebox.d.ts +160 -0
- package/dist/types/components/datebox/field-adapter.d.ts +5 -0
- package/dist/types/components/dialog/dialog.d.ts +284 -0
- package/dist/types/components/dropdown/dropdown.d.ts +118 -0
- package/dist/types/components/empty-state/empty-state.d.ts +38 -0
- package/dist/types/components/field/field.d.ts +231 -0
- package/dist/types/components/field-upload/field-adapter.d.ts +5 -0
- package/dist/types/components/field-upload/field-upload.d.ts +213 -0
- package/dist/types/components/fieldset/fieldset.d.ts +125 -0
- package/dist/types/components/finder/finder.d.ts +252 -0
- package/dist/types/components/form/form.d.ts +194 -0
- package/dist/types/components/groupbox/groupbox.d.ts +89 -0
- package/dist/types/components/layout/layout.d.ts +132 -0
- package/dist/types/components/loading/inline-loading.d.ts +86 -0
- package/dist/types/components/loading/progress-bar.d.ts +168 -0
- package/dist/types/components/loading/spinner.d.ts +34 -0
- package/dist/types/components/master-panel/master-panel.d.ts +112 -0
- package/dist/types/components/menu-button/menu-button.d.ts +142 -0
- package/dist/types/components/message/message.d.ts +212 -0
- package/dist/types/components/modal/modal.d.ts +105 -0
- package/dist/types/components/multi-value-editor/field-adapter.d.ts +5 -0
- package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +66 -0
- package/dist/types/components/navigation-bar/navigation-bar.d.ts +229 -0
- package/dist/types/components/number-field/field-adapter.d.ts +5 -0
- package/dist/types/components/number-field/number-field.d.ts +292 -0
- package/dist/types/components/pagination/pagination.d.ts +267 -0
- package/dist/types/components/panel/panel.d.ts +194 -0
- package/dist/types/components/permission/permission.d.ts +93 -0
- package/dist/types/components/rating/field-adapter.d.ts +5 -0
- package/dist/types/components/rating/rating.d.ts +213 -0
- package/dist/types/components/search/search.d.ts +98 -0
- package/dist/types/components/select/field-adapter.d.ts +5 -0
- package/dist/types/components/select/filter.d.ts +11 -0
- package/dist/types/components/select/select.d.ts +235 -0
- package/dist/types/components/skeleton/skeleton.d.ts +97 -0
- package/dist/types/components/slider/field-adapter.d.ts +5 -0
- package/dist/types/components/slider/slider.d.ts +216 -0
- package/dist/types/components/split-view/split-view.d.ts +335 -0
- package/dist/types/components/stepper/stepper.d.ts +314 -0
- package/dist/types/components/stepper/steps.d.ts +131 -0
- package/dist/types/components/tabbox/tabbox.d.ts +230 -0
- package/dist/types/components/table/sort.d.ts +49 -0
- package/dist/types/components/table/table.d.ts +872 -0
- package/dist/types/components/tag-picker/field-adapter.d.ts +5 -0
- package/dist/types/components/tag-picker/tag-picker.d.ts +375 -0
- package/dist/types/components/timebox/field-adapter.d.ts +5 -0
- package/dist/types/components/timebox/timebox.d.ts +121 -0
- package/dist/types/components/toggle/field-adapter.d.ts +5 -0
- package/dist/types/components/toggle/toggle.d.ts +94 -0
- package/dist/types/components/toolbar/toolbar.d.ts +230 -0
- package/dist/types/components/tooltip/tooltip.d.ts +183 -0
- package/dist/types/components/tree/hierarchy.d.ts +113 -0
- package/dist/types/components/tree/tree.d.ts +383 -0
- package/dist/types/components/truncate/truncate.d.ts +61 -0
- package/dist/types/components/value-list/field-adapter.d.ts +5 -0
- package/dist/types/components/value-list/value-list.d.ts +143 -0
- package/dist/types/core/breakpoint.d.ts +68 -0
- package/dist/types/core/component.d.ts +115 -0
- package/dist/types/core/date.d.ts +50 -0
- package/dist/types/core/dom.d.ts +86 -0
- package/dist/types/core/export.d.ts +70 -0
- package/dist/types/core/fontawesome.d.ts +107 -0
- package/dist/types/core/format.d.ts +107 -0
- package/dist/types/core/http.d.ts +122 -0
- package/dist/types/core/i18n.d.ts +35 -0
- package/dist/types/core/icons.d.ts +137 -0
- package/dist/types/core/keyboard.d.ts +72 -0
- package/dist/types/core/position.d.ts +45 -0
- package/dist/types/core/storage.d.ts +46 -0
- package/dist/types/core/util.d.ts +117 -0
- package/dist/types/elements/define.d.ts +73 -0
- package/dist/types/elements/reflect.d.ts +27 -0
- package/dist/types/index.d.ts +79 -0
- package/dist/types/zeyos/connect.d.ts +65 -0
- package/dist/types/zeyos/form.d.ts +140 -0
- package/dist/types/zeyos/icons.d.ts +104 -0
- package/dist/types/zeyos/index.d.ts +12 -0
- package/dist/types/zeyos/modules.d.ts +110 -0
- package/dist/types/zeyos/query.d.ts +157 -0
- package/dist/types/zeyos/schema.d.ts +79 -0
- package/dist/types/zeyos/select.d.ts +167 -0
- package/dist/types/zeyos/table.d.ts +205 -0
- package/dist/zx-compat.esm.js +362 -33
- package/dist/zx-compat.esm.min.js +1 -1
- package/dist/zx-compat.global.js +359 -67
- package/dist/zx-compat.global.min.js +3 -3
- package/dist/zx-zeyos.esm.js +342 -43
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +68 -6
- package/dist/zx.esm.js +346 -67
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +346 -67
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +48 -2
- package/docs/llms.md +28 -0
- package/package.json +37 -9
- package/src/compat/base.js +291 -0
- package/src/compat/globals.js +165 -0
- package/src/compat/index.js +84 -0
- package/src/compat/map/basic.js +464 -0
- package/src/compat/map/data.js +232 -0
- package/src/compat/map/date-time.js +177 -0
- package/src/compat/map/factory.js +45 -0
- package/src/compat/map/form.js +330 -0
- package/src/compat/map/helpers.js +46 -0
- package/src/compat/map/http.js +117 -0
- package/src/compat/map/layout.js +256 -0
- package/src/compat/map/options.js +267 -0
- package/src/compat/map/select.js +198 -0
- package/src/compat/map/stubs.js +26 -0
- package/src/compat/map/utilities.js +113 -0
- package/src/compat-entry.js +11 -0
- package/src/components/badge/badge.css +147 -0
- package/src/components/badge/badge.js +92 -0
- package/src/components/breadcrumb/breadcrumb.css +105 -0
- package/src/components/breadcrumb/breadcrumb.js +331 -0
- package/src/components/button/button.css +104 -0
- package/src/components/button/button.js +76 -0
- package/src/components/check-button/check-button.css +66 -0
- package/src/components/check-button/check-button.js +167 -0
- package/src/components/checklist/checklist.css +65 -0
- package/src/components/checklist/checklist.js +282 -0
- package/src/components/checklist/field-adapter.js +46 -0
- package/src/components/context-menu/context-menu.css +54 -0
- package/src/components/context-menu/context-menu.js +334 -0
- package/src/components/copy/copy-button.css +27 -0
- package/src/components/copy/copy-button.js +78 -0
- package/src/components/copy/copy-input.css +59 -0
- package/src/components/copy/copy-input.js +123 -0
- package/src/components/data-filter/data-filter.css +53 -0
- package/src/components/data-filter/data-filter.js +400 -0
- package/src/components/data-filter/filter-core.js +128 -0
- package/src/components/date-picker/date-picker.css +181 -0
- package/src/components/date-picker/date-picker.js +500 -0
- package/src/components/date-picker/date-range-picker.css +264 -0
- package/src/components/date-picker/date-range-picker.js +990 -0
- package/src/components/date-picker/field-adapter.js +36 -0
- package/src/components/date-picker/month-picker.js +223 -0
- package/src/components/date-picker/range-field-adapter.js +31 -0
- package/src/components/date-picker/time-picker.css +191 -0
- package/src/components/date-picker/time-picker.js +406 -0
- package/src/components/datebox/date-range-box.css +128 -0
- package/src/components/datebox/date-range-box.js +492 -0
- package/src/components/datebox/datebox.css +108 -0
- package/src/components/datebox/datebox.js +358 -0
- package/src/components/datebox/field-adapter.js +34 -0
- package/src/components/dialog/dialog.css +74 -0
- package/src/components/dialog/dialog.js +450 -0
- package/src/components/dropdown/dropdown.css +24 -0
- package/src/components/dropdown/dropdown.js +288 -0
- package/src/components/empty-state/empty-state.css +80 -0
- package/src/components/empty-state/empty-state.js +92 -0
- package/src/components/field/field.css +127 -0
- package/src/components/field/field.js +605 -0
- package/src/components/field-upload/field-adapter.js +21 -0
- package/src/components/field-upload/field-upload.css +108 -0
- package/src/components/field-upload/field-upload.js +475 -0
- package/src/components/fieldset/fieldset.css +37 -0
- package/src/components/fieldset/fieldset.js +307 -0
- package/src/components/finder/finder.css +98 -0
- package/src/components/finder/finder.js +509 -0
- package/src/components/form/form.css +32 -0
- package/src/components/form/form.js +302 -0
- package/src/components/groupbox/groupbox.css +51 -0
- package/src/components/groupbox/groupbox.js +199 -0
- package/src/components/layout/layout.css +117 -0
- package/src/components/layout/layout.js +136 -0
- package/src/components/loading/inline-loading.css +34 -0
- package/src/components/loading/inline-loading.js +140 -0
- package/src/components/loading/progress-bar.css +115 -0
- package/src/components/loading/progress-bar.js +259 -0
- package/src/components/loading/spinner.css +58 -0
- package/src/components/loading/spinner.js +57 -0
- package/src/components/master-panel/master-panel.css +51 -0
- package/src/components/master-panel/master-panel.js +203 -0
- package/src/components/menu-button/menu-button.css +49 -0
- package/src/components/menu-button/menu-button.js +294 -0
- package/src/components/message/message.css +122 -0
- package/src/components/message/message.js +499 -0
- package/src/components/modal/modal.css +47 -0
- package/src/components/modal/modal.js +203 -0
- package/src/components/multi-value-editor/field-adapter.js +44 -0
- package/src/components/multi-value-editor/multi-value-editor.css +56 -0
- package/src/components/multi-value-editor/multi-value-editor.js +170 -0
- package/src/components/navigation-bar/navigation-bar.css +130 -0
- package/src/components/navigation-bar/navigation-bar.js +450 -0
- package/src/components/number-field/field-adapter.js +40 -0
- package/src/components/number-field/number-field.css +77 -0
- package/src/components/number-field/number-field.js +545 -0
- package/src/components/pagination/pagination.css +95 -0
- package/src/components/pagination/pagination.js +481 -0
- package/src/components/panel/panel.css +123 -0
- package/src/components/panel/panel.js +336 -0
- package/src/components/permission/permission.css +47 -0
- package/src/components/permission/permission.js +198 -0
- package/src/components/rating/field-adapter.js +34 -0
- package/src/components/rating/rating.css +97 -0
- package/src/components/rating/rating.js +395 -0
- package/src/components/search/search.css +70 -0
- package/src/components/search/search.js +193 -0
- package/src/components/select/field-adapter.js +24 -0
- package/src/components/select/filter.js +39 -0
- package/src/components/select/select.css +180 -0
- package/src/components/select/select.js +663 -0
- package/src/components/skeleton/skeleton.css +76 -0
- package/src/components/skeleton/skeleton.js +139 -0
- package/src/components/slider/field-adapter.js +36 -0
- package/src/components/slider/slider.css +181 -0
- package/src/components/slider/slider.js +399 -0
- package/src/components/split-view/split-view.css +139 -0
- package/src/components/split-view/split-view.js +806 -0
- package/src/components/stepper/stepper.css +234 -0
- package/src/components/stepper/stepper.js +459 -0
- package/src/components/stepper/steps.js +131 -0
- package/src/components/tabbox/tabbox.css +281 -0
- package/src/components/tabbox/tabbox.js +484 -0
- package/src/components/table/sort.js +85 -0
- package/src/components/table/table.css +364 -0
- package/src/components/table/table.js +1693 -0
- package/src/components/tag-picker/field-adapter.js +35 -0
- package/src/components/tag-picker/tag-picker.css +169 -0
- package/src/components/tag-picker/tag-picker.js +837 -0
- package/src/components/timebox/field-adapter.js +24 -0
- package/src/components/timebox/timebox.css +51 -0
- package/src/components/timebox/timebox.js +276 -0
- package/src/components/toggle/field-adapter.js +24 -0
- package/src/components/toggle/toggle.css +66 -0
- package/src/components/toggle/toggle.js +150 -0
- package/src/components/toolbar/toolbar.css +81 -0
- package/src/components/toolbar/toolbar.js +511 -0
- package/src/components/tooltip/tooltip.css +36 -0
- package/src/components/tooltip/tooltip.js +420 -0
- package/src/components/tree/hierarchy.js +151 -0
- package/src/components/tree/tree.css +99 -0
- package/src/components/tree/tree.js +784 -0
- package/src/components/truncate/truncate.css +20 -0
- package/src/components/truncate/truncate.js +92 -0
- package/src/components/value-list/field-adapter.js +24 -0
- package/src/components/value-list/value-list.css +108 -0
- package/src/components/value-list/value-list.js +336 -0
- package/src/core/breakpoint.js +150 -0
- package/src/core/component.js +292 -0
- package/src/core/date.js +216 -0
- package/src/core/dom.js +260 -0
- package/src/core/export.js +204 -0
- package/src/core/fontawesome.js +177 -0
- package/src/core/format.js +283 -0
- package/src/core/http.js +246 -0
- package/src/core/i18n.js +83 -0
- package/src/core/icons.js +283 -0
- package/src/core/keyboard.js +240 -0
- package/src/core/position.js +177 -0
- package/src/core/storage.js +175 -0
- package/src/core/util.js +336 -0
- package/src/elements/define.js +715 -0
- package/src/elements/reflect.js +79 -0
- package/src/index.js +131 -0
- package/src/zeyos/connect.js +142 -0
- package/src/zeyos/form.js +254 -0
- package/src/zeyos/icons.js +92 -0
- package/src/zeyos/index.js +21 -0
- package/src/zeyos/modules.js +269 -0
- package/src/zeyos/query.js +199 -0
- package/src/zeyos/schema.js +474 -0
- package/src/zeyos/select.js +175 -0
- package/src/zeyos/table.js +272 -0
- package/styles/base.css +124 -0
- package/styles/icons.css +34 -0
- package/styles/tokens/dark.css +84 -0
- package/styles/tokens/density.css +9 -0
- package/styles/tokens/global.css +80 -0
- package/styles/tokens/modules.css +109 -0
- package/styles/tokens/semantic.css +49 -0
- package/styles/zx.css +61 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
.zx-rating {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: var(--zx-space-3);
|
|
5
|
+
color: var(--zx-color-text);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.zx-rating__symbols {
|
|
9
|
+
display: inline-flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: 2px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.zx-rating__symbol {
|
|
15
|
+
--zx-rating-fill: 0%;
|
|
16
|
+
position: relative;
|
|
17
|
+
display: inline-grid;
|
|
18
|
+
line-height: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Both glyphs sit in the same grid cell; the "on" copy is clipped to the filled fraction, which
|
|
22
|
+
is what makes half steps work without a second icon. */
|
|
23
|
+
.zx-rating__glyph {
|
|
24
|
+
grid-area: 1 / 1;
|
|
25
|
+
color: var(--zx-color-border-strong);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.zx-rating__glyph[data-part="on"] {
|
|
29
|
+
color: var(--zx-color-warning);
|
|
30
|
+
clip-path: inset(0 calc(100% - var(--zx-rating-fill)) 0 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.zx-rating__symbol[data-preview] .zx-rating__glyph[data-part="on"] {
|
|
34
|
+
color: var(--zx-color-warning);
|
|
35
|
+
opacity: 0.75;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
39
|
+
.zx-rating__glyph[data-part="on"] {
|
|
40
|
+
transition: clip-path var(--zx-dur-fast) var(--zx-ease);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* One transparent hit area per selectable step, layered over the glyph. */
|
|
45
|
+
.zx-rating__radio {
|
|
46
|
+
position: absolute;
|
|
47
|
+
inset-block: 0;
|
|
48
|
+
z-index: 1;
|
|
49
|
+
min-block-size: 0;
|
|
50
|
+
padding: 0;
|
|
51
|
+
border: 0;
|
|
52
|
+
border-radius: var(--zx-radius-sm);
|
|
53
|
+
background: none;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.zx-rating__radio[data-part="full"] {
|
|
58
|
+
inset-inline: 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.zx-rating__radio[data-part="half"] {
|
|
62
|
+
inset-inline-start: 0;
|
|
63
|
+
inline-size: 50%;
|
|
64
|
+
z-index: 2;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.zx-rating__radio:hover {
|
|
68
|
+
background: none;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.zx-rating__radio:focus-visible {
|
|
72
|
+
outline: 0;
|
|
73
|
+
box-shadow: var(--zx-focus-ring);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.zx-rating[data-inert] .zx-rating__radio {
|
|
77
|
+
cursor: default;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.zx-rating[data-disabled] {
|
|
81
|
+
opacity: 0.55;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.zx-rating__readout {
|
|
85
|
+
color: var(--zx-color-text-muted);
|
|
86
|
+
font-size: var(--zx-text-sm);
|
|
87
|
+
font-variant-numeric: tabular-nums;
|
|
88
|
+
white-space: nowrap;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.zx-rating[data-size="sm"] .zx-rating__symbols {
|
|
92
|
+
gap: 1px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.zx-rating[data-size="lg"] .zx-rating__symbols {
|
|
96
|
+
gap: 4px;
|
|
97
|
+
}
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { Component } from '../../core/component.js';
|
|
2
|
+
import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
3
|
+
import { printf } from '../../core/i18n.js';
|
|
4
|
+
import { icon as createIcon } from '../../core/icons.js';
|
|
5
|
+
import { uid } from '../../core/util.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} RatingOptions
|
|
9
|
+
* @property {number} [value=0] Initial rating; 0 means unrated.
|
|
10
|
+
* @property {number} [max=5] Number of symbols.
|
|
11
|
+
* @property {boolean} [allowHalf=false] Whether half steps are selectable.
|
|
12
|
+
* @property {boolean} [clearable=true] Whether re-selecting the current value clears it.
|
|
13
|
+
* @property {boolean} [readonly=false] Whether the rating is display-only.
|
|
14
|
+
* @property {boolean} [disabled=false] Whether interaction is disabled.
|
|
15
|
+
* @property {string} [label='Rating'] Accessible name of the group.
|
|
16
|
+
* @property {string} [icon='star'] Icon name used for each symbol.
|
|
17
|
+
* @property {string[]|null} [labels=null] Per-step accessible names, lowest first.
|
|
18
|
+
* @property {boolean} [showValue=false] Whether to render the numeric value beside the symbols.
|
|
19
|
+
* @property {number|null} [count=null] Optional rating count rendered beside the value.
|
|
20
|
+
* @property {'sm'|'md'|'lg'} [size='md'] Symbol size.
|
|
21
|
+
* @property {Record<string, string>|Record<string, Record<string, string>>} [msg] Localized messages.
|
|
22
|
+
* @property {(event: CustomEvent<{value: number}>) => void} [onchange] Change listener.
|
|
23
|
+
* @property {(event: CustomEvent<{value: number|null}>) => void} [onhover] Hover-preview listener.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Star-rating control implementing the APG radio-group pattern: one radio per selectable step,
|
|
28
|
+
* a single tab stop, and arrow-key navigation. Half steps add a second radio per symbol rather
|
|
29
|
+
* than bending the radio semantics, so every reachable value has a real, nameable control.
|
|
30
|
+
*
|
|
31
|
+
* @fires Rating#change
|
|
32
|
+
* @fires Rating#hover
|
|
33
|
+
* @extends {Component<RatingOptions>}
|
|
34
|
+
*/
|
|
35
|
+
export class Rating extends Component {
|
|
36
|
+
static cssName = 'rating';
|
|
37
|
+
|
|
38
|
+
/** @type {RatingOptions} */
|
|
39
|
+
static defaults = {
|
|
40
|
+
value: 0,
|
|
41
|
+
max: 5,
|
|
42
|
+
allowHalf: false,
|
|
43
|
+
clearable: true,
|
|
44
|
+
readonly: false,
|
|
45
|
+
disabled: false,
|
|
46
|
+
label: 'Rating',
|
|
47
|
+
icon: 'star',
|
|
48
|
+
labels: null,
|
|
49
|
+
showValue: false,
|
|
50
|
+
count: null,
|
|
51
|
+
size: 'md'
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** @returns {HTMLElement} */
|
|
55
|
+
render() {
|
|
56
|
+
// render() runs inside the base constructor, before class-field initializers would run.
|
|
57
|
+
this._value = 0;
|
|
58
|
+
this._preview = null;
|
|
59
|
+
this._disabled = false;
|
|
60
|
+
this._readonly = false;
|
|
61
|
+
this._radios = [];
|
|
62
|
+
this._createdRoot = this.el === null;
|
|
63
|
+
this._snapshot = this._createdRoot ? null : snapshotTarget(this.el);
|
|
64
|
+
|
|
65
|
+
const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
|
|
66
|
+
this.el = root;
|
|
67
|
+
|
|
68
|
+
this._max = Math.max(1, Math.trunc(Number(this.options.max) || 5));
|
|
69
|
+
this._step = this.options.allowHalf ? 0.5 : 1;
|
|
70
|
+
this._disabled = Boolean(this.options.disabled);
|
|
71
|
+
this._readonly = Boolean(this.options.readonly);
|
|
72
|
+
this._name = uid('zx-rating');
|
|
73
|
+
|
|
74
|
+
const symbols = h('div', {
|
|
75
|
+
ref: 'symbols',
|
|
76
|
+
class: 'zx-rating__symbols',
|
|
77
|
+
role: 'radiogroup',
|
|
78
|
+
ariaLabel: String(this.options.label ?? 'Rating')
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
for (let index = 1; index <= this._max; index += 1) {
|
|
82
|
+
const symbol = h('span', { class: 'zx-rating__symbol' },
|
|
83
|
+
h('span', { class: 'zx-rating__glyph', ariaHidden: 'true' },
|
|
84
|
+
createIcon(this.options.icon, { size: SIZES[this.options.size] ?? SIZES.md })),
|
|
85
|
+
h('span', { class: 'zx-rating__glyph', dataset: { part: 'on' }, ariaHidden: 'true' },
|
|
86
|
+
createIcon(this.options.icon, { size: SIZES[this.options.size] ?? SIZES.md }))
|
|
87
|
+
);
|
|
88
|
+
if (this.options.allowHalf) {
|
|
89
|
+
symbol.append(this._radio(index - 0.5, 'half'), this._radio(index, 'full'));
|
|
90
|
+
} else {
|
|
91
|
+
symbol.append(this._radio(index, 'full'));
|
|
92
|
+
}
|
|
93
|
+
symbols.append(symbol);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// The readout is always rendered (empty when there is nothing to show) so `setCount()` can
|
|
97
|
+
// start displaying a count on a component created without one.
|
|
98
|
+
root.replaceChildren(symbols, h('span', { ref: 'readout', class: 'zx-rating__readout' }));
|
|
99
|
+
root.dataset.size = SIZES[this.options.size] ? this.options.size : 'md';
|
|
100
|
+
|
|
101
|
+
this.listen(symbols, 'keydown', (event) => this._onKeydown(/** @type {KeyboardEvent} */ (event)));
|
|
102
|
+
this.listen(symbols, 'pointerleave', () => this._setPreview(null));
|
|
103
|
+
|
|
104
|
+
this.set(this.options.value, { silent: true });
|
|
105
|
+
this._syncDisabled();
|
|
106
|
+
return root;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Current rating. @returns {number} */
|
|
110
|
+
get value() {
|
|
111
|
+
return this._value;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** @param {number} next Next rating. */
|
|
115
|
+
set value(next) {
|
|
116
|
+
this.set(next);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Returns the current rating.
|
|
121
|
+
* @returns {number}
|
|
122
|
+
*/
|
|
123
|
+
get() {
|
|
124
|
+
return this._value;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Sets the rating, snapped to the configured step and clamped to `[0, max]`.
|
|
129
|
+
* @param {number} next Next rating; 0 clears it.
|
|
130
|
+
* @param {{silent?: boolean}} [options={}] Set `silent` to suppress `change`.
|
|
131
|
+
* @returns {this}
|
|
132
|
+
* @fires Rating#change
|
|
133
|
+
*/
|
|
134
|
+
set(next, { silent = false } = {}) {
|
|
135
|
+
const value = this._clamp(next);
|
|
136
|
+
const changed = value !== this._value;
|
|
137
|
+
this._value = value;
|
|
138
|
+
// Drop any hover/focus preview so the glyphs show the value that was just set.
|
|
139
|
+
this._preview = null;
|
|
140
|
+
this._sync();
|
|
141
|
+
if (changed && !silent) this.emit('change', { value: this._value });
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Clears the rating.
|
|
147
|
+
* @param {{silent?: boolean}} [options={}] Set `silent` to suppress `change`.
|
|
148
|
+
* @returns {this}
|
|
149
|
+
*/
|
|
150
|
+
clear({ silent = false } = {}) {
|
|
151
|
+
return this.set(0, { silent });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Restores the configured initial value.
|
|
156
|
+
* @param {{silent?: boolean}} [options={}] Reset behavior.
|
|
157
|
+
* @returns {this}
|
|
158
|
+
*/
|
|
159
|
+
reset({ silent = false } = {}) {
|
|
160
|
+
return this.set(this.options.value, { silent });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Replaces the rating count shown beside the value.
|
|
165
|
+
* @param {number|null} count Number of ratings, or null to hide it.
|
|
166
|
+
* @returns {this}
|
|
167
|
+
*/
|
|
168
|
+
setCount(count) {
|
|
169
|
+
this._count = count;
|
|
170
|
+
this._syncReadout();
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Focuses the selected step, or the first one when unrated. @returns {this} */
|
|
175
|
+
focus() {
|
|
176
|
+
const active = this._radios.find((radio) => radio.tabIndex === 0) ?? this._radios[0];
|
|
177
|
+
active?.focus();
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Enables user interaction. @returns {this} */
|
|
182
|
+
enable() {
|
|
183
|
+
this._disabled = false;
|
|
184
|
+
this._syncDisabled();
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Disables user interaction. @returns {this} */
|
|
189
|
+
disable() {
|
|
190
|
+
this._disabled = true;
|
|
191
|
+
this._syncDisabled();
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Sets the read-only state.
|
|
197
|
+
* @param {boolean} readonly Whether the rating is display-only.
|
|
198
|
+
* @returns {this}
|
|
199
|
+
*/
|
|
200
|
+
setReadonly(readonly) {
|
|
201
|
+
this._readonly = Boolean(readonly);
|
|
202
|
+
this._syncDisabled();
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Builds one selectable step.
|
|
208
|
+
* @param {number} value Step value.
|
|
209
|
+
* @param {'half'|'full'} part Which half of the symbol the control covers.
|
|
210
|
+
* @returns {HTMLButtonElement}
|
|
211
|
+
*/
|
|
212
|
+
_radio(value, part) {
|
|
213
|
+
const button = /** @type {HTMLButtonElement} */ (h('button', {
|
|
214
|
+
class: 'zx-rating__radio',
|
|
215
|
+
type: 'button',
|
|
216
|
+
role: 'radio',
|
|
217
|
+
tabIndex: -1,
|
|
218
|
+
ariaChecked: 'false',
|
|
219
|
+
ariaLabel: this._stepLabel(value),
|
|
220
|
+
dataset: { value: String(value), part }
|
|
221
|
+
}));
|
|
222
|
+
this.listen(button, 'click', (event) => {
|
|
223
|
+
// `detail` is 0 for keyboard-synthesized clicks; only a real pointer press may clear.
|
|
224
|
+
this._choose(value, /** @type {MouseEvent} */ (event).detail > 0);
|
|
225
|
+
});
|
|
226
|
+
this.listen(button, 'pointerenter', () => this._setPreview(value));
|
|
227
|
+
this.listen(button, 'focus', () => this._setPreview(value));
|
|
228
|
+
this.listen(button, 'blur', () => this._setPreview(null));
|
|
229
|
+
this._radios.push(button);
|
|
230
|
+
return button;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* @param {number} value Step value.
|
|
235
|
+
* @returns {string}
|
|
236
|
+
*/
|
|
237
|
+
_stepLabel(value) {
|
|
238
|
+
const labels = this.options.labels;
|
|
239
|
+
if (Array.isArray(labels)) {
|
|
240
|
+
const index = Math.round(value / this._step) - 1;
|
|
241
|
+
if (labels[index] != null) return String(labels[index]);
|
|
242
|
+
}
|
|
243
|
+
return this._message('rating.value', '%1 of %2', [value, this._max]);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Resolves a localized message, falling back to a built-in template.
|
|
248
|
+
* @param {string} key Message key.
|
|
249
|
+
* @param {string} fallback Template used when the host has no translation.
|
|
250
|
+
* @param {unknown[]} args Positional interpolation values.
|
|
251
|
+
* @returns {string}
|
|
252
|
+
*/
|
|
253
|
+
_message(key, fallback, args) {
|
|
254
|
+
const message = this.msg(key, ...args);
|
|
255
|
+
return message === key ? printf(fallback, args) : message;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Applies an activation, honoring `clearable`.
|
|
260
|
+
* @param {number} value Step value.
|
|
261
|
+
* @param {boolean} allowClear Whether re-selecting the current value may clear it. Keyboard
|
|
262
|
+
* activation never clears, because the APG radio pattern requires Space on an already
|
|
263
|
+
* checked radio to do nothing.
|
|
264
|
+
* @returns {void}
|
|
265
|
+
*/
|
|
266
|
+
_choose(value, allowClear) {
|
|
267
|
+
if (this._disabled || this._readonly) return;
|
|
268
|
+
if (allowClear && this.options.clearable && value === this._value) this.set(0);
|
|
269
|
+
else this.set(value);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** @param {KeyboardEvent} event @returns {void} */
|
|
273
|
+
_onKeydown(event) {
|
|
274
|
+
if (this._disabled || this._readonly) return;
|
|
275
|
+
// APG radio group: Right/Down move to the next radio, Left/Up to the previous, both wrapping.
|
|
276
|
+
const forward = ['ArrowRight', 'ArrowDown'];
|
|
277
|
+
const backward = ['ArrowLeft', 'ArrowUp'];
|
|
278
|
+
if (forward.includes(event.key)) {
|
|
279
|
+
event.preventDefault();
|
|
280
|
+
this.set(this._value >= this._max ? this._step : (this._value || 0) + this._step);
|
|
281
|
+
} else if (backward.includes(event.key)) {
|
|
282
|
+
event.preventDefault();
|
|
283
|
+
this.set(this._value <= this._step ? this._max : this._value - this._step);
|
|
284
|
+
} else if (event.key === 'Home') {
|
|
285
|
+
event.preventDefault();
|
|
286
|
+
this.set(this._step);
|
|
287
|
+
} else if (event.key === 'End') {
|
|
288
|
+
event.preventDefault();
|
|
289
|
+
this.set(this._max);
|
|
290
|
+
} else if ((event.key === 'Delete' || event.key === 'Backspace') && this.options.clearable) {
|
|
291
|
+
event.preventDefault();
|
|
292
|
+
this.set(0);
|
|
293
|
+
} else {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
this.focus();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @param {number|null} value Previewed value, or null to drop the preview.
|
|
301
|
+
* @returns {void}
|
|
302
|
+
*/
|
|
303
|
+
_setPreview(value) {
|
|
304
|
+
const next = this._disabled || this._readonly ? null : value;
|
|
305
|
+
if (next === this._preview) return;
|
|
306
|
+
this._preview = next;
|
|
307
|
+
this._paint();
|
|
308
|
+
this.emit('hover', { value: this._preview });
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** @param {unknown} raw @returns {number} */
|
|
312
|
+
_clamp(raw) {
|
|
313
|
+
const value = Number(raw);
|
|
314
|
+
if (!Number.isFinite(value) || value <= 0) return 0;
|
|
315
|
+
const snapped = Math.round(value / this._step) * this._step;
|
|
316
|
+
return Math.min(this._max, Math.max(0, snapped));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** @returns {void} */
|
|
320
|
+
_sync() {
|
|
321
|
+
for (const radio of this._radios) {
|
|
322
|
+
const value = Number(radio.dataset.value);
|
|
323
|
+
radio.setAttribute('aria-checked', String(value === this._value));
|
|
324
|
+
}
|
|
325
|
+
// APG radio group: exactly one tab stop, on the checked step or the first one when unrated.
|
|
326
|
+
const checked = this._radios.find((radio) => radio.getAttribute('aria-checked') === 'true');
|
|
327
|
+
for (const radio of this._radios) radio.tabIndex = -1;
|
|
328
|
+
(checked ?? this._radios[0]).tabIndex = 0;
|
|
329
|
+
this._paint();
|
|
330
|
+
this._syncReadout();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Paints the filled portion of every symbol from the preview or the value. @returns {void} */
|
|
334
|
+
_paint() {
|
|
335
|
+
const shown = this._preview ?? this._value;
|
|
336
|
+
const symbols = this.refs.symbols.querySelectorAll('.zx-rating__symbol');
|
|
337
|
+
symbols.forEach((symbol, index) => {
|
|
338
|
+
const filled = Math.min(1, Math.max(0, shown - index));
|
|
339
|
+
symbol.style.setProperty('--zx-rating-fill', `${filled * 100}%`);
|
|
340
|
+
symbol.toggleAttribute('data-preview', this._preview !== null);
|
|
341
|
+
});
|
|
342
|
+
this.el.dataset.value = String(shown);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** @returns {void} */
|
|
346
|
+
_syncReadout() {
|
|
347
|
+
const readout = this.refs.readout;
|
|
348
|
+
if (!readout) return;
|
|
349
|
+
const count = this._count === undefined ? this.options.count : this._count;
|
|
350
|
+
const parts = [];
|
|
351
|
+
if (this.options.showValue) parts.push(formatValue(this._value, this._step));
|
|
352
|
+
if (count !== null && count !== undefined) {
|
|
353
|
+
parts.push(this._message('rating.count', '%1 ratings', [count]));
|
|
354
|
+
}
|
|
355
|
+
readout.textContent = parts.join(' · ');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** @returns {void} */
|
|
359
|
+
_syncDisabled() {
|
|
360
|
+
const inert = this._disabled || this._readonly;
|
|
361
|
+
for (const radio of this._radios) radio.disabled = this._disabled;
|
|
362
|
+
this.refs.symbols.setAttribute('aria-readonly', String(this._readonly));
|
|
363
|
+
this.el.toggleAttribute('data-disabled', this._disabled);
|
|
364
|
+
this.el.toggleAttribute('data-readonly', this._readonly);
|
|
365
|
+
this.el.toggleAttribute('data-inert', inert);
|
|
366
|
+
if (this._disabled) this.el.setAttribute('aria-disabled', 'true');
|
|
367
|
+
else this.el.removeAttribute('aria-disabled');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Restores an enhanced target to the markup it had before the takeover. @returns {void} */
|
|
371
|
+
destroy() {
|
|
372
|
+
const root = this.el;
|
|
373
|
+
super.destroy();
|
|
374
|
+
if (!this._createdRoot && root) restoreTarget(root, this._snapshot);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Rating change.
|
|
380
|
+
* @event Rating#change
|
|
381
|
+
* @type {CustomEvent<{value: number}>}
|
|
382
|
+
*/
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Hover or focus preview; `value` is null when the pointer leaves the group.
|
|
386
|
+
* @event Rating#hover
|
|
387
|
+
* @type {CustomEvent<{value: number|null}>}
|
|
388
|
+
*/
|
|
389
|
+
|
|
390
|
+
const SIZES = { sm: 14, md: 18, lg: 24 };
|
|
391
|
+
|
|
392
|
+
/** @param {number} value @param {number} step @returns {string} */
|
|
393
|
+
function formatValue(value, step) {
|
|
394
|
+
return step < 1 ? value.toFixed(1) : String(value);
|
|
395
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
.zx-search {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
align-items: stretch;
|
|
5
|
+
min-block-size: var(--zx-control-height);
|
|
6
|
+
border: 1px solid var(--zx-color-border-control);
|
|
7
|
+
border-radius: var(--zx-control-radius);
|
|
8
|
+
background: var(--zx-color-bg-control);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.zx-search:focus-within {
|
|
12
|
+
box-shadow: var(--zx-focus-ring);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* The input reserves room for the clear button, which floats over its trailing edge, so showing
|
|
16
|
+
or hiding the button never changes the control's total width. */
|
|
17
|
+
.zx-search__input {
|
|
18
|
+
min-inline-size: 12rem;
|
|
19
|
+
min-block-size: 0;
|
|
20
|
+
border: 0;
|
|
21
|
+
background: none;
|
|
22
|
+
padding-inline: var(--zx-control-pad-x) var(--zx-control-height);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.zx-search__input:focus-visible,
|
|
26
|
+
.zx-search__clear:focus-visible,
|
|
27
|
+
.zx-search__submit:focus-visible {
|
|
28
|
+
box-shadow: var(--zx-focus-ring);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.zx-search__input::-webkit-search-cancel-button {
|
|
32
|
+
appearance: none;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.zx-search__clear,
|
|
36
|
+
.zx-search__submit {
|
|
37
|
+
display: inline-grid;
|
|
38
|
+
place-items: center;
|
|
39
|
+
min-block-size: 0;
|
|
40
|
+
inline-size: var(--zx-control-height);
|
|
41
|
+
border: 0;
|
|
42
|
+
border-radius: 0;
|
|
43
|
+
background: none;
|
|
44
|
+
color: var(--zx-color-text-muted);
|
|
45
|
+
padding: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.zx-search__clear:hover,
|
|
49
|
+
.zx-search__submit:hover {
|
|
50
|
+
color: var(--zx-color-text);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.zx-search__submit:hover {
|
|
54
|
+
background: var(--zx-color-bg-hover);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.zx-search__submit {
|
|
58
|
+
border-start-end-radius: var(--zx-control-radius);
|
|
59
|
+
border-end-end-radius: var(--zx-control-radius);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.zx-search__clear {
|
|
63
|
+
position: absolute;
|
|
64
|
+
inset-block: 0;
|
|
65
|
+
inset-inline-end: var(--zx-control-height);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.zx-search__clear[hidden] {
|
|
69
|
+
display: none;
|
|
70
|
+
}
|