@expressivecss/expressive 0.11.0 → 0.13.1
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 +392 -7
- package/README.md +37 -1
- package/dist/css/expressive.css +5440 -234
- package/dist/css/expressive.css.map +1 -1
- package/dist/css/expressive.min.css +1 -1
- package/dist/css/expressive.min.css.map +1 -1
- package/dist/fonts/material-symbols-outlined.woff2 +0 -0
- package/dist/fonts/material-symbols-rounded.woff2 +0 -0
- package/dist/fonts/material-symbols-sharp.woff2 +0 -0
- package/dist/js/chunks/{chunk-AYYYWUJ4.mjs → chunk-3CLXLPYS.mjs} +17 -11
- package/dist/js/chunks/chunk-3CLXLPYS.mjs.map +7 -0
- package/dist/js/chunks/chunk-3DHK5CZU.mjs +105 -0
- package/dist/js/chunks/chunk-3DHK5CZU.mjs.map +7 -0
- package/dist/js/chunks/{chunk-IMUPRRI6.mjs → chunk-3VOK6J7U.mjs} +3 -3
- package/dist/js/chunks/chunk-4PWAPV6F.mjs +7 -0
- package/dist/js/chunks/chunk-4PWAPV6F.mjs.map +7 -0
- package/dist/js/chunks/{chunk-XFJ452AZ.mjs → chunk-55IA2XUC.mjs} +5 -6
- package/dist/js/chunks/{chunk-XFJ452AZ.mjs.map → chunk-55IA2XUC.mjs.map} +2 -2
- package/dist/js/chunks/{chunk-XDB3G6RL.mjs → chunk-6N2XAEFK.mjs} +15 -4
- package/dist/js/chunks/{chunk-XDB3G6RL.mjs.map → chunk-6N2XAEFK.mjs.map} +2 -2
- package/dist/js/chunks/{chunk-MISGHDUU.mjs → chunk-6OMRGML6.mjs} +9 -7
- package/dist/js/chunks/{chunk-MISGHDUU.mjs.map → chunk-6OMRGML6.mjs.map} +2 -2
- package/dist/js/chunks/{chunk-A3BJA7V4.mjs → chunk-6WLSDZ4O.mjs} +2 -2
- package/dist/js/chunks/chunk-CCWDV5CR.mjs +292 -0
- package/dist/js/chunks/chunk-CCWDV5CR.mjs.map +7 -0
- package/dist/js/chunks/{chunk-JODJAGFS.mjs → chunk-CKQP6P76.mjs} +31 -8
- package/dist/js/chunks/chunk-CKQP6P76.mjs.map +7 -0
- package/dist/js/chunks/{chunk-KR44BSYQ.mjs → chunk-DLWYKFEI.mjs} +3 -3
- package/dist/js/chunks/chunk-DTKR54I6.mjs +270 -0
- package/dist/js/chunks/chunk-DTKR54I6.mjs.map +7 -0
- package/dist/js/chunks/{chunk-SLFNPY5M.mjs → chunk-EC63CHXU.mjs} +2 -2
- package/dist/js/chunks/chunk-GVGT6MIU.mjs +47 -0
- package/dist/js/chunks/chunk-GVGT6MIU.mjs.map +7 -0
- package/dist/js/chunks/{chunk-LZVBJ33S.mjs → chunk-GZWPXXY6.mjs} +15 -8
- package/dist/js/chunks/chunk-GZWPXXY6.mjs.map +7 -0
- package/dist/js/chunks/chunk-HKT2S7VM.mjs +48 -0
- package/dist/js/chunks/chunk-HKT2S7VM.mjs.map +7 -0
- package/dist/js/chunks/{chunk-D4UZ4HFN.mjs → chunk-IFXVDDDH.mjs} +23 -11
- package/dist/js/chunks/chunk-IFXVDDDH.mjs.map +7 -0
- package/dist/js/chunks/chunk-K4CJCRGE.mjs +262 -0
- package/dist/js/chunks/chunk-K4CJCRGE.mjs.map +7 -0
- package/dist/js/chunks/chunk-LNCCYGPC.mjs +163 -0
- package/dist/js/chunks/chunk-LNCCYGPC.mjs.map +7 -0
- package/dist/js/chunks/{chunk-QSQ24FYT.mjs → chunk-LTWPGFBR.mjs} +2 -2
- package/dist/js/chunks/{chunk-QSQ24FYT.mjs.map → chunk-LTWPGFBR.mjs.map} +2 -2
- package/dist/js/chunks/{chunk-I5KR22BY.mjs → chunk-LWBQIX5J.mjs} +15 -15
- package/dist/js/chunks/chunk-LWBQIX5J.mjs.map +7 -0
- package/dist/js/chunks/{chunk-GY2Z5CJ2.mjs → chunk-NNYP33NB.mjs} +67 -32
- package/dist/js/chunks/chunk-NNYP33NB.mjs.map +7 -0
- package/dist/js/chunks/chunk-NQLC3YMW.mjs +1 -0
- package/dist/js/chunks/chunk-O5XPWXIQ.mjs +406 -0
- package/dist/js/chunks/chunk-O5XPWXIQ.mjs.map +7 -0
- package/dist/js/chunks/chunk-P3G3QHII.mjs +166 -0
- package/dist/js/chunks/chunk-P3G3QHII.mjs.map +7 -0
- package/dist/js/chunks/{chunk-VAS6JQ6P.mjs → chunk-PIJ2TAR3.mjs} +3 -3
- package/dist/js/chunks/{chunk-74N6U5JJ.mjs → chunk-PLYXLSH3.mjs} +38 -37
- package/dist/js/chunks/chunk-PLYXLSH3.mjs.map +7 -0
- package/dist/js/chunks/{chunk-PIIAEYAJ.mjs → chunk-QDAL4IYN.mjs} +2 -2
- package/dist/js/chunks/chunk-QKWPQK5N.mjs +179 -0
- package/dist/js/chunks/chunk-QKWPQK5N.mjs.map +7 -0
- package/dist/js/chunks/{chunk-FRB7TOCS.mjs → chunk-QV2JAWIZ.mjs} +2 -2
- package/dist/js/chunks/{chunk-N3UHGN3N.mjs → chunk-QVVEWBOF.mjs} +3 -3
- package/dist/js/chunks/chunk-RHRQKTTX.mjs +139 -0
- package/dist/js/chunks/chunk-RHRQKTTX.mjs.map +7 -0
- package/dist/js/chunks/chunk-SPETGK25.mjs +59 -0
- package/dist/js/chunks/chunk-SPETGK25.mjs.map +7 -0
- package/dist/js/chunks/{chunk-TK6PX2GC.mjs → chunk-U2OTHOTI.mjs} +131 -19
- package/dist/js/chunks/chunk-U2OTHOTI.mjs.map +7 -0
- package/dist/js/chunks/{chunk-TYM6U5DY.mjs → chunk-UAEAQBRC.mjs} +2 -2
- package/dist/js/chunks/{chunk-7UXHSYIA.mjs → chunk-UBLRRJ7N.mjs} +3 -3
- package/dist/js/chunks/{chunk-EP3CVKB5.mjs → chunk-UEZUJQ66.mjs} +8 -5
- package/dist/js/chunks/chunk-UEZUJQ66.mjs.map +7 -0
- package/dist/js/chunks/{chunk-JNPIWFJT.mjs → chunk-UNEMYWEW.mjs} +3 -3
- package/dist/js/chunks/chunk-VTLBW7QS.mjs +124 -0
- package/dist/js/chunks/chunk-VTLBW7QS.mjs.map +7 -0
- package/dist/js/chunks/{chunk-JKNPQQWY.mjs → chunk-WJXDBPXQ.mjs} +28 -12
- package/dist/js/chunks/chunk-WJXDBPXQ.mjs.map +7 -0
- package/dist/js/chunks/{chunk-CDW6WIRT.mjs → chunk-XGHORT7J.mjs} +18 -11
- package/dist/js/chunks/chunk-XGHORT7J.mjs.map +7 -0
- package/dist/js/chunks/{chunk-7GKXUG4B.mjs → chunk-XH6BEEMY.mjs} +78 -25
- package/dist/js/chunks/chunk-XH6BEEMY.mjs.map +7 -0
- package/dist/js/chunks/{chunk-XO5WX3TK.mjs → chunk-YDCS5S3J.mjs} +2 -2
- package/dist/js/expressive.cjs +2540 -233
- package/dist/js/expressive.cjs.map +4 -4
- package/dist/js/expressive.js +2540 -233
- package/dist/js/expressive.js.map +4 -4
- package/dist/js/expressive.min.js +10 -10
- package/dist/js/expressive.min.js.map +4 -4
- package/dist/js/expressive.mjs +86 -38
- package/dist/js/expressive.mjs.map +1 -1
- package/dist/js/internal/behaviors/bottomSheets.mjs +3 -3
- package/dist/js/internal/behaviors/dialogs.mjs +2 -2
- package/dist/js/internal/behaviors/forms.mjs +2 -2
- package/dist/js/internal/behaviors/sheetDrag.mjs +2 -2
- package/dist/js/internal/behaviors/sideSheets.mjs +3 -3
- package/dist/js/internal/components/appBar.mjs +2 -2
- package/dist/js/internal/components/autocomplete.mjs +4 -4
- package/dist/js/internal/components/barChart.mjs +10 -0
- package/dist/js/internal/components/barChart.mjs.map +7 -0
- package/dist/js/internal/components/buttonGroup.mjs +2 -2
- package/dist/js/internal/components/buttons.mjs +3 -3
- package/dist/js/internal/components/cards.mjs +3 -3
- package/dist/js/internal/components/carousel.mjs +3 -3
- package/dist/js/internal/components/characterCounter.mjs +2 -2
- package/dist/js/internal/components/chips.mjs +5 -5
- package/dist/js/internal/components/columnChart.mjs +9 -0
- package/dist/js/internal/components/columnChart.mjs.map +7 -0
- package/dist/js/internal/components/commandPalette.mjs +9 -0
- package/dist/js/internal/components/commandPalette.mjs.map +7 -0
- package/dist/js/internal/components/datepicker.mjs +6 -6
- package/dist/js/internal/components/expandingCard.mjs +3 -3
- package/dist/js/internal/components/heatmapChart.mjs +9 -0
- package/dist/js/internal/components/heatmapChart.mjs.map +7 -0
- package/dist/js/internal/components/index.mjs +79 -31
- package/dist/js/internal/components/lightbox.mjs +3 -3
- package/dist/js/internal/components/lineChart.mjs +12 -0
- package/dist/js/internal/components/lineChart.mjs.map +7 -0
- package/dist/js/internal/components/menu.mjs +3 -3
- package/dist/js/internal/components/messageScroller.mjs +9 -0
- package/dist/js/internal/components/messageScroller.mjs.map +7 -0
- package/dist/js/internal/components/mixedChart.mjs +10 -0
- package/dist/js/internal/components/mixedChart.mjs.map +7 -0
- package/dist/js/internal/components/navigationRail.mjs +2 -2
- package/dist/js/internal/components/pieChart.mjs +9 -0
- package/dist/js/internal/components/pieChart.mjs.map +7 -0
- package/dist/js/internal/components/questionnaire.mjs +9 -0
- package/dist/js/internal/components/questionnaire.mjs.map +7 -0
- package/dist/js/internal/components/radarChart.mjs +9 -0
- package/dist/js/internal/components/radarChart.mjs.map +7 -0
- package/dist/js/internal/components/radialChart.mjs +9 -0
- package/dist/js/internal/components/radialChart.mjs.map +7 -0
- package/dist/js/internal/components/registry.mjs +37 -25
- package/dist/js/internal/components/richTextEditor.mjs +9 -0
- package/dist/js/internal/components/richTextEditor.mjs.map +7 -0
- package/dist/js/internal/components/scrollspy.mjs +2 -2
- package/dist/js/internal/components/select.mjs +4 -4
- package/dist/js/internal/components/slider.mjs +3 -3
- package/dist/js/internal/components/snackbar.mjs +2 -2
- package/dist/js/internal/components/tabs.mjs +4 -4
- package/dist/js/internal/components/timepicker.mjs +4 -4
- package/dist/js/internal/components/tooltip.mjs +3 -3
- package/dist/js/internal/core/component.mjs +1 -1
- package/dist/js/internal/core/utils.mjs +1 -1
- package/dist/js/internal/plugins/dockedDisplayPlugin.mjs +2 -2
- package/dist/js/modular.mjs +86 -38
- package/dist/types/behaviors/forms.d.ts +22 -0
- package/dist/types/components/appBar.d.ts +1 -1
- package/dist/types/components/barChart.d.ts +24 -0
- package/dist/types/components/columnChart.d.ts +21 -0
- package/dist/types/components/commandPalette.d.ts +96 -0
- package/dist/types/components/datepicker.d.ts +5 -2
- package/dist/types/components/heatmapChart.d.ts +49 -0
- package/dist/types/components/index.d.ts +12 -0
- package/dist/types/components/lineChart.d.ts +134 -0
- package/dist/types/components/menu.d.ts +32 -0
- package/dist/types/components/messageScroller.d.ts +111 -0
- package/dist/types/components/mixedChart.d.ts +25 -0
- package/dist/types/components/pieChart.d.ts +40 -0
- package/dist/types/components/questionnaire.d.ts +96 -0
- package/dist/types/components/radarChart.d.ts +39 -0
- package/dist/types/components/radialChart.d.ts +45 -0
- package/dist/types/components/registry.d.ts +44 -0
- package/dist/types/components/richTextEditor.d.ts +71 -0
- package/dist/types/components/slider.d.ts +1 -0
- package/dist/types/components/snackbar.d.ts +6 -0
- package/dist/types/components/timepicker.d.ts +4 -1
- package/dist/types/components/tooltip.d.ts +2 -1
- package/dist/types/modular.d.ts +10 -1
- package/package.json +4 -1
- package/src/sass/README.md +3 -6
- package/src/sass/abstracts/_index.scss +1 -1
- package/src/sass/abstracts/_mixins.scss +226 -0
- package/src/sass/abstracts/_variables.scss +14 -0
- package/src/sass/base/_typography.scss +45 -0
- package/src/sass/components/_accordion.scss +161 -0
- package/src/sass/components/_aura.scss +119 -0
- package/src/sass/components/_avatar.scss +86 -0
- package/src/sass/components/_bar-chart.scss +285 -0
- package/src/sass/components/_bottom-sheet.scss +1 -0
- package/src/sass/components/_button-groups.scss +13 -3
- package/src/sass/components/_cards.scss +16 -13
- package/src/sass/components/_carousel.scss +9 -7
- package/src/sass/components/_column-chart.scss +134 -0
- package/src/sass/components/_command-palette.scss +140 -0
- package/src/sass/components/_countdown.scss +89 -0
- package/src/sass/components/_data-table.scss +188 -0
- package/src/sass/components/_dialog.scss +1 -0
- package/src/sass/components/_empty-state.scss +78 -0
- package/src/sass/components/_expanding-card.scss +3 -0
- package/src/sass/components/_heatmap-chart.scss +156 -0
- package/src/sass/components/_index.scss +27 -0
- package/src/sass/components/_kpi.scss +162 -0
- package/src/sass/components/_line-chart.scss +102 -0
- package/src/sass/components/_marquee.scss +111 -0
- package/src/sass/components/_menu.scss +10 -0
- package/src/sass/components/_message-scroller.scss +66 -0
- package/src/sass/components/_message.scss +202 -0
- package/src/sass/components/_mixed-chart.scss +157 -0
- package/src/sass/components/_navbar.scss +11 -6
- package/src/sass/components/_navigation-bar.scss +1 -0
- package/src/sass/components/_navigation-rail.scss +9 -0
- package/src/sass/components/_page-footer.scss +0 -11
- package/src/sass/components/_pie-chart.scss +203 -0
- package/src/sass/components/_popover.scss +112 -0
- package/src/sass/components/_progress.scss +25 -6
- package/src/sass/components/_questionnaire.scss +144 -0
- package/src/sass/components/_radar-chart.scss +205 -0
- package/src/sass/components/_radial-chart.scss +174 -0
- package/src/sass/components/_rich-text-editor.scss +99 -0
- package/src/sass/components/_search.scss +1 -0
- package/src/sass/components/_side-sheet.scss +1 -0
- package/src/sass/components/_skeleton.scss +81 -0
- package/src/sass/components/_snackbar.scss +2 -1
- package/src/sass/components/_stat.scss +86 -0
- package/src/sass/components/_stepper.scss +190 -0
- package/src/sass/components/_timeline.scss +114 -0
- package/src/sass/components/_timepicker.scss +2 -2
- package/src/sass/components/_toolbar.scss +2 -1
- package/src/sass/components/_tooltip.scss +2 -0
- package/src/sass/components/_tree.scss +139 -0
- package/src/sass/components/forms/_checkboxes.scss +2 -2
- package/src/sass/components/forms/_drop-zone.scss +128 -0
- package/src/sass/components/forms/_index.scss +2 -0
- package/src/sass/components/forms/_input-fields.scss +166 -15
- package/src/sass/components/forms/_radio-buttons.scss +2 -2
- package/src/sass/components/forms/_rating.scss +165 -0
- package/src/sass/components/forms/_slider.scss +51 -29
- package/src/sass/components/forms/_switches.scss +3 -3
- package/src/sass/tokens/_reference.scss +3 -15
- package/src/sass/utilities/_helpers.scss +14 -0
- package/src/sass/utilities/_index.scss +2 -0
- package/src/sass/utilities/_scroll-area.scss +35 -0
- package/src/sass/utilities/_shapes.scss +164 -0
- package/src/sass/utilities/_spacing.scss +29 -23
- package/src/sass/utilities/_visibility.scss +3 -2
- package/dist/js/chunks/chunk-74N6U5JJ.mjs.map +0 -7
- package/dist/js/chunks/chunk-7GKXUG4B.mjs.map +0 -7
- package/dist/js/chunks/chunk-AYYYWUJ4.mjs.map +0 -7
- package/dist/js/chunks/chunk-CDW6WIRT.mjs.map +0 -7
- package/dist/js/chunks/chunk-D4UZ4HFN.mjs.map +0 -7
- package/dist/js/chunks/chunk-EP3CVKB5.mjs.map +0 -7
- package/dist/js/chunks/chunk-GY2Z5CJ2.mjs.map +0 -7
- package/dist/js/chunks/chunk-I5KR22BY.mjs.map +0 -7
- package/dist/js/chunks/chunk-JKNPQQWY.mjs.map +0 -7
- package/dist/js/chunks/chunk-JODJAGFS.mjs.map +0 -7
- package/dist/js/chunks/chunk-JR24UB4X.mjs +0 -100
- package/dist/js/chunks/chunk-JR24UB4X.mjs.map +0 -7
- package/dist/js/chunks/chunk-LZVBJ33S.mjs.map +0 -7
- package/dist/js/chunks/chunk-PMBPAFAS.mjs +0 -7
- package/dist/js/chunks/chunk-PMBPAFAS.mjs.map +0 -7
- package/dist/js/chunks/chunk-TK6PX2GC.mjs.map +0 -7
- package/dist/js/chunks/chunk-WWPAPHMA.mjs +0 -1
- /package/dist/js/chunks/{chunk-IMUPRRI6.mjs.map → chunk-3VOK6J7U.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-A3BJA7V4.mjs.map → chunk-6WLSDZ4O.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-KR44BSYQ.mjs.map → chunk-DLWYKFEI.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-SLFNPY5M.mjs.map → chunk-EC63CHXU.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-WWPAPHMA.mjs.map → chunk-NQLC3YMW.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-VAS6JQ6P.mjs.map → chunk-PIJ2TAR3.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-PIIAEYAJ.mjs.map → chunk-QDAL4IYN.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-FRB7TOCS.mjs.map → chunk-QV2JAWIZ.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-N3UHGN3N.mjs.map → chunk-QVVEWBOF.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-TYM6U5DY.mjs.map → chunk-UAEAQBRC.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-7UXHSYIA.mjs.map → chunk-UBLRRJ7N.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-JNPIWFJT.mjs.map → chunk-UNEMYWEW.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-XO5WX3TK.mjs.map → chunk-YDCS5S3J.mjs.map} +0 -0
package/dist/js/expressive.cjs
CHANGED
|
@@ -22,22 +22,34 @@ __export(index_exports, {
|
|
|
22
22
|
AppBar: () => AppBar,
|
|
23
23
|
AutoInit: () => AutoInit,
|
|
24
24
|
Autocomplete: () => Autocomplete,
|
|
25
|
+
BarChart: () => BarChart,
|
|
25
26
|
BottomSheets: () => BottomSheets,
|
|
26
27
|
ButtonGroup: () => ButtonGroup,
|
|
27
28
|
Cards: () => Cards,
|
|
28
29
|
Carousel: () => Carousel,
|
|
29
30
|
CharacterCounter: () => CharacterCounter,
|
|
30
31
|
Chips: () => Chips,
|
|
32
|
+
ColumnChart: () => ColumnChart,
|
|
33
|
+
CommandPalette: () => CommandPalette,
|
|
31
34
|
Datepicker: () => Datepicker,
|
|
32
35
|
Dialogs: () => Dialogs,
|
|
33
36
|
ExpandingCard: () => ExpandingCard,
|
|
34
37
|
FloatingActionButton: () => FloatingActionButton,
|
|
35
38
|
FormSelect: () => FormSelect,
|
|
36
39
|
Forms: () => Forms,
|
|
40
|
+
HeatmapChart: () => HeatmapChart,
|
|
37
41
|
Lightbox: () => Lightbox,
|
|
42
|
+
LineChart: () => LineChart,
|
|
38
43
|
Menu: () => Menu,
|
|
44
|
+
MessageScroller: () => MessageScroller,
|
|
45
|
+
MixedChart: () => MixedChart,
|
|
39
46
|
NavigationRail: () => NavigationRail,
|
|
47
|
+
PieChart: () => PieChart,
|
|
48
|
+
Questionnaire: () => Questionnaire,
|
|
49
|
+
RadarChart: () => RadarChart,
|
|
50
|
+
RadialChart: () => RadialChart,
|
|
40
51
|
Range: () => Slider,
|
|
52
|
+
RichTextEditor: () => RichTextEditor,
|
|
41
53
|
ScrollSpy: () => ScrollSpy,
|
|
42
54
|
SideSheets: () => SideSheets,
|
|
43
55
|
Slider: () => Slider,
|
|
@@ -271,12 +283,7 @@ var _Utils = class _Utils {
|
|
|
271
283
|
button.type = "button";
|
|
272
284
|
button.tabIndex = !!visibility ? 0 : -1;
|
|
273
285
|
button.innerText = text;
|
|
274
|
-
if (typeof callback === "function")
|
|
275
|
-
button.addEventListener("click", callback);
|
|
276
|
-
button.addEventListener("keypress", (e) => {
|
|
277
|
-
if (e.key === _Utils.keys.ENTER) callback(e);
|
|
278
|
-
});
|
|
279
|
-
}
|
|
286
|
+
if (typeof callback === "function") button.addEventListener("click", callback);
|
|
280
287
|
container.append(button);
|
|
281
288
|
}
|
|
282
289
|
static _setAbsolutePosition(origin, container, position, margin, transitionMovement, align = "center") {
|
|
@@ -315,6 +322,13 @@ var _Utils = class _Utils {
|
|
|
315
322
|
transitionMovement,
|
|
316
323
|
align
|
|
317
324
|
);
|
|
325
|
+
const parent = container.offsetParent;
|
|
326
|
+
if (parent && (parent !== document.body || getComputedStyle(parent).position !== "static")) {
|
|
327
|
+
const rect = parent.getBoundingClientRect();
|
|
328
|
+
const root = parent === document.scrollingElement;
|
|
329
|
+
newCoordinates.x -= rect.left + _Utils.getDocumentScrollLeft() + parent.clientLeft - (root ? 0 : parent.scrollLeft);
|
|
330
|
+
newCoordinates.y -= rect.top + _Utils.getDocumentScrollTop() + parent.clientTop - (root ? 0 : parent.scrollTop);
|
|
331
|
+
}
|
|
318
332
|
container.style.top = newCoordinates.y + "px";
|
|
319
333
|
container.style.left = newCoordinates.x + "px";
|
|
320
334
|
return { x: xMovement, y: yMovement };
|
|
@@ -396,7 +410,7 @@ var Component = class {
|
|
|
396
410
|
let instances = null;
|
|
397
411
|
if (els instanceof Element) {
|
|
398
412
|
instances = new classDef(els, options);
|
|
399
|
-
} else if (
|
|
413
|
+
} else if (els) {
|
|
400
414
|
instances = [];
|
|
401
415
|
for (let i = 0; i < els.length; i++) {
|
|
402
416
|
instances.push(new classDef(els[i], options));
|
|
@@ -770,6 +784,7 @@ var _defaults3 = {
|
|
|
770
784
|
coverTrigger: false,
|
|
771
785
|
closeOnClick: true,
|
|
772
786
|
hover: false,
|
|
787
|
+
contextMenu: false,
|
|
773
788
|
inDuration: 150,
|
|
774
789
|
outDuration: 250,
|
|
775
790
|
onOpenStart: null,
|
|
@@ -784,6 +799,41 @@ var _Menu = class _Menu extends Component {
|
|
|
784
799
|
super(el, options, _Menu);
|
|
785
800
|
this._pendingTimers = /* @__PURE__ */ new Set();
|
|
786
801
|
this._transition = 0;
|
|
802
|
+
this._menuClickTargets = /* @__PURE__ */ new WeakMap();
|
|
803
|
+
/**
|
|
804
|
+
* Viewport point the open context menu belongs to. Null outside a
|
|
805
|
+
* right-click, so a keyboard or programmatic open uses the region's corner
|
|
806
|
+
* rather than an old pointer position.
|
|
807
|
+
*/
|
|
808
|
+
this._contextPoint = null;
|
|
809
|
+
/** What had focus when a context menu opened; focus goes back there. */
|
|
810
|
+
this._contextReturn = null;
|
|
811
|
+
this._handleContextMenu = (e) => {
|
|
812
|
+
if (e.defaultPrevented) {
|
|
813
|
+
this.close();
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
const path = e.composedPath();
|
|
817
|
+
if (_Menu._menus.some((menu) => menu !== this && menu.isOpen && path.includes(menu.menuEl))) return;
|
|
818
|
+
e.preventDefault();
|
|
819
|
+
if (!this.isOpen) {
|
|
820
|
+
const root = this.el.getRootNode();
|
|
821
|
+
this._contextReturn = root.activeElement instanceof HTMLElement ? root.activeElement : null;
|
|
822
|
+
}
|
|
823
|
+
const rect = this.el.getBoundingClientRect();
|
|
824
|
+
const fromPointer = (e.clientX !== 0 || e.clientY !== 0) && e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
|
|
825
|
+
this._contextPoint = fromPointer ? { x: e.clientX, y: e.clientY } : null;
|
|
826
|
+
if (this.isOpen) this.recalculateDimensions();
|
|
827
|
+
else this.open();
|
|
828
|
+
};
|
|
829
|
+
this._handleDocumentContextMenu = (e) => {
|
|
830
|
+
const path = e.composedPath();
|
|
831
|
+
if (path.includes(this.menuEl)) {
|
|
832
|
+
e.preventDefault();
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if (!path.includes(this.el)) this.close();
|
|
836
|
+
};
|
|
787
837
|
this._handleClick = (e) => {
|
|
788
838
|
e.preventDefault();
|
|
789
839
|
if (this.isOpen) {
|
|
@@ -812,11 +862,15 @@ var _Menu = class _Menu extends Component {
|
|
|
812
862
|
}
|
|
813
863
|
};
|
|
814
864
|
this._handleDocumentClick = (e) => {
|
|
815
|
-
const
|
|
865
|
+
const path = e.composedPath();
|
|
866
|
+
const menuTarget = this._menuClickTargets.get(e);
|
|
867
|
+
this._menuClickTargets.delete(e);
|
|
868
|
+
const target = menuTarget ?? (path[0] || e.target);
|
|
869
|
+
const inside = !!menuTarget || path.includes(this.menuEl) || this.menuEl?.contains(target);
|
|
816
870
|
if (this._isSubmenuTriggerClick(target)) return;
|
|
817
|
-
if (this.options.closeOnClick &&
|
|
871
|
+
if (this.options.closeOnClick && inside && !this.isTouchMoving) {
|
|
818
872
|
this.close();
|
|
819
|
-
} else if (!
|
|
873
|
+
} else if (!inside) {
|
|
820
874
|
this._schedule(() => {
|
|
821
875
|
if (this.isOpen) {
|
|
822
876
|
this.close();
|
|
@@ -826,6 +880,7 @@ var _Menu = class _Menu extends Component {
|
|
|
826
880
|
this.isTouchMoving = false;
|
|
827
881
|
};
|
|
828
882
|
this._handleTriggerKeydown = (e) => {
|
|
883
|
+
if (this.options.contextMenu) return;
|
|
829
884
|
const opensMenu = e.key === SPACE || e.key === Utils.keys.ENTER || e.key === Utils.keys.ARROW_DOWN || e.key === Utils.keys.ARROW_UP;
|
|
830
885
|
if (opensMenu && !this.isOpen) {
|
|
831
886
|
e.preventDefault();
|
|
@@ -834,12 +889,13 @@ var _Menu = class _Menu extends Component {
|
|
|
834
889
|
};
|
|
835
890
|
this._handleDocumentTouchmove = (e) => {
|
|
836
891
|
const target = e.target;
|
|
837
|
-
if (this.menuEl?.contains(target)) {
|
|
892
|
+
if (e.composedPath().includes(this.menuEl) || this.menuEl?.contains(target)) {
|
|
838
893
|
this.isTouchMoving = true;
|
|
839
894
|
}
|
|
840
895
|
};
|
|
841
896
|
this._handleMenuClick = (e) => {
|
|
842
897
|
const target = e.target;
|
|
898
|
+
this._menuClickTargets.set(e, target);
|
|
843
899
|
const li = target.closest("li");
|
|
844
900
|
if (li?.matches('.disabled, [aria-disabled="true"]')) {
|
|
845
901
|
e.preventDefault();
|
|
@@ -983,7 +1039,7 @@ var _Menu = class _Menu extends Component {
|
|
|
983
1039
|
this._placeMenu();
|
|
984
1040
|
this._schedule(() => this._setupTemporaryEventHandlers(), 0);
|
|
985
1041
|
this._animateIn();
|
|
986
|
-
this.
|
|
1042
|
+
this._setExpanded(true);
|
|
987
1043
|
};
|
|
988
1044
|
/**
|
|
989
1045
|
* Close menu.
|
|
@@ -1005,10 +1061,13 @@ var _Menu = class _Menu extends Component {
|
|
|
1005
1061
|
if (transition !== this._transition) return;
|
|
1006
1062
|
this._animateOut();
|
|
1007
1063
|
this._removeTemporaryEventHandlers();
|
|
1008
|
-
this.
|
|
1009
|
-
if (this.options.autoFocus && (root.activeElement === focused ||
|
|
1010
|
-
this.
|
|
1064
|
+
this._setExpanded(false);
|
|
1065
|
+
if (this.options.autoFocus && this.menuEl.contains(focused) && (root.activeElement === focused || this.el.ownerDocument.activeElement === this.el.ownerDocument.body)) {
|
|
1066
|
+
const back = this._contextReturn;
|
|
1067
|
+
(back?.isConnected && back !== this.el.ownerDocument.body ? back : this.el).focus();
|
|
1011
1068
|
}
|
|
1069
|
+
this._contextReturn = null;
|
|
1070
|
+
this._contextPoint = null;
|
|
1012
1071
|
};
|
|
1013
1072
|
/**
|
|
1014
1073
|
* While menu is open, you can recalculate its dimensions if its contents have changed.
|
|
@@ -1026,6 +1085,7 @@ var _Menu = class _Menu extends Component {
|
|
|
1026
1085
|
this._originalInert = this.menuEl?.getAttribute("inert") ?? null;
|
|
1027
1086
|
this.options = {
|
|
1028
1087
|
..._Menu.defaults,
|
|
1088
|
+
contextMenu: el.hasAttribute("data-context-menu"),
|
|
1029
1089
|
...options
|
|
1030
1090
|
};
|
|
1031
1091
|
this.isOpen = false;
|
|
@@ -1033,10 +1093,12 @@ var _Menu = class _Menu extends Component {
|
|
|
1033
1093
|
this.isTouchMoving = false;
|
|
1034
1094
|
this.focusedIndex = -1;
|
|
1035
1095
|
this.filterQuery = [];
|
|
1036
|
-
this.
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
this.el.
|
|
1096
|
+
if (!this.options.contextMenu) {
|
|
1097
|
+
this.el.ariaExpanded = "false";
|
|
1098
|
+
if (!this.el.hasAttribute("aria-haspopup")) this.el.setAttribute("aria-haspopup", "menu");
|
|
1099
|
+
if (this.id && !this.el.hasAttribute("aria-controls"))
|
|
1100
|
+
this.el.setAttribute("aria-controls", this.id);
|
|
1101
|
+
}
|
|
1040
1102
|
this._moveMenuToElement();
|
|
1041
1103
|
this._setupAccessibility();
|
|
1042
1104
|
this._makeMenuFocusable();
|
|
@@ -1060,7 +1122,7 @@ var _Menu = class _Menu extends Component {
|
|
|
1060
1122
|
destroy() {
|
|
1061
1123
|
this._clearPendingTimers();
|
|
1062
1124
|
this.isOpen = false;
|
|
1063
|
-
this.
|
|
1125
|
+
this._setExpanded(false);
|
|
1064
1126
|
clearTimeout(this.filterTimeout);
|
|
1065
1127
|
this._resetMenuStyles();
|
|
1066
1128
|
this._restoreInert();
|
|
@@ -1076,7 +1138,9 @@ var _Menu = class _Menu extends Component {
|
|
|
1076
1138
|
if (this.menuEl?.querySelector(":scope > li > menu")) {
|
|
1077
1139
|
this.menuEl.addEventListener("mouseover", this._handleSubmenuAlign);
|
|
1078
1140
|
}
|
|
1079
|
-
if (this.options.
|
|
1141
|
+
if (this.options.contextMenu) {
|
|
1142
|
+
this.el.addEventListener("contextmenu", this._handleContextMenu);
|
|
1143
|
+
} else if (this.options.hover) {
|
|
1080
1144
|
this.el.addEventListener("mouseenter", this._handleMouseEnter);
|
|
1081
1145
|
this.el.addEventListener("mouseleave", this._handleMouseLeave);
|
|
1082
1146
|
this.menuEl.addEventListener("mouseleave", this._handleMouseLeave);
|
|
@@ -1088,7 +1152,9 @@ var _Menu = class _Menu extends Component {
|
|
|
1088
1152
|
this.el.removeEventListener("keydown", this._handleTriggerKeydown);
|
|
1089
1153
|
this.menuEl?.removeEventListener("click", this._handleMenuClick);
|
|
1090
1154
|
this.menuEl?.removeEventListener("mouseover", this._handleSubmenuAlign);
|
|
1091
|
-
if (this.options.
|
|
1155
|
+
if (this.options.contextMenu) {
|
|
1156
|
+
this.el.removeEventListener("contextmenu", this._handleContextMenu);
|
|
1157
|
+
} else if (this.options.hover) {
|
|
1092
1158
|
this.el.removeEventListener("mouseenter", this._handleMouseEnter);
|
|
1093
1159
|
this.el.removeEventListener("mouseleave", this._handleMouseLeave);
|
|
1094
1160
|
this.menuEl?.removeEventListener("mouseleave", this._handleMouseLeave);
|
|
@@ -1098,17 +1164,21 @@ var _Menu = class _Menu extends Component {
|
|
|
1098
1164
|
}
|
|
1099
1165
|
_setupTemporaryEventHandlers() {
|
|
1100
1166
|
document.body.addEventListener("click", this._handleDocumentClick);
|
|
1167
|
+
this.menuEl?.addEventListener("touchmove", this._handleDocumentTouchmove, { passive: true });
|
|
1101
1168
|
document.body.addEventListener("touchmove", this._handleDocumentTouchmove, {
|
|
1102
1169
|
passive: true
|
|
1103
1170
|
});
|
|
1104
1171
|
this.menuEl?.addEventListener("keydown", this._handleMenuKeydown);
|
|
1105
1172
|
window.addEventListener("resize", this._handleWindowResize);
|
|
1173
|
+
if (this.options.contextMenu) document.addEventListener("contextmenu", this._handleDocumentContextMenu);
|
|
1106
1174
|
}
|
|
1107
1175
|
_removeTemporaryEventHandlers() {
|
|
1108
1176
|
document.body.removeEventListener("click", this._handleDocumentClick);
|
|
1177
|
+
this.menuEl?.removeEventListener("touchmove", this._handleDocumentTouchmove);
|
|
1109
1178
|
document.body.removeEventListener("touchmove", this._handleDocumentTouchmove);
|
|
1110
1179
|
this.menuEl?.removeEventListener("keydown", this._handleMenuKeydown);
|
|
1111
1180
|
window.removeEventListener("resize", this._handleWindowResize);
|
|
1181
|
+
document.removeEventListener("contextmenu", this._handleDocumentContextMenu);
|
|
1112
1182
|
}
|
|
1113
1183
|
_setupAccessibility() {
|
|
1114
1184
|
if (!this.menuEl) return;
|
|
@@ -1398,8 +1468,12 @@ var _Menu = class _Menu extends Component {
|
|
|
1398
1468
|
closestOverflowParent.style.position = "relative";
|
|
1399
1469
|
const natural = this.menuEl.getBoundingClientRect().width;
|
|
1400
1470
|
const triggerWidth = this.el.getBoundingClientRect().width;
|
|
1401
|
-
const idealWidth = this.options.constrainWidth && triggerWidth >= MIN_SURFACE_WIDTH ? triggerWidth : Math.min(MAX_SURFACE_WIDTH, Math.max(MIN_SURFACE_WIDTH, natural));
|
|
1471
|
+
const idealWidth = this.options.constrainWidth && !this.options.contextMenu && triggerWidth >= MIN_SURFACE_WIDTH ? triggerWidth : Math.min(MAX_SURFACE_WIDTH, Math.max(MIN_SURFACE_WIDTH, natural));
|
|
1402
1472
|
this.menuEl.style.width = idealWidth + "px";
|
|
1473
|
+
if (this.options.contextMenu) {
|
|
1474
|
+
this._placeMenuAtPoint(this._contextPoint ?? this._regionCorner(), closestOverflowParent);
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1403
1477
|
const positionInfo = this._getMenuPosition(closestOverflowParent);
|
|
1404
1478
|
this.menuEl.style.left = positionInfo.x + "px";
|
|
1405
1479
|
this.menuEl.style.top = positionInfo.y + "px";
|
|
@@ -1407,6 +1481,58 @@ var _Menu = class _Menu extends Component {
|
|
|
1407
1481
|
this.menuEl.style.width = positionInfo.width + "px";
|
|
1408
1482
|
this.menuEl.style.transformOrigin = `${positionInfo.horizontalAlignment === "left" ? "0" : "100%"} ${positionInfo.verticalAlignment === "top" ? "0" : "100%"}`;
|
|
1409
1483
|
}
|
|
1484
|
+
/** Where a context menu opens without a pointer: the region's start corner. */
|
|
1485
|
+
_regionCorner() {
|
|
1486
|
+
const rect = this.el.getBoundingClientRect();
|
|
1487
|
+
const rtl = getComputedStyle(this.el).direction === "rtl";
|
|
1488
|
+
return { x: rtl ? rect.right : rect.left, y: rect.bottom };
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Places a context menu with its start corner on the point, flipping to
|
|
1492
|
+
* the other side of the point where it would not fit. The bounds are the
|
|
1493
|
+
* viewport, narrowed to a scrolling or clipping ancestor the menu sits in;
|
|
1494
|
+
* a menu taller than them gets their height and scrolls.
|
|
1495
|
+
*
|
|
1496
|
+
* The menu rests at left/top 0 of its containing block, so its rect is the
|
|
1497
|
+
* offset to subtract - the same trick _getMenuPosition uses. It is measured
|
|
1498
|
+
* without the entry animation's scale, which a second right-click can catch.
|
|
1499
|
+
*/
|
|
1500
|
+
_placeMenuAtPoint({ x, y }, container) {
|
|
1501
|
+
const { transform, transition } = this.menuEl.style;
|
|
1502
|
+
this.menuEl.style.transition = "none";
|
|
1503
|
+
this.menuEl.style.transform = "none";
|
|
1504
|
+
const menu = this.menuEl.getBoundingClientRect();
|
|
1505
|
+
this.menuEl.style.transform = transform;
|
|
1506
|
+
this.menuEl.style.transition = transition;
|
|
1507
|
+
let bounds = { left: 0, top: 0, right: window.innerWidth, bottom: window.innerHeight };
|
|
1508
|
+
if (container && getComputedStyle(container).overflow !== "visible") {
|
|
1509
|
+
const clip = container.getBoundingClientRect();
|
|
1510
|
+
bounds = {
|
|
1511
|
+
left: Math.max(bounds.left, clip.left),
|
|
1512
|
+
top: Math.max(bounds.top, clip.top),
|
|
1513
|
+
right: Math.min(bounds.right, clip.right),
|
|
1514
|
+
bottom: Math.min(bounds.bottom, clip.bottom)
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
const width = menu.width;
|
|
1518
|
+
const height = Math.min(menu.height, bounds.bottom - bounds.top);
|
|
1519
|
+
this.isScrollable = height < menu.height;
|
|
1520
|
+
if (this.isScrollable) this.menuEl.style.height = height + "px";
|
|
1521
|
+
const rtl = getComputedStyle(this.el).direction === "rtl";
|
|
1522
|
+
const fitsRight = x + width <= bounds.right;
|
|
1523
|
+
const fitsLeft = x - width >= bounds.left;
|
|
1524
|
+
const leftward = rtl ? fitsLeft || !fitsRight : !fitsRight && fitsLeft;
|
|
1525
|
+
const upward = y + height > bounds.bottom && y - height >= bounds.top;
|
|
1526
|
+
const clamp = (value, min, max) => Math.max(min, Math.min(value, max));
|
|
1527
|
+
const left = clamp(leftward ? x - width : x, bounds.left, bounds.right - width);
|
|
1528
|
+
const top = clamp(upward ? y - height : y, bounds.top, bounds.bottom - height);
|
|
1529
|
+
this.menuEl.style.left = left - menu.left + "px";
|
|
1530
|
+
this.menuEl.style.top = top - menu.top + "px";
|
|
1531
|
+
this.menuEl.style.transformOrigin = `${leftward ? "100%" : "0"} ${upward ? "100%" : "0"}`;
|
|
1532
|
+
}
|
|
1533
|
+
_setExpanded(expanded) {
|
|
1534
|
+
if (!this.options.contextMenu) this.el.ariaExpanded = String(expanded);
|
|
1535
|
+
}
|
|
1410
1536
|
};
|
|
1411
1537
|
_Menu._menus = [];
|
|
1412
1538
|
var Menu = _Menu;
|
|
@@ -1539,7 +1665,6 @@ var _Autocomplete = class _Autocomplete extends Component {
|
|
|
1539
1665
|
const inputText = this.el.value.toLocaleLowerCase();
|
|
1540
1666
|
this._resetAutocomplete();
|
|
1541
1667
|
if (inputText.length >= this.options.minLength) {
|
|
1542
|
-
this.isOpen = true;
|
|
1543
1668
|
this._renderMenu();
|
|
1544
1669
|
this._announce(this.menuItems.length ? this.options.i18n.results(this.menuItems.length) : this.options.i18n.noResults);
|
|
1545
1670
|
}
|
|
@@ -1633,6 +1758,7 @@ var _Autocomplete = class _Autocomplete extends Component {
|
|
|
1633
1758
|
}
|
|
1634
1759
|
this._removeEventHandlers();
|
|
1635
1760
|
this.menu.destroy();
|
|
1761
|
+
this.isOpen = false;
|
|
1636
1762
|
this._removeMenu();
|
|
1637
1763
|
this._statusInfo.remove();
|
|
1638
1764
|
this._announcer.remove();
|
|
@@ -1680,6 +1806,16 @@ var _Autocomplete = class _Autocomplete extends Component {
|
|
|
1680
1806
|
..._Autocomplete.defaults.menuOptions,
|
|
1681
1807
|
...this.options.menuOptions
|
|
1682
1808
|
};
|
|
1809
|
+
const userOnOpenStart = menuOptions.onOpenStart;
|
|
1810
|
+
const userOnCloseStart = menuOptions.onCloseStart;
|
|
1811
|
+
menuOptions.onOpenStart = (el) => {
|
|
1812
|
+
this.isOpen = true;
|
|
1813
|
+
if (typeof userOnOpenStart === "function") userOnOpenStart.call(this.menu, el);
|
|
1814
|
+
};
|
|
1815
|
+
menuOptions.onCloseStart = (el) => {
|
|
1816
|
+
this.isOpen = false;
|
|
1817
|
+
if (typeof userOnCloseStart === "function") userOnCloseStart.call(this.menu, el);
|
|
1818
|
+
};
|
|
1683
1819
|
const userOnItemClick = menuOptions.onItemClick;
|
|
1684
1820
|
menuOptions.onItemClick = (li) => {
|
|
1685
1821
|
if (!li) return;
|
|
@@ -1692,10 +1828,7 @@ var _Autocomplete = class _Autocomplete extends Component {
|
|
|
1692
1828
|
const label = this.el.parentElement.querySelector("label");
|
|
1693
1829
|
if (label) this.el.after(label);
|
|
1694
1830
|
this.el.removeEventListener("click", this.menu._handleClick);
|
|
1695
|
-
if (!this.options.isMultiSelect
|
|
1696
|
-
const selectedValue = this.menuItems.filter((value) => value.id === this.selectedValues[0].id);
|
|
1697
|
-
this.el.value = selectedValue[0].text;
|
|
1698
|
-
}
|
|
1831
|
+
if (!this.options.isMultiSelect) this._refreshInputText();
|
|
1699
1832
|
if (this.el.value) this.selectOption(this.el.value);
|
|
1700
1833
|
const div = document.createElement("div");
|
|
1701
1834
|
div.classList.add("status-info");
|
|
@@ -1723,7 +1856,6 @@ var _Autocomplete = class _Autocomplete extends Component {
|
|
|
1723
1856
|
this.container.replaceChildren();
|
|
1724
1857
|
this._resetCurrentElementPosition();
|
|
1725
1858
|
this.oldVal = null;
|
|
1726
|
-
this.isOpen = false;
|
|
1727
1859
|
this._pointerDown = false;
|
|
1728
1860
|
}
|
|
1729
1861
|
_highlightPartialText(input, label) {
|
|
@@ -1841,7 +1973,7 @@ var _Autocomplete = class _Autocomplete extends Component {
|
|
|
1841
1973
|
}
|
|
1842
1974
|
}
|
|
1843
1975
|
_triggerChanged() {
|
|
1844
|
-
this.el.dispatchEvent(new Event("change"));
|
|
1976
|
+
this.el.dispatchEvent(new Event("change", { bubbles: true, cancelable: true, composed: true }));
|
|
1845
1977
|
if (typeof this.options.onAutocomplete === "function")
|
|
1846
1978
|
this.options.onAutocomplete.call(this, this.selectedValues);
|
|
1847
1979
|
}
|
|
@@ -1950,7 +2082,7 @@ var _Chips = class _Chips extends Component {
|
|
|
1950
2082
|
this._handleInputKeydown = (e) => {
|
|
1951
2083
|
_Chips._keydown = true;
|
|
1952
2084
|
if (e.key === Utils.keys.ENTER) {
|
|
1953
|
-
if (this.hasAutocomplete && this.autocomplete && this.autocomplete.
|
|
2085
|
+
if (this.hasAutocomplete && this.autocomplete?.isOpen && this.autocomplete.container.querySelector('[role="option"]')) {
|
|
1954
2086
|
return;
|
|
1955
2087
|
}
|
|
1956
2088
|
e.preventDefault();
|
|
@@ -2016,6 +2148,9 @@ var _Chips = class _Chips extends Component {
|
|
|
2016
2148
|
return this.chipsData;
|
|
2017
2149
|
}
|
|
2018
2150
|
destroy() {
|
|
2151
|
+
if (this.autocomplete && Autocomplete.getInstance(this._input) === this.autocomplete) {
|
|
2152
|
+
this.autocomplete.destroy();
|
|
2153
|
+
}
|
|
2019
2154
|
if (this.options.allowUserInput) {
|
|
2020
2155
|
this._removeEventHandlers();
|
|
2021
2156
|
}
|
|
@@ -2081,7 +2216,7 @@ var _Chips = class _Chips extends Component {
|
|
|
2081
2216
|
}
|
|
2082
2217
|
}
|
|
2083
2218
|
_renderChip(chip) {
|
|
2084
|
-
if (!chip.id) return;
|
|
2219
|
+
if (!chip.id && chip.id !== 0) return;
|
|
2085
2220
|
const label = chip.text || chip.id;
|
|
2086
2221
|
const renderedChip = document.createElement("span");
|
|
2087
2222
|
renderedChip.classList.add("chip");
|
|
@@ -2115,14 +2250,16 @@ var _Chips = class _Chips extends Component {
|
|
|
2115
2250
|
}
|
|
2116
2251
|
_setupAutocomplete() {
|
|
2117
2252
|
this.options.autocompleteOptions.onAutocomplete = (items) => {
|
|
2118
|
-
if (
|
|
2253
|
+
if (_Chips.getInstance(this.el) !== this) return;
|
|
2254
|
+
if (items.length > 0) {
|
|
2119
2255
|
this.addChip({
|
|
2120
2256
|
id: items[0].id,
|
|
2121
2257
|
text: items[0].text,
|
|
2122
2258
|
image: items[0].image
|
|
2123
2259
|
});
|
|
2124
|
-
|
|
2125
|
-
|
|
2260
|
+
this._input.value = "";
|
|
2261
|
+
this._input.focus();
|
|
2262
|
+
}
|
|
2126
2263
|
};
|
|
2127
2264
|
this.autocomplete = Autocomplete.init(this._input, this.options.autocompleteOptions);
|
|
2128
2265
|
}
|
|
@@ -2143,6 +2280,7 @@ var _Chips = class _Chips extends Component {
|
|
|
2143
2280
|
if (this._label && this._input) this._label.setAttribute("for", this._input.getAttribute("id"));
|
|
2144
2281
|
}
|
|
2145
2282
|
_setPlaceholder() {
|
|
2283
|
+
if (!this._input) return;
|
|
2146
2284
|
if (this.chipsData !== void 0 && !this.chipsData.length && this.options.placeholder) {
|
|
2147
2285
|
this._input.placeholder = this.options.placeholder;
|
|
2148
2286
|
} else if ((this.chipsData === void 0 || !!this.chipsData.length) && this.options.secondaryPlaceholder) {
|
|
@@ -2150,7 +2288,7 @@ var _Chips = class _Chips extends Component {
|
|
|
2150
2288
|
}
|
|
2151
2289
|
}
|
|
2152
2290
|
_isValidAndNotExist(chip) {
|
|
2153
|
-
const isValid = !!chip.id;
|
|
2291
|
+
const isValid = !!chip.id || chip.id === 0;
|
|
2154
2292
|
const doesNotExist = !this.chipsData.some((item) => item.id == chip.id);
|
|
2155
2293
|
return isValid && doesNotExist;
|
|
2156
2294
|
}
|
|
@@ -2163,7 +2301,8 @@ var _Chips = class _Chips extends Component {
|
|
|
2163
2301
|
const renderedChip = this._renderChip(chip);
|
|
2164
2302
|
this._chips.push(renderedChip);
|
|
2165
2303
|
this.chipsData.push(chip);
|
|
2166
|
-
this._input.before(renderedChip);
|
|
2304
|
+
if (this._input) this._input.before(renderedChip);
|
|
2305
|
+
else this.el.append(renderedChip);
|
|
2167
2306
|
this._setPlaceholder();
|
|
2168
2307
|
if (typeof this.options.onChipAdd === "function") {
|
|
2169
2308
|
this.options.onChipAdd(this.el, renderedChip);
|
|
@@ -2234,6 +2373,7 @@ var Chips = _Chips;
|
|
|
2234
2373
|
|
|
2235
2374
|
// src/ts/components/slider.ts
|
|
2236
2375
|
var _defaults6 = {};
|
|
2376
|
+
var _authored = /* @__PURE__ */ new WeakMap();
|
|
2237
2377
|
var Slider = class _Slider extends Component {
|
|
2238
2378
|
constructor(el, options) {
|
|
2239
2379
|
super(el, options, _Slider);
|
|
@@ -2302,6 +2442,18 @@ var Slider = class _Slider extends Component {
|
|
|
2302
2442
|
this._removeEventHandlers();
|
|
2303
2443
|
this._removeThumb();
|
|
2304
2444
|
this.el["Expressive_Slider"] = void 0;
|
|
2445
|
+
const host = this._host();
|
|
2446
|
+
for (const el of [this.el, host]) {
|
|
2447
|
+
for (const name in _authored.get(el)) el.style.setProperty(name, _authored.get(el)[name]);
|
|
2448
|
+
_authored.delete(el);
|
|
2449
|
+
}
|
|
2450
|
+
host?.querySelectorAll('input[type="range"]').forEach((el) => el["Expressive_Slider"]?._sync());
|
|
2451
|
+
}
|
|
2452
|
+
_set(el, name, value) {
|
|
2453
|
+
const authored = _authored.get(el) ?? {};
|
|
2454
|
+
_authored.set(el, authored);
|
|
2455
|
+
authored[name] ?? (authored[name] = el.style.getPropertyValue(name));
|
|
2456
|
+
el.style.setProperty(name, value);
|
|
2305
2457
|
}
|
|
2306
2458
|
// Eleven listeners used to cover four stages, because each one was written
|
|
2307
2459
|
// twice - once for mouse, once for touch. One of the touch spellings,
|
|
@@ -2372,25 +2524,28 @@ var Slider = class _Slider extends Component {
|
|
|
2372
2524
|
const handle = parseFloat(style.getPropertyValue("--md-comp-slider-handle-width")) || 4;
|
|
2373
2525
|
const length = vertical ? height : width;
|
|
2374
2526
|
const position = handle / 2 + percent * (length - handle);
|
|
2375
|
-
this.el
|
|
2376
|
-
this.el
|
|
2527
|
+
this._set(this.el, "--md-comp-slider-active-fraction", fraction);
|
|
2528
|
+
this._set(this.el, "--md-comp-slider-active-position", `${position}px`);
|
|
2377
2529
|
const host = this._host();
|
|
2378
2530
|
const peers = host?.querySelectorAll('input[type="range"]');
|
|
2379
2531
|
if (host && peers && peers.length === 2) {
|
|
2380
2532
|
const nums = Array.from(peers).map((el) => this._fraction(el));
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2533
|
+
this._set(host, "--md-comp-slider-start-fraction", `${Math.min(...nums) * 100}%`);
|
|
2534
|
+
this._set(host, "--md-comp-slider-end-fraction", `${Math.max(...nums) * 100}%`);
|
|
2535
|
+
this._set(host, "--md-comp-slider-start-position", `${handle / 2 + Math.min(...nums) * (length - handle)}px`);
|
|
2536
|
+
this._set(host, "--md-comp-slider-end-position", `${handle / 2 + Math.max(...nums) * (length - handle)}px`);
|
|
2385
2537
|
}
|
|
2386
2538
|
if (host?.classList.contains("stops")) {
|
|
2387
2539
|
const parsedMax = parseFloat(this.el.max);
|
|
2388
2540
|
const max = Number.isNaN(parsedMax) ? 100 : parsedMax;
|
|
2389
2541
|
const min = parseFloat(this.el.min) || 0;
|
|
2390
2542
|
const step = parseFloat(this.el.step);
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2543
|
+
const stops = step > 0 && Number.isFinite(step) && max > min;
|
|
2544
|
+
this._set(host, "--md-comp-slider-stop-count", stops ? String(Math.floor((max - min) / step + 1e-9) + 1) : "");
|
|
2545
|
+
this._set(host, "--md-comp-slider-stop-spacing", stops ? `${(length - handle) * step / (max - min)}px` : "");
|
|
2546
|
+
if (peers?.length === 1) {
|
|
2547
|
+
this._set(host, "--md-comp-slider-active-position", `${position}px`);
|
|
2548
|
+
this._set(host, "--md-comp-slider-track-center", `${top + height / 2}px`);
|
|
2394
2549
|
}
|
|
2395
2550
|
}
|
|
2396
2551
|
this.value.textContent = this.el.value;
|
|
@@ -2483,6 +2638,7 @@ var Dialogs = _Dialogs;
|
|
|
2483
2638
|
// src/ts/behaviors/sheetDrag.ts
|
|
2484
2639
|
var DISMISS_DISTANCE = 96;
|
|
2485
2640
|
var DISMISS_VELOCITY = 0.5;
|
|
2641
|
+
var FLICK_TIMEOUT = 100;
|
|
2486
2642
|
var DRAG_SLOP = 4;
|
|
2487
2643
|
function installSheetDrag(config) {
|
|
2488
2644
|
const coord = (event) => config.axis === "x" ? event.clientX : event.clientY;
|
|
@@ -2519,6 +2675,7 @@ function installSheetDrag(config) {
|
|
|
2519
2675
|
start: coord(event),
|
|
2520
2676
|
last: coord(event),
|
|
2521
2677
|
lastT: Date.now(),
|
|
2678
|
+
velocity: 0,
|
|
2522
2679
|
shift: 0
|
|
2523
2680
|
};
|
|
2524
2681
|
observer.observe(document, { childList: true, subtree: true, attributes: true, attributeFilter: ["open"], attributeOldValue: true });
|
|
@@ -2531,8 +2688,12 @@ function installSheetDrag(config) {
|
|
|
2531
2688
|
}
|
|
2532
2689
|
const delta = coord(event) - drag.start;
|
|
2533
2690
|
drag.shift = drag.direction === 1 ? Math.max(0, delta) : Math.min(0, delta);
|
|
2534
|
-
|
|
2535
|
-
|
|
2691
|
+
if (coord(event) !== drag.last) {
|
|
2692
|
+
const now = Date.now();
|
|
2693
|
+
drag.velocity = (coord(event) - drag.last) / Math.max(1, now - drag.lastT);
|
|
2694
|
+
drag.last = coord(event);
|
|
2695
|
+
drag.lastT = now;
|
|
2696
|
+
}
|
|
2536
2697
|
drag.dialog.style.setProperty(config.property, `${drag.shift}px`);
|
|
2537
2698
|
};
|
|
2538
2699
|
const onUp = (event) => {
|
|
@@ -2542,13 +2703,14 @@ function installSheetDrag(config) {
|
|
|
2542
2703
|
onCancel();
|
|
2543
2704
|
return;
|
|
2544
2705
|
}
|
|
2706
|
+
onMove(event);
|
|
2545
2707
|
drag = null;
|
|
2546
2708
|
observer.disconnect();
|
|
2547
2709
|
held.dialog.style.transition = "";
|
|
2548
|
-
const
|
|
2549
|
-
const
|
|
2550
|
-
config.onRelease?.(
|
|
2551
|
-
const dismiss = held.direction === 1 ? held.shift > DISMISS_DISTANCE || velocity > DISMISS_VELOCITY : held.shift < -DISMISS_DISTANCE || velocity < -DISMISS_VELOCITY;
|
|
2710
|
+
const velocity = Date.now() - held.lastT < FLICK_TIMEOUT ? held.velocity : 0;
|
|
2711
|
+
const dragged = Math.abs(held.shift) > DRAG_SLOP;
|
|
2712
|
+
config.onRelease?.(dragged);
|
|
2713
|
+
const dismiss = dragged && (held.direction === 1 ? held.shift > DISMISS_DISTANCE || velocity > DISMISS_VELOCITY : held.shift < -DISMISS_DISTANCE || velocity < -DISMISS_VELOCITY);
|
|
2552
2714
|
if (dismiss && held.dialog.open) held.dialog.close();
|
|
2553
2715
|
held.dialog.style.setProperty(config.property, "0px");
|
|
2554
2716
|
};
|
|
@@ -2685,6 +2847,10 @@ var Forms = class _Forms {
|
|
|
2685
2847
|
Utils.onDocumentReady(() => {
|
|
2686
2848
|
document.addEventListener("change", (e) => {
|
|
2687
2849
|
const target = e.target;
|
|
2850
|
+
if (target instanceof HTMLInputElement && target.type === "file") {
|
|
2851
|
+
const zone = target.closest(".drop-zone");
|
|
2852
|
+
if (zone) _Forms.listDropZoneFiles(zone, target);
|
|
2853
|
+
}
|
|
2688
2854
|
if (target instanceof HTMLInputElement) {
|
|
2689
2855
|
_Forms.validateField(target);
|
|
2690
2856
|
}
|
|
@@ -2700,6 +2866,12 @@ var Forms = class _Forms {
|
|
|
2700
2866
|
}
|
|
2701
2867
|
}
|
|
2702
2868
|
});
|
|
2869
|
+
document.addEventListener("click", _Forms.togglePassword);
|
|
2870
|
+
document.addEventListener("submit", _Forms.resetFormExtras, true);
|
|
2871
|
+
document.addEventListener("reset", _Forms.resetFormExtras, true);
|
|
2872
|
+
for (const type of ["dragenter", "dragover", "dragleave", "drop"]) {
|
|
2873
|
+
document.addEventListener(type, _Forms.markDropZone);
|
|
2874
|
+
}
|
|
2703
2875
|
document.querySelectorAll(".expressive-textarea").forEach((textArea) => {
|
|
2704
2876
|
_Forms.InitTextarea(textArea);
|
|
2705
2877
|
});
|
|
@@ -2708,6 +2880,75 @@ var Forms = class _Forms {
|
|
|
2708
2880
|
});
|
|
2709
2881
|
});
|
|
2710
2882
|
}
|
|
2883
|
+
/**
|
|
2884
|
+
* A .password-toggle button shows or hides the password in its field. The
|
|
2885
|
+
* button's own name stays put ("Show password"); aria-pressed carries the
|
|
2886
|
+
* state, so there is no generated text to translate.
|
|
2887
|
+
*/
|
|
2888
|
+
static togglePassword(e) {
|
|
2889
|
+
const button = e.target.closest?.(".password-toggle");
|
|
2890
|
+
const input = button?.closest(".field")?.querySelector(
|
|
2891
|
+
'input[type="password"], input[data-password]'
|
|
2892
|
+
);
|
|
2893
|
+
if (!input) return;
|
|
2894
|
+
const show = input.type === "password";
|
|
2895
|
+
input.toggleAttribute("data-password", show);
|
|
2896
|
+
input.type = show ? "text" : "password";
|
|
2897
|
+
button.setAttribute("aria-pressed", String(show));
|
|
2898
|
+
}
|
|
2899
|
+
/**
|
|
2900
|
+
* A revealed password goes back to type="password" when its form is
|
|
2901
|
+
* submitted or reset, so it is never sent or autofilled as plain text and
|
|
2902
|
+
* a reset field does not stay revealed. A reset form also drops the file
|
|
2903
|
+
* lists it would otherwise leave showing: reset fires no change event.
|
|
2904
|
+
*/
|
|
2905
|
+
static resetFormExtras(e) {
|
|
2906
|
+
const form = e.target;
|
|
2907
|
+
if (!(form instanceof HTMLFormElement)) return;
|
|
2908
|
+
form.querySelectorAll("input[data-password]").forEach((input) => {
|
|
2909
|
+
input.removeAttribute("data-password");
|
|
2910
|
+
input.type = "password";
|
|
2911
|
+
input.closest(".field")?.querySelector(".password-toggle")?.setAttribute("aria-pressed", "false");
|
|
2912
|
+
});
|
|
2913
|
+
if (e.type === "reset") {
|
|
2914
|
+
form.querySelectorAll(".drop-zone-files").forEach((list) => list.replaceChildren());
|
|
2915
|
+
form.querySelectorAll(".drop-zone.dragover").forEach((zone) => zone.classList.remove("dragover"));
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
/**
|
|
2919
|
+
* The native file input covers its .drop-zone, so the browser already takes
|
|
2920
|
+
* a dropped file. This only shows the drag: the input is the topmost
|
|
2921
|
+
* element, so every drag event over the zone targets it and leaving it is
|
|
2922
|
+
* leaving the zone.
|
|
2923
|
+
*/
|
|
2924
|
+
static markDropZone(e) {
|
|
2925
|
+
const input = e.target;
|
|
2926
|
+
if (!(input instanceof HTMLInputElement) || input.type !== "file") return;
|
|
2927
|
+
const zone = input.closest(".drop-zone");
|
|
2928
|
+
if (!zone) return;
|
|
2929
|
+
const over = e.type === "dragenter" || e.type === "dragover";
|
|
2930
|
+
zone.classList.toggle("dragover", over && !input.disabled);
|
|
2931
|
+
}
|
|
2932
|
+
/** Lists the chosen files, name and size, in the zone's .drop-zone-files. */
|
|
2933
|
+
static listDropZoneFiles(zone, input) {
|
|
2934
|
+
const list = zone.querySelector(".drop-zone-files");
|
|
2935
|
+
if (!list) return;
|
|
2936
|
+
const size = (bytes) => {
|
|
2937
|
+
const [value, unit] = bytes >= 1e6 ? [bytes / 1e6, "megabyte"] : bytes >= 1e3 ? [bytes / 1e3, "kilobyte"] : [bytes, "byte"];
|
|
2938
|
+
return new Intl.NumberFormat(void 0, { style: "unit", unit, maximumFractionDigits: 1 }).format(value);
|
|
2939
|
+
};
|
|
2940
|
+
list.replaceChildren(
|
|
2941
|
+
...Array.from(input.files ?? [], (file) => {
|
|
2942
|
+
const item = document.createElement("li");
|
|
2943
|
+
const name = document.createElement("span");
|
|
2944
|
+
const meta = document.createElement("span");
|
|
2945
|
+
name.textContent = file.name;
|
|
2946
|
+
meta.textContent = size(file.size);
|
|
2947
|
+
item.append(name, meta);
|
|
2948
|
+
return item;
|
|
2949
|
+
})
|
|
2950
|
+
);
|
|
2951
|
+
}
|
|
2711
2952
|
static InitTextarea(textarea) {
|
|
2712
2953
|
textarea.setAttribute("original-height", textarea.getBoundingClientRect().height.toString());
|
|
2713
2954
|
_Forms.textareaAutoResize(textarea);
|
|
@@ -2743,7 +2984,10 @@ var AppBar = class _AppBar extends Component {
|
|
|
2743
2984
|
this._onIntersect = (entries) => {
|
|
2744
2985
|
const entry = entries[0];
|
|
2745
2986
|
if (!entry) return;
|
|
2746
|
-
this.el.classList.toggle(
|
|
2987
|
+
this.el.classList.toggle(
|
|
2988
|
+
"collapsed",
|
|
2989
|
+
!entry.isIntersecting && entry.boundingClientRect.top < this.el.getBoundingClientRect().top
|
|
2990
|
+
);
|
|
2747
2991
|
};
|
|
2748
2992
|
this._onSearchViewClose = () => {
|
|
2749
2993
|
this._searchOpen = this._view.open;
|
|
@@ -2790,8 +3034,8 @@ var AppBar = class _AppBar extends Component {
|
|
|
2790
3034
|
if (typeof IntersectionObserver === "undefined") return;
|
|
2791
3035
|
this._sentinel = document.createElement("span");
|
|
2792
3036
|
this._sentinel.setAttribute("aria-hidden", "true");
|
|
2793
|
-
this._sentinel.style.cssText = "display:block;
|
|
2794
|
-
this.el.insertAdjacentElement("
|
|
3037
|
+
this._sentinel.style.cssText = "display:block;height:0;position:relative;top:-64px;pointer-events:none;visibility:hidden";
|
|
3038
|
+
this.el.insertAdjacentElement("afterend", this._sentinel);
|
|
2795
3039
|
this._observer = new IntersectionObserver(this._onIntersect);
|
|
2796
3040
|
this._observer.observe(this._sentinel);
|
|
2797
3041
|
}
|
|
@@ -3218,10 +3462,6 @@ var _Carousel = class _Carousel extends Component {
|
|
|
3218
3462
|
this._dragStartX = e.clientX;
|
|
3219
3463
|
this._dragStartY = e.clientY;
|
|
3220
3464
|
this._dragStartScroll = this._vertical ? this._scroller.scrollTop : this._scroller.scrollLeft;
|
|
3221
|
-
try {
|
|
3222
|
-
this._scroller.setPointerCapture(e.pointerId);
|
|
3223
|
-
} catch {
|
|
3224
|
-
}
|
|
3225
3465
|
};
|
|
3226
3466
|
this._handleTrackPointerMove = (e) => {
|
|
3227
3467
|
if (!this.pressed || e.pointerId !== this._dragPointerId) return;
|
|
@@ -3233,6 +3473,10 @@ var _Carousel = class _Carousel extends Component {
|
|
|
3233
3473
|
if (Math.abs(primaryDelta) < 6 || Math.abs(primaryDelta) <= Math.abs(crossDelta)) return;
|
|
3234
3474
|
this.dragged = true;
|
|
3235
3475
|
this.el.classList.add("dragging");
|
|
3476
|
+
try {
|
|
3477
|
+
this._scroller.setPointerCapture(e.pointerId);
|
|
3478
|
+
} catch {
|
|
3479
|
+
}
|
|
3236
3480
|
}
|
|
3237
3481
|
if (this._vertical) this._scroller.scrollTop = this._dragStartScroll - deltaY;
|
|
3238
3482
|
else this._scroller.scrollLeft = this._dragStartScroll - deltaX;
|
|
@@ -3262,7 +3506,7 @@ var _Carousel = class _Carousel extends Component {
|
|
|
3262
3506
|
e.stopImmediatePropagation();
|
|
3263
3507
|
};
|
|
3264
3508
|
this._handleTrackDragStart = (e) => {
|
|
3265
|
-
if (e.target.closest("img, picture, video")) e.preventDefault();
|
|
3509
|
+
if (e.target.closest("a, img, picture, video")) e.preventDefault();
|
|
3266
3510
|
};
|
|
3267
3511
|
this._handleItemResize = (e) => {
|
|
3268
3512
|
if (this._alignAfterResize && !this.pressed && e.propertyName === "flex-basis" && this.images.includes(e.target)) {
|
|
@@ -3318,7 +3562,7 @@ var _Carousel = class _Carousel extends Component {
|
|
|
3318
3562
|
if (!this._started || !this.images.length) return;
|
|
3319
3563
|
this._syncAdaptiveMode();
|
|
3320
3564
|
this._syncLayoutRoles(this.center);
|
|
3321
|
-
this._scrollToIndex(this.center, false);
|
|
3565
|
+
if (!this.pressed) this._scrollToIndex(this.center, false);
|
|
3322
3566
|
this._updateParallax();
|
|
3323
3567
|
};
|
|
3324
3568
|
this._finishScroll = () => {
|
|
@@ -3502,9 +3746,9 @@ var _Carousel = class _Carousel extends Component {
|
|
|
3502
3746
|
});
|
|
3503
3747
|
this._scroller.addEventListener("pointerdown", this._handleTrackPointerDown);
|
|
3504
3748
|
this._scroller.addEventListener("wheel", this._handleNativeScroll, { passive: true });
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3749
|
+
document.addEventListener("pointermove", this._handleTrackPointerMove);
|
|
3750
|
+
document.addEventListener("pointerup", this._handleTrackPointerUp);
|
|
3751
|
+
document.addEventListener("pointercancel", this._handleTrackPointerUp);
|
|
3508
3752
|
this._scroller.addEventListener("click", this._handleTrackClick, true);
|
|
3509
3753
|
this._scroller.addEventListener("dragstart", this._handleTrackDragStart);
|
|
3510
3754
|
this._scroller.addEventListener("transitionend", this._handleItemResize);
|
|
@@ -3523,9 +3767,9 @@ var _Carousel = class _Carousel extends Component {
|
|
|
3523
3767
|
this._scroller.removeEventListener("scroll", this._handleTrackScroll);
|
|
3524
3768
|
this._scroller.removeEventListener("pointerdown", this._handleTrackPointerDown);
|
|
3525
3769
|
this._scroller.removeEventListener("wheel", this._handleNativeScroll);
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3770
|
+
document.removeEventListener("pointermove", this._handleTrackPointerMove);
|
|
3771
|
+
document.removeEventListener("pointerup", this._handleTrackPointerUp);
|
|
3772
|
+
document.removeEventListener("pointercancel", this._handleTrackPointerUp);
|
|
3529
3773
|
this._scroller.removeEventListener("click", this._handleTrackClick, true);
|
|
3530
3774
|
this._scroller.removeEventListener("dragstart", this._handleTrackDragStart);
|
|
3531
3775
|
this._scroller.removeEventListener("transitionend", this._handleItemResize);
|
|
@@ -3784,92 +4028,1660 @@ var _Carousel = class _Carousel extends Component {
|
|
|
3784
4028
|
this._cycleTo(n, callback);
|
|
3785
4029
|
}
|
|
3786
4030
|
};
|
|
3787
|
-
_Carousel._live = /* @__PURE__ */ new Set();
|
|
3788
|
-
_Carousel._resizeListening = false;
|
|
3789
|
-
_Carousel._onResize = Utils.throttle(() => {
|
|
3790
|
-
_Carousel._live.forEach((c) => c._handleResize());
|
|
3791
|
-
}, 200);
|
|
3792
|
-
var Carousel = _Carousel;
|
|
4031
|
+
_Carousel._live = /* @__PURE__ */ new Set();
|
|
4032
|
+
_Carousel._resizeListening = false;
|
|
4033
|
+
_Carousel._onResize = Utils.throttle(() => {
|
|
4034
|
+
_Carousel._live.forEach((c) => c._handleResize());
|
|
4035
|
+
}, 200);
|
|
4036
|
+
var Carousel = _Carousel;
|
|
4037
|
+
|
|
4038
|
+
// src/ts/components/characterCounter.ts
|
|
4039
|
+
var _defaults11 = Object.freeze({});
|
|
4040
|
+
var CharacterCounter = class _CharacterCounter extends Component {
|
|
4041
|
+
constructor(el, options) {
|
|
4042
|
+
super(el, {}, _CharacterCounter);
|
|
4043
|
+
this.updateCounter = () => {
|
|
4044
|
+
const maxLength = parseInt(this.el.getAttribute("maxlength")), actualLength = this.el.value.length;
|
|
4045
|
+
this.isValidLength = actualLength <= maxLength;
|
|
4046
|
+
let counterString = actualLength.toString();
|
|
4047
|
+
if (maxLength) {
|
|
4048
|
+
counterString += "/" + maxLength;
|
|
4049
|
+
this._validateInput();
|
|
4050
|
+
}
|
|
4051
|
+
this.counterEl.textContent = counterString;
|
|
4052
|
+
};
|
|
4053
|
+
this.el["Expressive_CharacterCounter"] = this;
|
|
4054
|
+
this.options = {
|
|
4055
|
+
..._CharacterCounter.defaults,
|
|
4056
|
+
...options
|
|
4057
|
+
};
|
|
4058
|
+
this.isInvalid = false;
|
|
4059
|
+
this.isValidLength = false;
|
|
4060
|
+
this._setupCounter();
|
|
4061
|
+
this._setupEventHandlers();
|
|
4062
|
+
}
|
|
4063
|
+
static get defaults() {
|
|
4064
|
+
return _defaults11;
|
|
4065
|
+
}
|
|
4066
|
+
/**
|
|
4067
|
+
* Initializes instances of CharacterCounter.
|
|
4068
|
+
* @param els HTML elements.
|
|
4069
|
+
* @param options Component options.
|
|
4070
|
+
*/
|
|
4071
|
+
static init(els, options = {}) {
|
|
4072
|
+
return super.init(els, options, _CharacterCounter);
|
|
4073
|
+
}
|
|
4074
|
+
static getInstance(el) {
|
|
4075
|
+
return el["Expressive_CharacterCounter"];
|
|
4076
|
+
}
|
|
4077
|
+
destroy() {
|
|
4078
|
+
this._removeEventHandlers();
|
|
4079
|
+
this.el["Expressive_CharacterCounter"] = void 0;
|
|
4080
|
+
this._removeCounter();
|
|
4081
|
+
}
|
|
4082
|
+
_setupEventHandlers() {
|
|
4083
|
+
this.el.addEventListener("focus", this.updateCounter, true);
|
|
4084
|
+
this.el.addEventListener("input", this.updateCounter, true);
|
|
4085
|
+
}
|
|
4086
|
+
_removeEventHandlers() {
|
|
4087
|
+
this.el.removeEventListener("focus", this.updateCounter, true);
|
|
4088
|
+
this.el.removeEventListener("input", this.updateCounter, true);
|
|
4089
|
+
}
|
|
4090
|
+
_setupCounter() {
|
|
4091
|
+
this.counterEl = document.createElement("span");
|
|
4092
|
+
this.counterEl.classList.add("character-counter");
|
|
4093
|
+
this.counterEl.setAttribute("aria-live", "polite");
|
|
4094
|
+
this.counterEl.setAttribute("aria-atomic", "true");
|
|
4095
|
+
this.counterEl.style.float = "right";
|
|
4096
|
+
this.counterEl.style.fontSize = "12px";
|
|
4097
|
+
this.counterEl.style.height = "1";
|
|
4098
|
+
this.el.parentElement.appendChild(this.counterEl);
|
|
4099
|
+
}
|
|
4100
|
+
_removeCounter() {
|
|
4101
|
+
this.counterEl.remove();
|
|
4102
|
+
}
|
|
4103
|
+
_validateInput() {
|
|
4104
|
+
if (this.isValidLength && this.isInvalid) {
|
|
4105
|
+
this.isInvalid = false;
|
|
4106
|
+
this.el.classList.remove("invalid");
|
|
4107
|
+
} else if (!this.isValidLength && !this.isInvalid) {
|
|
4108
|
+
this.isInvalid = true;
|
|
4109
|
+
this.el.classList.remove("valid");
|
|
4110
|
+
this.el.classList.add("invalid");
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
};
|
|
4114
|
+
|
|
4115
|
+
// src/ts/components/commandPalette.ts
|
|
4116
|
+
var _defaults12 = {
|
|
4117
|
+
shortcut: "k",
|
|
4118
|
+
closeOnRun: true
|
|
4119
|
+
};
|
|
4120
|
+
var APPLE = typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.platform);
|
|
4121
|
+
var fold = (text) => text.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase();
|
|
4122
|
+
var CommandPalette = class _CommandPalette extends Component {
|
|
4123
|
+
constructor(el, options) {
|
|
4124
|
+
super(el, options, _CommandPalette);
|
|
4125
|
+
this._active = null;
|
|
4126
|
+
this._generatedIds = [];
|
|
4127
|
+
/** Wires and refilters commands the page adds or removes later. */
|
|
4128
|
+
this._observer = null;
|
|
4129
|
+
/** Opens the palette as a modal dialog with the search input focused. */
|
|
4130
|
+
this.open = () => {
|
|
4131
|
+
if (!this.el.open) this.el.showModal();
|
|
4132
|
+
this.input?.focus();
|
|
4133
|
+
};
|
|
4134
|
+
/** Closes the palette. The dialog returns focus to where it was. */
|
|
4135
|
+
this.close = () => {
|
|
4136
|
+
if (this.el.open) this.el.close();
|
|
4137
|
+
};
|
|
4138
|
+
this._handleInput = () => this._filter();
|
|
4139
|
+
this._handleKeydown = (e) => {
|
|
4140
|
+
if (e.key === Utils.keys.ARROW_DOWN || e.key === Utils.keys.ARROW_UP) {
|
|
4141
|
+
e.preventDefault();
|
|
4142
|
+
this._move(e.key === Utils.keys.ARROW_DOWN ? 1 : -1);
|
|
4143
|
+
} else if (e.key === Utils.keys.ENTER && !e.isComposing && e.keyCode !== 229 && this._active) {
|
|
4144
|
+
e.preventDefault();
|
|
4145
|
+
this._run(this._active);
|
|
4146
|
+
}
|
|
4147
|
+
};
|
|
4148
|
+
this._handleListClick = (e) => {
|
|
4149
|
+
const item = e.target.closest('[role="option"]');
|
|
4150
|
+
if (!item || !this.list.contains(item) || item.getAttribute("aria-disabled") === "true") return;
|
|
4151
|
+
const action = this._action(item);
|
|
4152
|
+
if (action && !action.contains(e.target)) {
|
|
4153
|
+
action.click();
|
|
4154
|
+
return;
|
|
4155
|
+
}
|
|
4156
|
+
if (this.options.closeOnRun) this.close();
|
|
4157
|
+
};
|
|
4158
|
+
this._handlePointerMove = (e) => {
|
|
4159
|
+
const item = e.target.closest('[role="option"]');
|
|
4160
|
+
if (item && this.list.contains(item) && item.getAttribute("aria-disabled") !== "true") this._setActive(item);
|
|
4161
|
+
};
|
|
4162
|
+
/**
|
|
4163
|
+
* Each opening starts from the full list. The close event is queued, so a
|
|
4164
|
+
* palette reopened straight away may already be open again; its new search
|
|
4165
|
+
* is left alone.
|
|
4166
|
+
*/
|
|
4167
|
+
this._handleClose = () => {
|
|
4168
|
+
if (this.el.open) return;
|
|
4169
|
+
this.input.value = "";
|
|
4170
|
+
this._filter();
|
|
4171
|
+
};
|
|
4172
|
+
this._handleShortcut = (e) => {
|
|
4173
|
+
const { shortcut } = this.options;
|
|
4174
|
+
if (!shortcut || e.defaultPrevented || e.altKey || e.shiftKey) return;
|
|
4175
|
+
if (!(APPLE ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey)) return;
|
|
4176
|
+
const letter = shortcut.toLowerCase();
|
|
4177
|
+
const latin = /^[a-z]$/i.test(e.key);
|
|
4178
|
+
const hit = e.key.toLowerCase() === letter || !latin && /^[a-z]$/.test(letter) && e.code === `Key${letter.toUpperCase()}`;
|
|
4179
|
+
if (!hit) return;
|
|
4180
|
+
e.preventDefault();
|
|
4181
|
+
if (this.el.open) this.close();
|
|
4182
|
+
else this.open();
|
|
4183
|
+
};
|
|
4184
|
+
this.el["Expressive_CommandPalette"] = this;
|
|
4185
|
+
const shortcut = el.dataset.shortcut;
|
|
4186
|
+
this.options = {
|
|
4187
|
+
..._CommandPalette.defaults,
|
|
4188
|
+
...shortcut !== void 0 ? { shortcut: shortcut || null } : {},
|
|
4189
|
+
...options
|
|
4190
|
+
};
|
|
4191
|
+
this.input = el.querySelector(":scope > input");
|
|
4192
|
+
this.list = el.querySelector(":scope > :is(ul, ol)");
|
|
4193
|
+
if (!this.input || !this.list) {
|
|
4194
|
+
console.error("CommandPalette needs an <input> and a <ul> as children of the dialog.");
|
|
4195
|
+
return;
|
|
4196
|
+
}
|
|
4197
|
+
this._setupAccessibility();
|
|
4198
|
+
this._filter();
|
|
4199
|
+
this._observer = new MutationObserver(() => this._filter(true));
|
|
4200
|
+
this._observer.observe(this.list, {
|
|
4201
|
+
childList: true,
|
|
4202
|
+
subtree: true,
|
|
4203
|
+
attributes: true,
|
|
4204
|
+
attributeFilter: ["hidden", "disabled"]
|
|
4205
|
+
});
|
|
4206
|
+
this.input.addEventListener("input", this._handleInput);
|
|
4207
|
+
this.input.addEventListener("keydown", this._handleKeydown);
|
|
4208
|
+
this.list.addEventListener("click", this._handleListClick);
|
|
4209
|
+
this.list.addEventListener("pointermove", this._handlePointerMove);
|
|
4210
|
+
this.el.addEventListener("close", this._handleClose);
|
|
4211
|
+
document.addEventListener("keydown", this._handleShortcut);
|
|
4212
|
+
}
|
|
4213
|
+
static get defaults() {
|
|
4214
|
+
return _defaults12;
|
|
4215
|
+
}
|
|
4216
|
+
/**
|
|
4217
|
+
* Initializes instances of CommandPalette.
|
|
4218
|
+
* @param els HTML elements.
|
|
4219
|
+
* @param options Component options.
|
|
4220
|
+
*/
|
|
4221
|
+
static init(els, options = {}) {
|
|
4222
|
+
return super.init(els, options, _CommandPalette);
|
|
4223
|
+
}
|
|
4224
|
+
static getInstance(el) {
|
|
4225
|
+
return el["Expressive_CommandPalette"];
|
|
4226
|
+
}
|
|
4227
|
+
/** If the palette is open. */
|
|
4228
|
+
get isOpen() {
|
|
4229
|
+
return this.el.open;
|
|
4230
|
+
}
|
|
4231
|
+
destroy() {
|
|
4232
|
+
this._observer?.disconnect();
|
|
4233
|
+
document.removeEventListener("keydown", this._handleShortcut);
|
|
4234
|
+
this.el.removeEventListener("close", this._handleClose);
|
|
4235
|
+
if (this.input && this.list) {
|
|
4236
|
+
this.input.removeEventListener("input", this._handleInput);
|
|
4237
|
+
this.input.removeEventListener("keydown", this._handleKeydown);
|
|
4238
|
+
this.list.removeEventListener("click", this._handleListClick);
|
|
4239
|
+
this.list.removeEventListener("pointermove", this._handlePointerMove);
|
|
4240
|
+
for (const name of ["role", "aria-controls", "aria-expanded", "aria-autocomplete", "aria-activedescendant"]) {
|
|
4241
|
+
this.input.removeAttribute(name);
|
|
4242
|
+
}
|
|
4243
|
+
this.list.removeAttribute("role");
|
|
4244
|
+
for (const item of this._items()) {
|
|
4245
|
+
for (const name of ["role", "aria-selected", "aria-disabled", "data-filtered"]) item.removeAttribute(name);
|
|
4246
|
+
item.classList.remove("active");
|
|
4247
|
+
this._action(item)?.removeAttribute("tabindex");
|
|
4248
|
+
}
|
|
4249
|
+
for (const label of this._labels()) {
|
|
4250
|
+
label.removeAttribute("role");
|
|
4251
|
+
label.removeAttribute("data-filtered");
|
|
4252
|
+
}
|
|
4253
|
+
for (const el of this._generatedIds) el.removeAttribute("id");
|
|
4254
|
+
const empty = this._empty();
|
|
4255
|
+
if (empty) empty.hidden = false;
|
|
4256
|
+
}
|
|
4257
|
+
this.el["Expressive_CommandPalette"] = void 0;
|
|
4258
|
+
}
|
|
4259
|
+
_items() {
|
|
4260
|
+
return Array.from(this.list.children).filter(
|
|
4261
|
+
(child) => child instanceof HTMLElement && !child.classList.contains("label")
|
|
4262
|
+
);
|
|
4263
|
+
}
|
|
4264
|
+
_labels() {
|
|
4265
|
+
return Array.from(this.list.querySelectorAll(":scope > .label"));
|
|
4266
|
+
}
|
|
4267
|
+
_empty() {
|
|
4268
|
+
return this.el.querySelector(":scope > .command-palette-empty");
|
|
4269
|
+
}
|
|
4270
|
+
_action(item) {
|
|
4271
|
+
return item.querySelector(":scope > :is(a[href], button)");
|
|
4272
|
+
}
|
|
4273
|
+
_ensureId(el, prefix) {
|
|
4274
|
+
if (!el.id) {
|
|
4275
|
+
el.id = `${prefix}-${Utils.guid()}`;
|
|
4276
|
+
this._generatedIds.push(el);
|
|
4277
|
+
}
|
|
4278
|
+
return el.id;
|
|
4279
|
+
}
|
|
4280
|
+
_setupAccessibility() {
|
|
4281
|
+
this.list.setAttribute("role", "listbox");
|
|
4282
|
+
this.input.setAttribute("role", "combobox");
|
|
4283
|
+
this.input.setAttribute("aria-controls", this._ensureId(this.list, "command-palette-list"));
|
|
4284
|
+
this.input.setAttribute("aria-expanded", "true");
|
|
4285
|
+
this.input.setAttribute("aria-autocomplete", "list");
|
|
4286
|
+
}
|
|
4287
|
+
/** Every command, including ones added since the last pass, is an option. */
|
|
4288
|
+
_wireItems() {
|
|
4289
|
+
for (const item of this._items()) {
|
|
4290
|
+
if (item.getAttribute("role") === "option") continue;
|
|
4291
|
+
item.setAttribute("role", "option");
|
|
4292
|
+
this._ensureId(item, "command-palette-option");
|
|
4293
|
+
this._action(item)?.setAttribute("tabindex", "-1");
|
|
4294
|
+
}
|
|
4295
|
+
for (const label of this._labels()) label.setAttribute("role", "none");
|
|
4296
|
+
}
|
|
4297
|
+
/** Commands on screen: neither hidden by the page nor filtered out. */
|
|
4298
|
+
_shown() {
|
|
4299
|
+
return this._items().filter((item) => !item.hidden && !item.hasAttribute("data-filtered"));
|
|
4300
|
+
}
|
|
4301
|
+
/** Shown commands the arrows and Enter can reach: disabled ones are skipped. */
|
|
4302
|
+
_visible() {
|
|
4303
|
+
return this._shown().filter((item) => item.getAttribute("aria-disabled") !== "true");
|
|
4304
|
+
}
|
|
4305
|
+
/** A disabled link or button cannot run, so its option says so. */
|
|
4306
|
+
_isDisabled(item) {
|
|
4307
|
+
const action = this._action(item);
|
|
4308
|
+
return !!action && (action.matches(":disabled") || action.getAttribute("aria-disabled") === "true");
|
|
4309
|
+
}
|
|
4310
|
+
_setActive(item) {
|
|
4311
|
+
if (this._active === item) return;
|
|
4312
|
+
this._active?.classList.remove("active");
|
|
4313
|
+
this._active?.removeAttribute("aria-selected");
|
|
4314
|
+
this._active = item;
|
|
4315
|
+
if (item) {
|
|
4316
|
+
item.classList.add("active");
|
|
4317
|
+
item.setAttribute("aria-selected", "true");
|
|
4318
|
+
this.input.setAttribute("aria-activedescendant", item.id);
|
|
4319
|
+
item.scrollIntoView({ block: "nearest" });
|
|
4320
|
+
} else {
|
|
4321
|
+
this.input.removeAttribute("aria-activedescendant");
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
/**
|
|
4325
|
+
* Shows the commands matching the query. Filtering marks rows with
|
|
4326
|
+
* data-filtered rather than hidden, so a command the page hides stays
|
|
4327
|
+
* hidden. `keepActive` holds the active command while it is still
|
|
4328
|
+
* reachable, for list changes the user did not make.
|
|
4329
|
+
*/
|
|
4330
|
+
_filter(keepActive = false) {
|
|
4331
|
+
this._wireItems();
|
|
4332
|
+
const terms = fold(this.input.value).split(/\s+/).filter(Boolean);
|
|
4333
|
+
for (const item of this._items()) {
|
|
4334
|
+
const keywords = item.dataset.keywords ?? this._action(item)?.dataset.keywords ?? "";
|
|
4335
|
+
const text = fold(`${item.textContent} ${keywords}`);
|
|
4336
|
+
item.toggleAttribute("data-filtered", !terms.every((term) => text.includes(term)));
|
|
4337
|
+
if (this._isDisabled(item)) item.setAttribute("aria-disabled", "true");
|
|
4338
|
+
else item.removeAttribute("aria-disabled");
|
|
4339
|
+
}
|
|
4340
|
+
for (const label of this._labels()) {
|
|
4341
|
+
let next = label.nextElementSibling;
|
|
4342
|
+
let any = false;
|
|
4343
|
+
while (next && !next.classList.contains("label")) {
|
|
4344
|
+
if (!next.hidden && !next.hasAttribute("data-filtered")) any = true;
|
|
4345
|
+
next = next.nextElementSibling;
|
|
4346
|
+
}
|
|
4347
|
+
label.toggleAttribute("data-filtered", !any);
|
|
4348
|
+
}
|
|
4349
|
+
const visible = this._visible();
|
|
4350
|
+
const empty = this._empty();
|
|
4351
|
+
if (empty) empty.hidden = this._shown().length > 0;
|
|
4352
|
+
this._setActive(keepActive && visible.includes(this._active) ? this._active : visible[0] ?? null);
|
|
4353
|
+
}
|
|
4354
|
+
_move(step) {
|
|
4355
|
+
const visible = this._visible();
|
|
4356
|
+
if (!visible.length) return;
|
|
4357
|
+
const index = visible.indexOf(this._active);
|
|
4358
|
+
this._setActive(visible[(index + step + visible.length) % visible.length]);
|
|
4359
|
+
}
|
|
4360
|
+
/** Runs a command by clicking its link or button; the click handler closes. */
|
|
4361
|
+
_run(item) {
|
|
4362
|
+
(this._action(item) ?? item).click();
|
|
4363
|
+
}
|
|
4364
|
+
};
|
|
4365
|
+
|
|
4366
|
+
// src/ts/components/questionnaire.ts
|
|
4367
|
+
var _defaults13 = {
|
|
4368
|
+
progressLabel: "Question {current} of {total}",
|
|
4369
|
+
requiredMessage: "Choose an answer to continue."
|
|
4370
|
+
};
|
|
4371
|
+
var NAVIGATION = ["previous", "skip", "next"];
|
|
4372
|
+
var Questionnaire = class _Questionnaire extends Component {
|
|
4373
|
+
constructor(el, options) {
|
|
4374
|
+
super(el, options, _Questionnaire);
|
|
4375
|
+
this._active = null;
|
|
4376
|
+
this._generated = [];
|
|
4377
|
+
this._generatedIds = [];
|
|
4378
|
+
/** Set while one submission's invalid events arrive, so only the first opens. */
|
|
4379
|
+
this._reporting = false;
|
|
4380
|
+
/** Shows the question at `index` without checking the current one. */
|
|
4381
|
+
this.goTo = (index) => {
|
|
4382
|
+
const item = this.items[index];
|
|
4383
|
+
if (!item) return;
|
|
4384
|
+
this._active = item;
|
|
4385
|
+
this._render();
|
|
4386
|
+
const controls = this._controls(item).filter((control) => !control.disabled);
|
|
4387
|
+
(controls.find((control) => control.checked) ?? controls[0])?.focus();
|
|
4388
|
+
};
|
|
4389
|
+
/** Checks the shown question and moves to the next one if it is answered. */
|
|
4390
|
+
this.next = () => {
|
|
4391
|
+
if (this._validate(this._active)) this.goTo(this.index + 1);
|
|
4392
|
+
};
|
|
4393
|
+
/** Moves to the previous question. */
|
|
4394
|
+
this.previous = () => this.goTo(this.index - 1);
|
|
4395
|
+
/** Clears the answer to the shown question and moves to the next one. */
|
|
4396
|
+
this.skip = () => {
|
|
4397
|
+
if (!this._active) return;
|
|
4398
|
+
for (const control of this._controls(this._active)) {
|
|
4399
|
+
const isChoice = control instanceof HTMLInputElement && (control.type === "radio" || control.type === "checkbox");
|
|
4400
|
+
if (isChoice ? !control.checked : !control.value) continue;
|
|
4401
|
+
if (isChoice) control.checked = false;
|
|
4402
|
+
else control.value = "";
|
|
4403
|
+
control.dispatchEvent(new Event("change", { bubbles: true }));
|
|
4404
|
+
}
|
|
4405
|
+
this.goTo(this.index + 1);
|
|
4406
|
+
};
|
|
4407
|
+
this._handleClick = (e) => {
|
|
4408
|
+
const button = e.target.closest?.("button");
|
|
4409
|
+
if (!button || !this._buttons().includes(button)) return;
|
|
4410
|
+
if (NAVIGATION.includes(button.value)) {
|
|
4411
|
+
e.preventDefault();
|
|
4412
|
+
this[button.value]();
|
|
4413
|
+
return;
|
|
4414
|
+
}
|
|
4415
|
+
this._syncRequired();
|
|
4416
|
+
};
|
|
4417
|
+
// Enter in a field moves on instead of submitting the form early.
|
|
4418
|
+
this._handleKeydown = (e) => {
|
|
4419
|
+
if (e.key !== "Enter" || e.defaultPrevented || e.isComposing) return;
|
|
4420
|
+
if (!(e.target instanceof HTMLInputElement) || this.index >= this.items.length - 1) return;
|
|
4421
|
+
e.preventDefault();
|
|
4422
|
+
this.next();
|
|
4423
|
+
};
|
|
4424
|
+
// A submission found an unanswered question, possibly a hidden one. Open the
|
|
4425
|
+
// first instead of letting the browser report a control it cannot show.
|
|
4426
|
+
// One submission fires its invalid events in a single task.
|
|
4427
|
+
this._handleInvalid = (e) => {
|
|
4428
|
+
const item = this.items.find((item2) => item2.contains(e.target));
|
|
4429
|
+
if (!item) return;
|
|
4430
|
+
e.preventDefault();
|
|
4431
|
+
if (this._reporting) return;
|
|
4432
|
+
this._reporting = true;
|
|
4433
|
+
setTimeout(() => this._reporting = false);
|
|
4434
|
+
this._active = item;
|
|
4435
|
+
this._render();
|
|
4436
|
+
this._validate(item);
|
|
4437
|
+
};
|
|
4438
|
+
// An answer clears the question's error.
|
|
4439
|
+
this._handleInput = (e) => {
|
|
4440
|
+
this._syncRequired();
|
|
4441
|
+
const item = e.target.closest?.("fieldset");
|
|
4442
|
+
if (item instanceof HTMLFieldSetElement && item.classList.contains("invalid")) this._setInvalid(item, false);
|
|
4443
|
+
};
|
|
4444
|
+
this.el["Expressive_Questionnaire"] = this;
|
|
4445
|
+
const progressLabel = el.dataset.progressLabel;
|
|
4446
|
+
this.options = {
|
|
4447
|
+
..._Questionnaire.defaults,
|
|
4448
|
+
...progressLabel ? { progressLabel } : {},
|
|
4449
|
+
...options
|
|
4450
|
+
};
|
|
4451
|
+
this._progress = document.createElement("div");
|
|
4452
|
+
this._progress.className = "questionnaire-progress";
|
|
4453
|
+
this._progressText = document.createElement("span");
|
|
4454
|
+
this._progressText.id = `questionnaire-progress-${Utils.guid()}`;
|
|
4455
|
+
this._progressBar = document.createElement("progress");
|
|
4456
|
+
this._progressBar.className = "progress";
|
|
4457
|
+
this._progressBar.setAttribute("aria-labelledby", this._progressText.id);
|
|
4458
|
+
this._progress.append(this._progressText, this._progressBar);
|
|
4459
|
+
this.el.prepend(this._progress);
|
|
4460
|
+
for (const item of this._fieldsets()) {
|
|
4461
|
+
const ids = Array.from(item.querySelectorAll(":scope > p:not(.questionnaire-error)")).map((p) => this._ensureId(p, "questionnaire-description"));
|
|
4462
|
+
if (ids.length && !item.hasAttribute("aria-describedby")) {
|
|
4463
|
+
item.setAttribute("aria-describedby", ids.join(" "));
|
|
4464
|
+
this._generatedIds.push(item);
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4467
|
+
this._active = this.items[0] ?? null;
|
|
4468
|
+
this._syncRequired();
|
|
4469
|
+
this._render();
|
|
4470
|
+
this._observer = new MutationObserver(() => {
|
|
4471
|
+
this._syncRequired();
|
|
4472
|
+
this._render();
|
|
4473
|
+
});
|
|
4474
|
+
this._observer.observe(this.el, { attributes: true, subtree: true, attributeFilter: ["disabled"] });
|
|
4475
|
+
this.el.addEventListener("click", this._handleClick);
|
|
4476
|
+
this.el.addEventListener("keydown", this._handleKeydown);
|
|
4477
|
+
this.el.addEventListener("input", this._handleInput);
|
|
4478
|
+
this.el.addEventListener("invalid", this._handleInvalid, true);
|
|
4479
|
+
}
|
|
4480
|
+
static get defaults() {
|
|
4481
|
+
return _defaults13;
|
|
4482
|
+
}
|
|
4483
|
+
/**
|
|
4484
|
+
* Initializes instances of Questionnaire.
|
|
4485
|
+
* @param els HTML elements.
|
|
4486
|
+
* @param options Component options.
|
|
4487
|
+
*/
|
|
4488
|
+
static init(els, options = {}) {
|
|
4489
|
+
return super.init(els, options, _Questionnaire);
|
|
4490
|
+
}
|
|
4491
|
+
static getInstance(el) {
|
|
4492
|
+
return el["Expressive_Questionnaire"];
|
|
4493
|
+
}
|
|
4494
|
+
/** The questions that apply: every fieldset child that is not disabled. */
|
|
4495
|
+
get items() {
|
|
4496
|
+
return this._fieldsets().filter((item) => !item.disabled);
|
|
4497
|
+
}
|
|
4498
|
+
/** Position of the shown question in `items`. */
|
|
4499
|
+
get index() {
|
|
4500
|
+
return this.items.indexOf(this._active);
|
|
4501
|
+
}
|
|
4502
|
+
destroy() {
|
|
4503
|
+
this._observer.disconnect();
|
|
4504
|
+
this.el.removeEventListener("click", this._handleClick);
|
|
4505
|
+
this.el.removeEventListener("keydown", this._handleKeydown);
|
|
4506
|
+
this.el.removeEventListener("input", this._handleInput);
|
|
4507
|
+
this.el.removeEventListener("invalid", this._handleInvalid, true);
|
|
4508
|
+
for (const item of this._fieldsets()) {
|
|
4509
|
+
if (item.hasAttribute("data-required")) for (const control of this._controls(item)) control.setCustomValidity("");
|
|
4510
|
+
item.hidden = false;
|
|
4511
|
+
this._setInvalid(item, false);
|
|
4512
|
+
}
|
|
4513
|
+
for (const button of this._buttons()) button.hidden = false;
|
|
4514
|
+
this._progress.remove();
|
|
4515
|
+
for (const el of this._generated) el.remove();
|
|
4516
|
+
for (const el of this._generatedIds) {
|
|
4517
|
+
el.removeAttribute(el instanceof HTMLFieldSetElement ? "aria-describedby" : "id");
|
|
4518
|
+
}
|
|
4519
|
+
this.el["Expressive_Questionnaire"] = void 0;
|
|
4520
|
+
}
|
|
4521
|
+
_fieldsets() {
|
|
4522
|
+
return Array.from(this.el.querySelectorAll(":scope > fieldset"));
|
|
4523
|
+
}
|
|
4524
|
+
/** The footer's navigation and submit buttons. */
|
|
4525
|
+
_buttons() {
|
|
4526
|
+
return Array.from(this.el.querySelectorAll(":scope > footer > button")).filter(
|
|
4527
|
+
(button) => NAVIGATION.includes(button.value) || button.type === "submit"
|
|
4528
|
+
);
|
|
4529
|
+
}
|
|
4530
|
+
_controls(item) {
|
|
4531
|
+
return Array.from(item.elements).filter(
|
|
4532
|
+
(el) => el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement || el instanceof HTMLInputElement && el.type !== "button" && el.type !== "submit"
|
|
4533
|
+
);
|
|
4534
|
+
}
|
|
4535
|
+
_required(item) {
|
|
4536
|
+
return item.hasAttribute("data-required") || this._controls(item).some((control) => control.required);
|
|
4537
|
+
}
|
|
4538
|
+
_ensureId(el, prefix) {
|
|
4539
|
+
if (!el.id) {
|
|
4540
|
+
el.id = `${prefix}-${Utils.guid()}`;
|
|
4541
|
+
this._generatedIds.push(el);
|
|
4542
|
+
}
|
|
4543
|
+
return el.id;
|
|
4544
|
+
}
|
|
4545
|
+
_render() {
|
|
4546
|
+
const items = this.items;
|
|
4547
|
+
if (!items.includes(this._active)) {
|
|
4548
|
+
const all = this._fieldsets();
|
|
4549
|
+
const after = all.slice(all.indexOf(this._active) + 1).find((item) => !item.disabled);
|
|
4550
|
+
this._active = after ?? items[items.length - 1] ?? null;
|
|
4551
|
+
}
|
|
4552
|
+
const index = items.indexOf(this._active);
|
|
4553
|
+
const last = index === items.length - 1;
|
|
4554
|
+
for (const item of this._fieldsets()) item.hidden = item !== this._active;
|
|
4555
|
+
for (const button of this._buttons()) {
|
|
4556
|
+
if (button.value === "previous") button.hidden = index <= 0;
|
|
4557
|
+
else if (button.value === "skip") button.hidden = last || !this._active || this._required(this._active);
|
|
4558
|
+
else if (button.value === "next") button.hidden = last;
|
|
4559
|
+
else button.hidden = !last;
|
|
4560
|
+
}
|
|
4561
|
+
this._progressText.textContent = this.options.progressLabel.replace("{current}", String(index + 1)).replace("{total}", String(items.length));
|
|
4562
|
+
this._progressBar.max = Math.max(items.length, 1);
|
|
4563
|
+
this._progressBar.value = index + 1;
|
|
4564
|
+
}
|
|
4565
|
+
/**
|
|
4566
|
+
* Puts "choose at least one" on the first enabled checkbox of each
|
|
4567
|
+
* data-required group with none checked, so the browser's validation, and
|
|
4568
|
+
* every way of submitting, sees it. A disabled checkbox is not sent, so it
|
|
4569
|
+
* does not count.
|
|
4570
|
+
*/
|
|
4571
|
+
_syncRequired() {
|
|
4572
|
+
for (const item of this._fieldsets()) {
|
|
4573
|
+
if (!item.hasAttribute("data-required")) continue;
|
|
4574
|
+
const boxes = this._controls(item).filter(
|
|
4575
|
+
(control) => control instanceof HTMLInputElement && control.type === "checkbox"
|
|
4576
|
+
);
|
|
4577
|
+
const enabled = boxes.filter((box) => !box.disabled);
|
|
4578
|
+
for (const box of boxes) box.setCustomValidity("");
|
|
4579
|
+
if (enabled.length && !enabled.some((box) => box.checked)) {
|
|
4580
|
+
const error = item.querySelector(":scope > .questionnaire-error:not([data-generated])");
|
|
4581
|
+
enabled[0].setCustomValidity(error?.textContent.trim() || this.options.requiredMessage);
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
/** The first control in the question that still needs an answer. */
|
|
4586
|
+
_invalidControl(item) {
|
|
4587
|
+
return this._controls(item).find((control) => control.willValidate && !control.validity.valid);
|
|
4588
|
+
}
|
|
4589
|
+
/** Shows the question's error and returns false when it needs an answer. */
|
|
4590
|
+
_validate(item) {
|
|
4591
|
+
this._syncRequired();
|
|
4592
|
+
const invalid = item && this._invalidControl(item);
|
|
4593
|
+
if (!invalid) return true;
|
|
4594
|
+
let error = item.querySelector(":scope > .questionnaire-error");
|
|
4595
|
+
if (!error) {
|
|
4596
|
+
error = document.createElement("p");
|
|
4597
|
+
error.className = "questionnaire-error";
|
|
4598
|
+
error.dataset.generated = "";
|
|
4599
|
+
error.textContent = invalid.validationMessage || this.options.requiredMessage;
|
|
4600
|
+
item.append(error);
|
|
4601
|
+
this._generated.push(error);
|
|
4602
|
+
}
|
|
4603
|
+
this._setInvalid(item, true);
|
|
4604
|
+
invalid.focus();
|
|
4605
|
+
return false;
|
|
4606
|
+
}
|
|
4607
|
+
_setInvalid(item, invalid) {
|
|
4608
|
+
item.classList.toggle("invalid", invalid);
|
|
4609
|
+
const error = item.querySelector(":scope > .questionnaire-error");
|
|
4610
|
+
const described = (item.getAttribute("aria-describedby") ?? "").split(" ").filter((id) => id && id !== error?.id);
|
|
4611
|
+
if (invalid && error) described.push(this._ensureId(error, "questionnaire-error"));
|
|
4612
|
+
if (described.length) item.setAttribute("aria-describedby", described.join(" "));
|
|
4613
|
+
else item.removeAttribute("aria-describedby");
|
|
4614
|
+
for (const control of this._controls(item)) {
|
|
4615
|
+
if (invalid) control.setAttribute("aria-invalid", "true");
|
|
4616
|
+
else control.removeAttribute("aria-invalid");
|
|
4617
|
+
}
|
|
4618
|
+
if (!invalid && this._generated.includes(error)) {
|
|
4619
|
+
error.remove();
|
|
4620
|
+
this._generated.splice(this._generated.indexOf(error), 1);
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
};
|
|
4624
|
+
|
|
4625
|
+
// src/ts/components/lineChart.ts
|
|
4626
|
+
var _defaults14 = {
|
|
4627
|
+
min: null,
|
|
4628
|
+
max: null
|
|
4629
|
+
};
|
|
4630
|
+
var SVG = "http://www.w3.org/2000/svg";
|
|
4631
|
+
var COLORS = 4;
|
|
4632
|
+
function svgNode(name, attributes = {}) {
|
|
4633
|
+
const element = document.createElementNS(SVG, name);
|
|
4634
|
+
for (const key in attributes) element.setAttribute(key, attributes[key]);
|
|
4635
|
+
return element;
|
|
4636
|
+
}
|
|
4637
|
+
var _gradientId = 0;
|
|
4638
|
+
function cellValue(cell) {
|
|
4639
|
+
if (!cell) return null;
|
|
4640
|
+
const raw = cell.dataset.value ?? cell.textContent;
|
|
4641
|
+
const text = raw.replace(/\u2212/g, "-").replace(/[\s%\p{Sc}]/gu, "");
|
|
4642
|
+
if (!/^[+-]?(\d{1,3}(,\d{3})+|\d+)?(\.\d+)?(e[+-]?\d+)?$/i.test(text) || !/\d/.test(text)) return null;
|
|
4643
|
+
return parseFloat(text.replace(/,/g, ""));
|
|
4644
|
+
}
|
|
4645
|
+
function monotonePath(points) {
|
|
4646
|
+
const n = points.length;
|
|
4647
|
+
if (n === 0) return "";
|
|
4648
|
+
if (n === 1) return `M${points[0][0]},${points[0][1]}h0`;
|
|
4649
|
+
const slopes = [];
|
|
4650
|
+
for (let i = 0; i < n - 1; i++) {
|
|
4651
|
+
slopes.push((points[i + 1][1] - points[i][1]) / (points[i + 1][0] - points[i][0]));
|
|
4652
|
+
}
|
|
4653
|
+
const tangents = points.map((_, i) => {
|
|
4654
|
+
if (i === 0) return slopes[0];
|
|
4655
|
+
if (i === n - 1) return slopes[n - 2];
|
|
4656
|
+
const [a, b] = [slopes[i - 1], slopes[i]];
|
|
4657
|
+
return a * b <= 0 ? 0 : 2 / (1 / a + 1 / b);
|
|
4658
|
+
});
|
|
4659
|
+
let d = `M${points[0][0]},${points[0][1]}`;
|
|
4660
|
+
for (let i = 0; i < n - 1; i++) {
|
|
4661
|
+
const [[x0, y0], [x1, y1]] = [points[i], points[i + 1]];
|
|
4662
|
+
const h = (x1 - x0) / 3;
|
|
4663
|
+
d += `C${x0 + h},${y0 + tangents[i] * h} ${x1 - h},${y1 - tangents[i + 1] * h} ${x1},${y1}`;
|
|
4664
|
+
}
|
|
4665
|
+
return d;
|
|
4666
|
+
}
|
|
4667
|
+
var LineChart = class extends Component {
|
|
4668
|
+
constructor(el, options) {
|
|
4669
|
+
super(el, options, new.target);
|
|
4670
|
+
/** The values' x positions and series, read from the table. */
|
|
4671
|
+
this.labels = [];
|
|
4672
|
+
this.series = [];
|
|
4673
|
+
/** The highlighted row, or -1. */
|
|
4674
|
+
this.activeIndex = -1;
|
|
4675
|
+
this._generated = [];
|
|
4676
|
+
/** The table's header cells and body rows, as read. */
|
|
4677
|
+
this._header = [];
|
|
4678
|
+
this._rows = [];
|
|
4679
|
+
/**
|
|
4680
|
+
* Whether the rows run right to left: a horizontal row axis in a
|
|
4681
|
+
* right-to-left context, like the labels. Bar chart rows keep their order.
|
|
4682
|
+
*/
|
|
4683
|
+
this._reversed = false;
|
|
4684
|
+
this._onPointerMove = (e) => {
|
|
4685
|
+
this.show(this._indexAt(e));
|
|
4686
|
+
};
|
|
4687
|
+
this._onFocus = () => {
|
|
4688
|
+
if (this.activeIndex < 0 && this._plot.matches(":focus-visible")) this.show(0);
|
|
4689
|
+
};
|
|
4690
|
+
this._onLeave = (e) => {
|
|
4691
|
+
if (e.pointerType === "touch" || this._plot.matches(":focus-visible")) return;
|
|
4692
|
+
this.show(-1);
|
|
4693
|
+
};
|
|
4694
|
+
/** The browser took the touch to scroll the page. */
|
|
4695
|
+
this._onCancel = () => {
|
|
4696
|
+
this.show(-1);
|
|
4697
|
+
};
|
|
4698
|
+
this._onKeyDown = (e) => {
|
|
4699
|
+
const next = this._keyTarget(e.key);
|
|
4700
|
+
if (next === void 0) return;
|
|
4701
|
+
e.preventDefault();
|
|
4702
|
+
this.show(next);
|
|
4703
|
+
};
|
|
4704
|
+
this.el[this._key] = this;
|
|
4705
|
+
this.options = {
|
|
4706
|
+
...new.target.defaults,
|
|
4707
|
+
...options
|
|
4708
|
+
};
|
|
4709
|
+
this._read();
|
|
4710
|
+
if (!this.labels.length || !this.series.length) return;
|
|
4711
|
+
this._reversed = !this._horizontal && getComputedStyle(this.el).direction === "rtl";
|
|
4712
|
+
this._render();
|
|
4713
|
+
if (!this.el.classList.contains("sparkline")) {
|
|
4714
|
+
this._plot.tabIndex = 0;
|
|
4715
|
+
this._plot.addEventListener("pointerdown", this._onPointerMove);
|
|
4716
|
+
this._plot.addEventListener("pointermove", this._onPointerMove);
|
|
4717
|
+
this._plot.addEventListener("pointerleave", this._onLeave);
|
|
4718
|
+
this._plot.addEventListener("pointercancel", this._onCancel);
|
|
4719
|
+
this._plot.addEventListener("keydown", this._onKeyDown);
|
|
4720
|
+
this._plot.addEventListener("focus", this._onFocus);
|
|
4721
|
+
this._plot.addEventListener("blur", this._onLeave);
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
/** The class prefix of the generated parts; ColumnChart reuses this class. */
|
|
4725
|
+
get _type() {
|
|
4726
|
+
return "line-chart";
|
|
4727
|
+
}
|
|
4728
|
+
/** Whether the y axis always includes zero, as columns grow from it. */
|
|
4729
|
+
get _fromZero() {
|
|
4730
|
+
return false;
|
|
4731
|
+
}
|
|
4732
|
+
/**
|
|
4733
|
+
* Whether the rows run down the plot and the values across it, as in a
|
|
4734
|
+
* bar chart. The rows then keep their order in a right-to-left context.
|
|
4735
|
+
*/
|
|
4736
|
+
get _horizontal() {
|
|
4737
|
+
return false;
|
|
4738
|
+
}
|
|
4739
|
+
/**
|
|
4740
|
+
* Whether the rows go round a circle, as in a pie or radar chart. The
|
|
4741
|
+
* arrows then step round it, wrapping from the last row to the first.
|
|
4742
|
+
*/
|
|
4743
|
+
get _circular() {
|
|
4744
|
+
return false;
|
|
4745
|
+
}
|
|
4746
|
+
/** Whether the cursor marks each series with a point. */
|
|
4747
|
+
get _points() {
|
|
4748
|
+
return true;
|
|
4749
|
+
}
|
|
4750
|
+
get _key() {
|
|
4751
|
+
return "Expressive_LineChart";
|
|
4752
|
+
}
|
|
4753
|
+
static get defaults() {
|
|
4754
|
+
return _defaults14;
|
|
4755
|
+
}
|
|
4756
|
+
static init(els, options = {}) {
|
|
4757
|
+
return super.init(els, options, this);
|
|
4758
|
+
}
|
|
4759
|
+
static getInstance(el) {
|
|
4760
|
+
return el["Expressive_LineChart"];
|
|
4761
|
+
}
|
|
4762
|
+
destroy() {
|
|
4763
|
+
this._generated.forEach((node) => node.remove());
|
|
4764
|
+
this._generated = [];
|
|
4765
|
+
this.el[this._key] = void 0;
|
|
4766
|
+
}
|
|
4767
|
+
/**
|
|
4768
|
+
* Highlights one row: moves the cursor and fills the tooltip.
|
|
4769
|
+
* Pass -1 to hide them. Does nothing for a chart without data.
|
|
4770
|
+
*/
|
|
4771
|
+
show(index) {
|
|
4772
|
+
if (!this._tooltip || index === this.activeIndex) return;
|
|
4773
|
+
this.activeIndex = index;
|
|
4774
|
+
const hidden = index < 0 || index >= this.labels.length;
|
|
4775
|
+
this._cursor.hidden = this._tooltip.hidden = hidden;
|
|
4776
|
+
if (hidden) return;
|
|
4777
|
+
this._cursor.replaceChildren();
|
|
4778
|
+
const title = document.createElement("strong");
|
|
4779
|
+
title.textContent = this.labels[index];
|
|
4780
|
+
this._tooltip.replaceChildren(title);
|
|
4781
|
+
this.series.forEach((series, i) => {
|
|
4782
|
+
const value = series.values[index];
|
|
4783
|
+
if (value === null) return;
|
|
4784
|
+
const color = String(i % COLORS + 1);
|
|
4785
|
+
const top = series.tops[index];
|
|
4786
|
+
if (top !== null && this._points) {
|
|
4787
|
+
const dot = document.createElement("span");
|
|
4788
|
+
dot.className = series.className;
|
|
4789
|
+
dot.dataset.series = color;
|
|
4790
|
+
dot.style.top = `${series.y(top)}%`;
|
|
4791
|
+
this._cursor.append(dot);
|
|
4792
|
+
}
|
|
4793
|
+
const name = document.createElement("span");
|
|
4794
|
+
name.dataset.series = color;
|
|
4795
|
+
name.textContent = series.name;
|
|
4796
|
+
const text = document.createElement("span");
|
|
4797
|
+
text.textContent = series.text[index];
|
|
4798
|
+
this._tooltip.append(name, text);
|
|
4799
|
+
});
|
|
4800
|
+
this._place(index);
|
|
4801
|
+
}
|
|
4802
|
+
/**
|
|
4803
|
+
* Moves the filled cursor and tooltip to a row: beside its band, with the
|
|
4804
|
+
* tooltip opening back over the plot past the middle.
|
|
4805
|
+
*/
|
|
4806
|
+
_place(index) {
|
|
4807
|
+
const x = this._x(index);
|
|
4808
|
+
const side = this._horizontal ? "top" : "left";
|
|
4809
|
+
this._cursor.style[side] = this._tooltip.style[side] = `${x}%`;
|
|
4810
|
+
this._tooltip.classList.toggle("end", x > 50);
|
|
4811
|
+
}
|
|
4812
|
+
_read() {
|
|
4813
|
+
const table = this.el.querySelector(":scope > table");
|
|
4814
|
+
if (!table) return;
|
|
4815
|
+
const headerRow = table.tHead?.rows[0] ?? table.rows[0];
|
|
4816
|
+
const header = Array.from(headerRow?.cells ?? []);
|
|
4817
|
+
const rows = Array.from(table.tBodies).flatMap((body) => Array.from(body.rows)).filter((row) => row !== headerRow);
|
|
4818
|
+
[this._header, this._rows] = [header, rows];
|
|
4819
|
+
this.labels = rows.map((row) => row.cells[0]?.textContent.trim() ?? "");
|
|
4820
|
+
this.series = header.slice(1).map((cell, i) => {
|
|
4821
|
+
const values = rows.map((row) => cellValue(row.cells[i + 1]));
|
|
4822
|
+
return {
|
|
4823
|
+
name: cell.textContent.trim(),
|
|
4824
|
+
className: cell.className,
|
|
4825
|
+
text: rows.map((row) => row.cells[i + 1]?.textContent.trim() ?? ""),
|
|
4826
|
+
values,
|
|
4827
|
+
tops: values,
|
|
4828
|
+
y: void 0
|
|
4829
|
+
};
|
|
4830
|
+
});
|
|
4831
|
+
const stacked = this.el.classList.contains("stacked");
|
|
4832
|
+
if (stacked) {
|
|
4833
|
+
const totals = this.labels.map(() => 0);
|
|
4834
|
+
for (const series of this.series) {
|
|
4835
|
+
series.base = [...totals];
|
|
4836
|
+
series.tops = series.values.map((value, i) => totals[i] = value === null || totals[i] === null ? null : totals[i] + value);
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
if (this.series.every((series) => series.tops.every((value) => value === null))) this.series = [];
|
|
4840
|
+
const option = (name) => this._number(name) ?? this.options[name];
|
|
4841
|
+
this._y = this._scale(this.series, option("min"), option("max"));
|
|
4842
|
+
}
|
|
4843
|
+
/** A number from one of the chart's data attributes, or null. */
|
|
4844
|
+
_number(name) {
|
|
4845
|
+
const value = parseFloat(this.el.dataset[name] ?? "");
|
|
4846
|
+
return Number.isFinite(value) ? value : null;
|
|
4847
|
+
}
|
|
4848
|
+
/**
|
|
4849
|
+
* Fits a y scale to the series' tops, between `min` and `max` where they
|
|
4850
|
+
* are set, and gives it to each series.
|
|
4851
|
+
*/
|
|
4852
|
+
_scale(series, min, max, fromZero = this._fromZero) {
|
|
4853
|
+
const stacked = this.el.classList.contains("stacked");
|
|
4854
|
+
let [low, high] = [Infinity, -Infinity];
|
|
4855
|
+
for (const { tops } of series) {
|
|
4856
|
+
for (const value of tops) {
|
|
4857
|
+
if (value === null) continue;
|
|
4858
|
+
low = Math.min(low, value);
|
|
4859
|
+
high = Math.max(high, value);
|
|
4860
|
+
}
|
|
4861
|
+
}
|
|
4862
|
+
if (low > high) low = high = 0;
|
|
4863
|
+
if (fromZero) {
|
|
4864
|
+
const [bottom, top] = [Math.min(0, low), Math.max(0, high)];
|
|
4865
|
+
const room = (top - (min ?? bottom)) * 0.1 || 1;
|
|
4866
|
+
min ?? (min = bottom < 0 ? bottom - room : 0);
|
|
4867
|
+
max ?? (max = top + room);
|
|
4868
|
+
} else {
|
|
4869
|
+
const pad = (high - low) * 0.1 || 1;
|
|
4870
|
+
min ?? (min = stacked ? Math.min(0, low) : low - pad);
|
|
4871
|
+
max ?? (max = high + (stacked ? (high - min) * 0.1 || 1 : pad));
|
|
4872
|
+
}
|
|
4873
|
+
const y = (value) => 100 - (value - min) / (max - min || 1) * 100;
|
|
4874
|
+
for (const each of series) each.y = y;
|
|
4875
|
+
return y;
|
|
4876
|
+
}
|
|
4877
|
+
/** Each row sits in the middle of an equal band, beside its label. */
|
|
4878
|
+
_x(index) {
|
|
4879
|
+
const x = (index + 0.5) / this.labels.length * 100;
|
|
4880
|
+
return this._reversed ? 100 - x : x;
|
|
4881
|
+
}
|
|
4882
|
+
_render() {
|
|
4883
|
+
const type = this._type;
|
|
4884
|
+
this._plot = document.createElement("div");
|
|
4885
|
+
this._plot.className = `${type}-plot`;
|
|
4886
|
+
const caption = this.el.querySelector(":scope > figcaption")?.textContent.trim();
|
|
4887
|
+
this._plot.setAttribute("role", "group");
|
|
4888
|
+
this._plot.setAttribute("aria-roledescription", type.replace(/-/g, " "));
|
|
4889
|
+
if (caption) this._plot.setAttribute("aria-label", caption);
|
|
4890
|
+
this._cursor = document.createElement("div");
|
|
4891
|
+
this._cursor.className = `${type}-cursor`;
|
|
4892
|
+
this._cursor.hidden = true;
|
|
4893
|
+
this._tooltip = document.createElement("div");
|
|
4894
|
+
this._tooltip.className = `${type}-tooltip`;
|
|
4895
|
+
this._tooltip.hidden = true;
|
|
4896
|
+
this._tooltip.setAttribute("aria-live", "polite");
|
|
4897
|
+
this._plot.append(this._draw(), this._cursor, this._tooltip);
|
|
4898
|
+
this._generated = [this._plot, ...this._guides()];
|
|
4899
|
+
this.el.append(...this._generated);
|
|
4900
|
+
}
|
|
4901
|
+
/**
|
|
4902
|
+
* The <tfoot> row as a total to show over the plot, its value over its
|
|
4903
|
+
* label, or null without one.
|
|
4904
|
+
*/
|
|
4905
|
+
_total(className) {
|
|
4906
|
+
const footer = this.el.querySelector(":scope > table")?.tFoot?.rows[0];
|
|
4907
|
+
if (!footer) return null;
|
|
4908
|
+
const total = document.createElement("div");
|
|
4909
|
+
total.className = className;
|
|
4910
|
+
const [label, value] = [document.createElement("span"), document.createElement("strong")];
|
|
4911
|
+
label.textContent = footer.cells[0]?.textContent.trim() ?? "";
|
|
4912
|
+
value.textContent = footer.cells[1]?.textContent.trim() ?? "";
|
|
4913
|
+
total.append(value, label);
|
|
4914
|
+
return total;
|
|
4915
|
+
}
|
|
4916
|
+
/** The parts below the plot: the row labels and the legend. */
|
|
4917
|
+
_guides() {
|
|
4918
|
+
const labels = document.createElement("ol");
|
|
4919
|
+
labels.className = `${this._type}-labels`;
|
|
4920
|
+
labels.setAttribute("aria-hidden", "true");
|
|
4921
|
+
for (const text of this.labels) {
|
|
4922
|
+
const li = document.createElement("li");
|
|
4923
|
+
li.textContent = text;
|
|
4924
|
+
labels.append(li);
|
|
4925
|
+
}
|
|
4926
|
+
return [labels, ...this._legend()];
|
|
4927
|
+
}
|
|
4928
|
+
/** The series legend, for more than one series. */
|
|
4929
|
+
_legend() {
|
|
4930
|
+
if (this.series.length < 2) return [];
|
|
4931
|
+
const legend = document.createElement("ul");
|
|
4932
|
+
legend.className = `${this._type}-legend`;
|
|
4933
|
+
legend.setAttribute("aria-hidden", "true");
|
|
4934
|
+
legend.append(...this.series.map((series, i) => {
|
|
4935
|
+
const li = document.createElement("li");
|
|
4936
|
+
li.dataset.series = String(i % COLORS + 1);
|
|
4937
|
+
li.textContent = series.name;
|
|
4938
|
+
return li;
|
|
4939
|
+
}));
|
|
4940
|
+
return [legend];
|
|
4941
|
+
}
|
|
4942
|
+
/** Draws the series, which fill the plot. */
|
|
4943
|
+
_draw(drawn = this.series) {
|
|
4944
|
+
const svg = document.createElementNS(SVG, "svg");
|
|
4945
|
+
svg.setAttribute("viewBox", "0 0 100 100");
|
|
4946
|
+
svg.setAttribute("preserveAspectRatio", "none");
|
|
4947
|
+
svg.setAttribute("aria-hidden", "true");
|
|
4948
|
+
svg.setAttribute("focusable", "false");
|
|
4949
|
+
for (const series of drawn) {
|
|
4950
|
+
const group = document.createElementNS(SVG, "g");
|
|
4951
|
+
group.setAttribute("class", `${this._type}-series ${series.className}`.trim());
|
|
4952
|
+
group.dataset.series = String(this.series.indexOf(series) % COLORS + 1);
|
|
4953
|
+
const runs = [[]];
|
|
4954
|
+
series.tops.forEach((value, index) => {
|
|
4955
|
+
if (value === null) runs.push([]);
|
|
4956
|
+
else runs[runs.length - 1].push(index);
|
|
4957
|
+
});
|
|
4958
|
+
const filled = runs.filter((run) => run.length);
|
|
4959
|
+
const points = (run, values) => run.map((index) => [this._x(index), series.y(values[index])]);
|
|
4960
|
+
const tops = filled.map((run) => monotonePath(points(run, series.tops)));
|
|
4961
|
+
const id = `line-chart-gradient-${++_gradientId}`;
|
|
4962
|
+
const gradient = document.createElementNS(SVG, "linearGradient");
|
|
4963
|
+
gradient.id = id;
|
|
4964
|
+
gradient.setAttribute("x2", "0");
|
|
4965
|
+
gradient.setAttribute("y2", "1");
|
|
4966
|
+
gradient.append(document.createElementNS(SVG, "stop"), document.createElementNS(SVG, "stop"));
|
|
4967
|
+
gradient.lastElementChild.setAttribute("offset", "1");
|
|
4968
|
+
const area = document.createElementNS(SVG, "path");
|
|
4969
|
+
area.setAttribute("class", `${this._type}-area`);
|
|
4970
|
+
area.setAttribute("fill", `url(#${id})`);
|
|
4971
|
+
area.setAttribute("d", filled.map((run, r) => {
|
|
4972
|
+
if (!series.base) return `${tops[r]}V100H${this._x(run[0])}Z`;
|
|
4973
|
+
const base = points(run, series.base).map(([x, y]) => [x, Math.min(100, y)]);
|
|
4974
|
+
return `${tops[r]}L${monotonePath(base.reverse()).slice(1)}Z`;
|
|
4975
|
+
}).join(""));
|
|
4976
|
+
const line = document.createElementNS(SVG, "path");
|
|
4977
|
+
line.setAttribute("class", `${this._type}-line`);
|
|
4978
|
+
line.setAttribute("d", tops.join(""));
|
|
4979
|
+
group.append(gradient, area, line);
|
|
4980
|
+
svg.append(group);
|
|
4981
|
+
}
|
|
4982
|
+
return svg;
|
|
4983
|
+
}
|
|
4984
|
+
/** The row under the pointer, or -1. */
|
|
4985
|
+
_indexAt(e) {
|
|
4986
|
+
const box = this._plot.getBoundingClientRect();
|
|
4987
|
+
const fraction = this._horizontal ? (e.clientY - box.top) / box.height : (e.clientX - box.left) / box.width;
|
|
4988
|
+
const index = Math.floor((this._reversed ? 1 - fraction : fraction) * this.labels.length);
|
|
4989
|
+
return Math.min(this.labels.length - 1, Math.max(0, index));
|
|
4990
|
+
}
|
|
4991
|
+
/**
|
|
4992
|
+
* For a circular chart, the pointer's position from the plot's middle: a
|
|
4993
|
+
* fraction of a turn clockwise from the top, and its distance as a share
|
|
4994
|
+
* of the plot's width.
|
|
4995
|
+
*/
|
|
4996
|
+
_polarAt(e) {
|
|
4997
|
+
const box = this._plot.getBoundingClientRect();
|
|
4998
|
+
const [x, y] = [e.clientX - box.left - box.width / 2, e.clientY - box.top - box.height / 2];
|
|
4999
|
+
return [(Math.atan2(x, -y) / (2 * Math.PI) + 1) % 1, Math.hypot(x, y) / box.width];
|
|
5000
|
+
}
|
|
5001
|
+
/** The keys that move to the next row and to the one before. */
|
|
5002
|
+
get _arrows() {
|
|
5003
|
+
if (this._circular) return [["ArrowRight", "ArrowDown"], ["ArrowLeft", "ArrowUp"]];
|
|
5004
|
+
if (this._horizontal) return [["ArrowDown"], ["ArrowUp"]];
|
|
5005
|
+
return this._reversed ? [["ArrowLeft"], ["ArrowRight"]] : [["ArrowRight"], ["ArrowLeft"]];
|
|
5006
|
+
}
|
|
5007
|
+
/** The index a key moves to, or undefined for a key the chart ignores. */
|
|
5008
|
+
_keyTarget(key) {
|
|
5009
|
+
const last = this.labels.length - 1;
|
|
5010
|
+
const [forward, back] = this._arrows;
|
|
5011
|
+
const step = (by) => {
|
|
5012
|
+
if (!this._circular) return Math.min(last, Math.max(0, this.activeIndex + by));
|
|
5013
|
+
if (this.activeIndex < 0) return by > 0 ? 0 : last;
|
|
5014
|
+
return (this.activeIndex + by + last + 1) % (last + 1);
|
|
5015
|
+
};
|
|
5016
|
+
if (key === "Escape") return this.activeIndex < 0 ? void 0 : -1;
|
|
5017
|
+
return forward.includes(key) ? step(1) : back.includes(key) ? step(-1) : { Home: 0, End: last }[key];
|
|
5018
|
+
}
|
|
5019
|
+
};
|
|
5020
|
+
|
|
5021
|
+
// src/ts/components/columnChart.ts
|
|
5022
|
+
var ColumnChart = class extends LineChart {
|
|
5023
|
+
get _type() {
|
|
5024
|
+
return "column-chart";
|
|
5025
|
+
}
|
|
5026
|
+
get _key() {
|
|
5027
|
+
return "Expressive_ColumnChart";
|
|
5028
|
+
}
|
|
5029
|
+
get _fromZero() {
|
|
5030
|
+
return true;
|
|
5031
|
+
}
|
|
5032
|
+
get _points() {
|
|
5033
|
+
return false;
|
|
5034
|
+
}
|
|
5035
|
+
static init(els, options = {}) {
|
|
5036
|
+
return super.init(els, options);
|
|
5037
|
+
}
|
|
5038
|
+
static getInstance(el) {
|
|
5039
|
+
return el["Expressive_ColumnChart"];
|
|
5040
|
+
}
|
|
5041
|
+
_draw(drawn = this.series) {
|
|
5042
|
+
const columns = document.createElement("div");
|
|
5043
|
+
columns.className = "column-chart-columns";
|
|
5044
|
+
columns.setAttribute("aria-hidden", "true");
|
|
5045
|
+
const clamp = (value, y = drawn[0]?.y ?? this._y) => Math.min(100, Math.max(0, y(value)));
|
|
5046
|
+
columns.style.setProperty("--zero", `${clamp(0)}%`);
|
|
5047
|
+
this._plot.style.setProperty("--rows", String(this.labels.length));
|
|
5048
|
+
this.labels.forEach((_, index) => {
|
|
5049
|
+
const row = document.createElement("div");
|
|
5050
|
+
drawn.forEach((series) => {
|
|
5051
|
+
const to = series.tops[index];
|
|
5052
|
+
const from = series.base?.[index] ?? 0;
|
|
5053
|
+
if (series.base && (to === null || from === null)) return;
|
|
5054
|
+
const column = document.createElement("span");
|
|
5055
|
+
if (to !== null) {
|
|
5056
|
+
column.className = series.className;
|
|
5057
|
+
column.dataset.series = String(this.series.indexOf(series) % COLORS + 1);
|
|
5058
|
+
column.classList.toggle("negative", !series.base && to < from);
|
|
5059
|
+
column.style.setProperty("--top", `${clamp(Math.max(from, to), series.y)}%`);
|
|
5060
|
+
column.style.setProperty("--bottom", `${100 - clamp(Math.min(from, to), series.y)}%`);
|
|
5061
|
+
if (this._horizontal) column.dataset.text = series.text[index];
|
|
5062
|
+
}
|
|
5063
|
+
row.append(column);
|
|
5064
|
+
});
|
|
5065
|
+
columns.append(row);
|
|
5066
|
+
});
|
|
5067
|
+
return columns;
|
|
5068
|
+
}
|
|
5069
|
+
};
|
|
5070
|
+
|
|
5071
|
+
// src/ts/components/barChart.ts
|
|
5072
|
+
var BarChart = class extends ColumnChart {
|
|
5073
|
+
get _type() {
|
|
5074
|
+
return "bar-chart";
|
|
5075
|
+
}
|
|
5076
|
+
get _key() {
|
|
5077
|
+
return "Expressive_BarChart";
|
|
5078
|
+
}
|
|
5079
|
+
get _horizontal() {
|
|
5080
|
+
return true;
|
|
5081
|
+
}
|
|
5082
|
+
static init(els, options = {}) {
|
|
5083
|
+
return super.init(els, options);
|
|
5084
|
+
}
|
|
5085
|
+
static getInstance(el) {
|
|
5086
|
+
return el["Expressive_BarChart"];
|
|
5087
|
+
}
|
|
5088
|
+
/**
|
|
5089
|
+
* The column chart's spans; CSS reads their --top and --bottom across the
|
|
5090
|
+
* plot. With .values, a copy of the bars without their clipping holds each
|
|
5091
|
+
* cell's text, so text longer than a short bar can run past its end.
|
|
5092
|
+
*/
|
|
5093
|
+
_draw() {
|
|
5094
|
+
const bars = super._draw();
|
|
5095
|
+
bars.className = "bar-chart-bars";
|
|
5096
|
+
bars.classList.toggle("rtl", getComputedStyle(this.el).direction === "rtl");
|
|
5097
|
+
if (!this.el.classList.contains("values")) return bars;
|
|
5098
|
+
const values = bars.cloneNode(true);
|
|
5099
|
+
values.classList.replace("bar-chart-bars", "bar-chart-values");
|
|
5100
|
+
values.querySelectorAll("span[data-text]").forEach((span) => {
|
|
5101
|
+
span.textContent = span.dataset.text;
|
|
5102
|
+
});
|
|
5103
|
+
const layers = document.createElement("div");
|
|
5104
|
+
layers.className = "bar-chart-layers";
|
|
5105
|
+
layers.append(bars, values);
|
|
5106
|
+
return layers;
|
|
5107
|
+
}
|
|
5108
|
+
};
|
|
5109
|
+
|
|
5110
|
+
// src/ts/components/pieChart.ts
|
|
5111
|
+
var COLORS2 = 6;
|
|
5112
|
+
var RADIUS = 46;
|
|
5113
|
+
var GROWN = 1.06;
|
|
5114
|
+
var LABEL_SHARE = 0.05;
|
|
5115
|
+
var _maskId = 0;
|
|
5116
|
+
var PieChart = class extends LineChart {
|
|
5117
|
+
get _type() {
|
|
5118
|
+
return "pie-chart";
|
|
5119
|
+
}
|
|
5120
|
+
get _key() {
|
|
5121
|
+
return "Expressive_PieChart";
|
|
5122
|
+
}
|
|
5123
|
+
get _points() {
|
|
5124
|
+
return false;
|
|
5125
|
+
}
|
|
5126
|
+
get _circular() {
|
|
5127
|
+
return true;
|
|
5128
|
+
}
|
|
5129
|
+
static init(els, options = {}) {
|
|
5130
|
+
return super.init(els, options);
|
|
5131
|
+
}
|
|
5132
|
+
static getInstance(el) {
|
|
5133
|
+
return el["Expressive_PieChart"];
|
|
5134
|
+
}
|
|
5135
|
+
show(index) {
|
|
5136
|
+
if (!this._tooltip || index === this.activeIndex) return;
|
|
5137
|
+
super.show(index);
|
|
5138
|
+
this._slices.forEach((slice, i) => slice?.classList.toggle("active", i === index));
|
|
5139
|
+
if (this._tooltip.hidden) return;
|
|
5140
|
+
const color = this._colors[index];
|
|
5141
|
+
this._tooltip.querySelectorAll("[data-series]").forEach((span) => {
|
|
5142
|
+
if (color) span.dataset.series = color;
|
|
5143
|
+
else delete span.dataset.series;
|
|
5144
|
+
});
|
|
5145
|
+
const share = this._share(index);
|
|
5146
|
+
const value = this._tooltip.querySelector("span:nth-of-type(2)");
|
|
5147
|
+
if (share && value) value.textContent += ` (${share})`;
|
|
5148
|
+
}
|
|
5149
|
+
/** CSS places the tooltip on the slice's middle from --turn. */
|
|
5150
|
+
_place(index) {
|
|
5151
|
+
const middle = (this._turns[index][0] + this._turns[index][1]) / 2;
|
|
5152
|
+
this._tooltip.style.setProperty("--turn", String(middle));
|
|
5153
|
+
this._tooltip.classList.toggle("end", middle < 0.5);
|
|
5154
|
+
}
|
|
5155
|
+
/** The row's share of the whole as a percentage, or '' for none. */
|
|
5156
|
+
_share(index) {
|
|
5157
|
+
const [start, end] = this._turns[index];
|
|
5158
|
+
if (end <= start) return "";
|
|
5159
|
+
const share = end - start;
|
|
5160
|
+
const options = { style: "percent", maximumFractionDigits: share < 0.1 ? 1 : 0 };
|
|
5161
|
+
try {
|
|
5162
|
+
return share.toLocaleString(this.el.closest("[lang]")?.lang || void 0, options);
|
|
5163
|
+
} catch {
|
|
5164
|
+
return share.toLocaleString(void 0, options);
|
|
5165
|
+
}
|
|
5166
|
+
}
|
|
5167
|
+
/** The legend: every row with its color, its cell and its share. */
|
|
5168
|
+
_guides() {
|
|
5169
|
+
const legend = document.createElement("ul");
|
|
5170
|
+
legend.className = "pie-chart-legend";
|
|
5171
|
+
legend.setAttribute("aria-hidden", "true");
|
|
5172
|
+
const text = this.series[0].text;
|
|
5173
|
+
legend.append(...this.labels.map((label, i) => {
|
|
5174
|
+
const li = document.createElement("li");
|
|
5175
|
+
if (this._colors[i]) li.dataset.series = this._colors[i];
|
|
5176
|
+
const [name, value, share] = [label, text[i], this._share(i)].map((content) => {
|
|
5177
|
+
const span = document.createElement("span");
|
|
5178
|
+
span.textContent = content;
|
|
5179
|
+
return span;
|
|
5180
|
+
});
|
|
5181
|
+
li.append(name, value, share);
|
|
5182
|
+
return li;
|
|
5183
|
+
}));
|
|
5184
|
+
return [legend];
|
|
5185
|
+
}
|
|
5186
|
+
/** The row under the pointer by its angle from the middle, or -1 off the slices. */
|
|
5187
|
+
_indexAt(e) {
|
|
5188
|
+
const [turn, share] = this._polarAt(e);
|
|
5189
|
+
const distance = share * 100;
|
|
5190
|
+
const hole = this.el.classList.contains("donut") ? parseFloat(getComputedStyle(this.el).getPropertyValue("--md-comp-pie-chart-hole")) || 0 : 0;
|
|
5191
|
+
if (distance < RADIUS * hole) return -1;
|
|
5192
|
+
const index = this._turns.findIndex(([start, end]) => start <= turn && turn < end);
|
|
5193
|
+
return distance > RADIUS * (index === this.activeIndex ? GROWN : 1) ? -1 : index;
|
|
5194
|
+
}
|
|
5195
|
+
/**
|
|
5196
|
+
* An SVG of slices in a -50 to 50 box, under a mask that cuts the gaps
|
|
5197
|
+
* between them and the donut's hole, which CSS sizes. The values and the
|
|
5198
|
+
* total are HTML over it.
|
|
5199
|
+
*/
|
|
5200
|
+
_draw() {
|
|
5201
|
+
const values = this.series[0].values.map((value) => Math.max(0, value ?? 0));
|
|
5202
|
+
const total = values.reduce((sum, value) => sum + value, 0);
|
|
5203
|
+
let turn = 0;
|
|
5204
|
+
this._turns = values.map((value) => [turn, turn += total ? value / total : 0]);
|
|
5205
|
+
const drawn = this._turns.filter(([start, end]) => end > start).length;
|
|
5206
|
+
let slice = 0;
|
|
5207
|
+
this._colors = this._turns.map(([start, end]) => {
|
|
5208
|
+
if (end <= start) return void 0;
|
|
5209
|
+
const index = slice++;
|
|
5210
|
+
return String(index === drawn - 1 && index > 0 && index % COLORS2 === 0 ? 2 : index % COLORS2 + 1);
|
|
5211
|
+
});
|
|
5212
|
+
const at = (t, r = RADIUS) => {
|
|
5213
|
+
const angle = t * 2 * Math.PI;
|
|
5214
|
+
return `${(r * Math.sin(angle)).toFixed(3)},${(-r * Math.cos(angle)).toFixed(3)}`;
|
|
5215
|
+
};
|
|
5216
|
+
const pie = document.createElement("div");
|
|
5217
|
+
pie.className = "pie-chart-pie";
|
|
5218
|
+
pie.setAttribute("aria-hidden", "true");
|
|
5219
|
+
const svg = svgNode("svg", { viewBox: "-50 -50 100 100", focusable: "false" });
|
|
5220
|
+
const id = `pie-chart-mask-${++_maskId}`;
|
|
5221
|
+
const area = { x: "-50", y: "-50", width: "100", height: "100" };
|
|
5222
|
+
const mask = svgNode("mask", { id, maskUnits: "userSpaceOnUse", ...area });
|
|
5223
|
+
const edges = this._turns.filter(([start, end]) => end > start);
|
|
5224
|
+
mask.append(
|
|
5225
|
+
svgNode("rect", { ...area, fill: "white" }),
|
|
5226
|
+
svgNode("path", {
|
|
5227
|
+
class: "pie-chart-gaps",
|
|
5228
|
+
stroke: "black",
|
|
5229
|
+
d: edges.length > 1 ? edges.map(([start]) => `M0,0L${at(start, 50)}`).join("") : ""
|
|
5230
|
+
}),
|
|
5231
|
+
svgNode("circle", { class: "pie-chart-hole", r: String(RADIUS), fill: "black" })
|
|
5232
|
+
);
|
|
5233
|
+
const slices = svgNode("g", { mask: `url(#${id})` });
|
|
5234
|
+
const arc = `A${RADIUS},${RADIUS} 0`;
|
|
5235
|
+
this._slices = this._turns.map(([start, end], i) => {
|
|
5236
|
+
if (end <= start) return null;
|
|
5237
|
+
const slice2 = svgNode("path", {
|
|
5238
|
+
class: "pie-chart-slice",
|
|
5239
|
+
"data-series": this._colors[i],
|
|
5240
|
+
// A whole circle is two half arcs; one arc cannot end where it starts.
|
|
5241
|
+
d: end - start >= 1 ? `M0,${-RADIUS}${arc} 1 1 0,${RADIUS}${arc} 1 1 0,${-RADIUS}Z` : `M0,0L${at(start)}${arc} ${end - start > 0.5 ? 1 : 0} 1 ${at(end)}Z`
|
|
5242
|
+
});
|
|
5243
|
+
slices.append(slice2);
|
|
5244
|
+
return slice2;
|
|
5245
|
+
});
|
|
5246
|
+
svg.append(mask, slices);
|
|
5247
|
+
pie.append(svg);
|
|
5248
|
+
if (this.el.classList.contains("values")) {
|
|
5249
|
+
const labels = document.createElement("div");
|
|
5250
|
+
labels.className = "pie-chart-values";
|
|
5251
|
+
this._turns.forEach(([start, end], i) => {
|
|
5252
|
+
if (end - start < LABEL_SHARE) return;
|
|
5253
|
+
const label = document.createElement("span");
|
|
5254
|
+
label.dataset.series = this._colors[i];
|
|
5255
|
+
label.style.setProperty("--turn", String((start + end) / 2));
|
|
5256
|
+
label.textContent = this._share(i);
|
|
5257
|
+
labels.append(label);
|
|
5258
|
+
});
|
|
5259
|
+
pie.append(labels);
|
|
5260
|
+
}
|
|
5261
|
+
const middle = this._total("pie-chart-total");
|
|
5262
|
+
if (middle) pie.append(middle);
|
|
5263
|
+
return pie;
|
|
5264
|
+
}
|
|
5265
|
+
};
|
|
5266
|
+
|
|
5267
|
+
// src/ts/components/heatmapChart.ts
|
|
5268
|
+
var HeatmapChart = class extends LineChart {
|
|
5269
|
+
get _type() {
|
|
5270
|
+
return "heatmap-chart";
|
|
5271
|
+
}
|
|
5272
|
+
get _key() {
|
|
5273
|
+
return "Expressive_HeatmapChart";
|
|
5274
|
+
}
|
|
5275
|
+
static init(els, options = {}) {
|
|
5276
|
+
return super.init(els, options);
|
|
5277
|
+
}
|
|
5278
|
+
static getInstance(el) {
|
|
5279
|
+
return el["Expressive_HeatmapChart"];
|
|
5280
|
+
}
|
|
5281
|
+
/** Highlights one cell, numbered row by row. Pass -1 to hide the tooltip. */
|
|
5282
|
+
show(index) {
|
|
5283
|
+
if (!this._tooltip || index === this.activeIndex) return;
|
|
5284
|
+
this._cells[this.activeIndex]?.classList.remove("active");
|
|
5285
|
+
this.activeIndex = index;
|
|
5286
|
+
const cell = this._cells[index];
|
|
5287
|
+
this._tooltip.hidden = !cell;
|
|
5288
|
+
if (!cell) return;
|
|
5289
|
+
cell.classList.add("active");
|
|
5290
|
+
const columns = this.series.length;
|
|
5291
|
+
const [row, series] = [Math.floor(index / columns), this.series[index % columns]];
|
|
5292
|
+
const title = document.createElement("strong");
|
|
5293
|
+
title.textContent = this.labels[row];
|
|
5294
|
+
const [name, text] = [document.createElement("span"), document.createElement("span")];
|
|
5295
|
+
name.textContent = series.name;
|
|
5296
|
+
if (this._levels[index] === null) name.className = "gap";
|
|
5297
|
+
else name.style.setProperty("--level", String(this._levels[index]));
|
|
5298
|
+
text.textContent = series.text[row];
|
|
5299
|
+
this._tooltip.replaceChildren(title, name, text);
|
|
5300
|
+
const end = cell.offsetLeft + cell.offsetWidth / 2 > this._plot.clientWidth / 2;
|
|
5301
|
+
this._tooltip.classList.toggle("end", end);
|
|
5302
|
+
this._tooltip.style.left = `${cell.offsetLeft + (end ? 0 : cell.offsetWidth)}px`;
|
|
5303
|
+
this._tooltip.style.top = `${cell.offsetTop}px`;
|
|
5304
|
+
}
|
|
5305
|
+
/** Arrows move the way they point, so left and right swap in RTL. */
|
|
5306
|
+
_keyTarget(key) {
|
|
5307
|
+
if (key === "Escape") return this.activeIndex < 0 ? void 0 : -1;
|
|
5308
|
+
const [columns, last] = [this.series.length, this.labels.length - 1];
|
|
5309
|
+
const index = Math.max(0, this.activeIndex);
|
|
5310
|
+
const [row, column] = [Math.floor(index / columns), index % columns];
|
|
5311
|
+
const [next, back] = this._reversed ? ["ArrowLeft", "ArrowRight"] : ["ArrowRight", "ArrowLeft"];
|
|
5312
|
+
const to = {
|
|
5313
|
+
[next]: [row, column + 1],
|
|
5314
|
+
[back]: [row, column - 1],
|
|
5315
|
+
ArrowDown: [row + 1, column],
|
|
5316
|
+
ArrowUp: [row - 1, column],
|
|
5317
|
+
Home: [row, 0],
|
|
5318
|
+
End: [row, columns - 1]
|
|
5319
|
+
}[key];
|
|
5320
|
+
if (!to) return void 0;
|
|
5321
|
+
if (this.activeIndex < 0) return 0;
|
|
5322
|
+
const clamp = (value, max) => Math.min(max, Math.max(0, value));
|
|
5323
|
+
return clamp(to[0], last) * columns + clamp(to[1], columns - 1);
|
|
5324
|
+
}
|
|
5325
|
+
/**
|
|
5326
|
+
* The cell under the pointer by its position, or -1 off the grid. A touch
|
|
5327
|
+
* keeps its first target while it drags, so the target cannot be used. The
|
|
5328
|
+
* cells and gaps are even, so each band's edge falls in a gap and a gap
|
|
5329
|
+
* counts as the nearer cell.
|
|
5330
|
+
*/
|
|
5331
|
+
_indexAt(e) {
|
|
5332
|
+
const [columns, rows] = [this.series.length, this.labels.length];
|
|
5333
|
+
const first = this._cells[0].getBoundingClientRect();
|
|
5334
|
+
const last = this._cells[this._cells.length - 1].getBoundingClientRect();
|
|
5335
|
+
const [left, right] = [Math.min(first.left, last.left), Math.max(first.right, last.right)];
|
|
5336
|
+
const [x, y] = [e.clientX, e.clientY];
|
|
5337
|
+
if (x < left || x > right || y < first.top || y > last.bottom) return -1;
|
|
5338
|
+
const band = (offset, size, count) => Math.min(count - 1, Math.floor(offset / (size || 1) * count));
|
|
5339
|
+
const column = band(x - left, right - left, columns);
|
|
5340
|
+
return band(y - first.top, last.bottom - first.top, rows) * columns + (this._reversed ? columns - 1 - column : column);
|
|
5341
|
+
}
|
|
5342
|
+
/**
|
|
5343
|
+
* The legend: a gradient from the lowest cell's shade to the highest's,
|
|
5344
|
+
* labelled with their text. A fixed scale wider than the data shows only
|
|
5345
|
+
* the part the data uses.
|
|
5346
|
+
*/
|
|
5347
|
+
_guides() {
|
|
5348
|
+
const legend = document.createElement("div");
|
|
5349
|
+
legend.className = "heatmap-chart-legend";
|
|
5350
|
+
legend.setAttribute("aria-hidden", "true");
|
|
5351
|
+
const [low, scale, high] = ["span", "span", "span"].map((tag) => document.createElement(tag));
|
|
5352
|
+
[low.textContent, high.textContent] = this._ends;
|
|
5353
|
+
scale.className = "heatmap-chart-scale";
|
|
5354
|
+
if (this._reversed) scale.style.setProperty("--heatmap-chart-to", "left");
|
|
5355
|
+
scale.style.setProperty("--low", String(this._endLevels[0]));
|
|
5356
|
+
scale.style.setProperty("--high", String(this._endLevels[1]));
|
|
5357
|
+
legend.append(low, scale, high);
|
|
5358
|
+
return [legend];
|
|
5359
|
+
}
|
|
5360
|
+
/**
|
|
5361
|
+
* A grid of the row headers and cells, row by row, with the column headers
|
|
5362
|
+
* below. Each cell's --level shades it in CSS.
|
|
5363
|
+
*/
|
|
5364
|
+
_draw() {
|
|
5365
|
+
const columns = this.series.length;
|
|
5366
|
+
let [low, high] = [Infinity, -Infinity];
|
|
5367
|
+
this._ends = ["", ""];
|
|
5368
|
+
this.series.forEach((series) => series.values.forEach((value, row) => {
|
|
5369
|
+
if (value === null) return;
|
|
5370
|
+
if (value < low) [low, this._ends[0]] = [value, series.text[row]];
|
|
5371
|
+
if (value > high) [high, this._ends[1]] = [value, series.text[row]];
|
|
5372
|
+
}));
|
|
5373
|
+
const option = (name) => {
|
|
5374
|
+
const value = parseFloat(this.el.dataset[name] ?? "");
|
|
5375
|
+
return Number.isFinite(value) ? value : this.options[name];
|
|
5376
|
+
};
|
|
5377
|
+
const [min, max] = [option("min") ?? low, option("max") ?? high];
|
|
5378
|
+
const shade = (value) => Math.min(1, Math.max(0, (value - min) / (max - min || 1)));
|
|
5379
|
+
this._endLevels = [shade(low), shade(high)];
|
|
5380
|
+
const grid = document.createElement("div");
|
|
5381
|
+
grid.className = "heatmap-chart-grid";
|
|
5382
|
+
grid.setAttribute("aria-hidden", "true");
|
|
5383
|
+
grid.style.setProperty("--columns", String(columns));
|
|
5384
|
+
grid.style.setProperty("--rows", String(this.labels.length));
|
|
5385
|
+
const span = (className, text = "") => {
|
|
5386
|
+
const element = document.createElement("span");
|
|
5387
|
+
element.className = className;
|
|
5388
|
+
element.textContent = text;
|
|
5389
|
+
return element;
|
|
5390
|
+
};
|
|
5391
|
+
const label = (cell) => cell?.dataset.label ?? cell?.textContent.trim() ?? "";
|
|
5392
|
+
const values = this.el.classList.contains("values");
|
|
5393
|
+
this._cells = [];
|
|
5394
|
+
this._levels = [];
|
|
5395
|
+
this.labels.forEach((_, row) => {
|
|
5396
|
+
grid.append(span("heatmap-chart-row", label(this._rows[row].cells[0])));
|
|
5397
|
+
this.series.forEach((series) => {
|
|
5398
|
+
const value = series.values[row];
|
|
5399
|
+
const level = value === null ? null : shade(value);
|
|
5400
|
+
const cell = span("heatmap-chart-cell", values ? series.text[row] : "");
|
|
5401
|
+
if (level === null) cell.classList.add("gap");
|
|
5402
|
+
else cell.style.setProperty("--level", String(level));
|
|
5403
|
+
this._cells.push(cell);
|
|
5404
|
+
this._levels.push(level);
|
|
5405
|
+
grid.append(cell);
|
|
5406
|
+
});
|
|
5407
|
+
});
|
|
5408
|
+
const labels = this._header.slice(1, columns + 1).map(label);
|
|
5409
|
+
labels.forEach((text, column) => {
|
|
5410
|
+
if (!text) return;
|
|
5411
|
+
let end = column + 1;
|
|
5412
|
+
while (end < columns && !labels[end]) end++;
|
|
5413
|
+
const element = span("heatmap-chart-column", text);
|
|
5414
|
+
element.classList.toggle("run", end > column + 1);
|
|
5415
|
+
element.style.gridArea = `${this.labels.length + 1} / ${column + 2} / auto / ${end + 2}`;
|
|
5416
|
+
grid.append(element);
|
|
5417
|
+
});
|
|
5418
|
+
return grid;
|
|
5419
|
+
}
|
|
5420
|
+
};
|
|
5421
|
+
|
|
5422
|
+
// src/ts/components/radarChart.ts
|
|
5423
|
+
var RINGS = 4;
|
|
5424
|
+
var RadarChart = class extends LineChart {
|
|
5425
|
+
get _type() {
|
|
5426
|
+
return "radar-chart";
|
|
5427
|
+
}
|
|
5428
|
+
get _key() {
|
|
5429
|
+
return "Expressive_RadarChart";
|
|
5430
|
+
}
|
|
5431
|
+
/** The scale starts at zero, the middle, so shapes compare by size. */
|
|
5432
|
+
get _fromZero() {
|
|
5433
|
+
return true;
|
|
5434
|
+
}
|
|
5435
|
+
get _circular() {
|
|
5436
|
+
return true;
|
|
5437
|
+
}
|
|
5438
|
+
static init(els, options = {}) {
|
|
5439
|
+
return super.init(els, options);
|
|
5440
|
+
}
|
|
5441
|
+
static getInstance(el) {
|
|
5442
|
+
return el["Expressive_RadarChart"];
|
|
5443
|
+
}
|
|
5444
|
+
/**
|
|
5445
|
+
* The cursor is a spoke turned by --turn, and the tooltip sits past its
|
|
5446
|
+
* tip. LineChart's points on the spoke keep their top, a share of the
|
|
5447
|
+
* radius, which stops at the rim and the middle like the shapes.
|
|
5448
|
+
*/
|
|
5449
|
+
_place(index) {
|
|
5450
|
+
for (const dot of this._cursor.children) {
|
|
5451
|
+
dot.style.top = `${Math.min(100, Math.max(0, parseFloat(dot.style.top)))}%`;
|
|
5452
|
+
}
|
|
5453
|
+
const turn = this._turn(index);
|
|
5454
|
+
this._cursor.style.setProperty("--turn", String(turn));
|
|
5455
|
+
this._tooltip.style.setProperty("--turn", String(turn));
|
|
5456
|
+
this._tooltip.classList.toggle("end", turn > 0 && turn < 0.5);
|
|
5457
|
+
}
|
|
5458
|
+
/** A row's spoke as a fraction of a turn clockwise from the top. */
|
|
5459
|
+
_turn(index) {
|
|
5460
|
+
return index / this.labels.length;
|
|
5461
|
+
}
|
|
5462
|
+
/** Only the legend; the labels go round the rim in _draw. */
|
|
5463
|
+
_guides() {
|
|
5464
|
+
return this._legend();
|
|
5465
|
+
}
|
|
5466
|
+
/** The nearest spoke to the pointer, by its angle from the middle. */
|
|
5467
|
+
_indexAt(e) {
|
|
5468
|
+
const [turn] = this._polarAt(e);
|
|
5469
|
+
return Math.round(turn * this.labels.length) % this.labels.length;
|
|
5470
|
+
}
|
|
5471
|
+
/**
|
|
5472
|
+
* An SVG in a -50 to 50 box: the rings and spokes of the grid, then each
|
|
5473
|
+
* series' shape, with its points for .points. The rim is the box's edge;
|
|
5474
|
+
* the labels round it are HTML.
|
|
5475
|
+
*/
|
|
5476
|
+
_draw() {
|
|
5477
|
+
const at = (index, share2) => {
|
|
5478
|
+
const angle = this._turn(index) * 2 * Math.PI;
|
|
5479
|
+
return `${(50 * share2 * Math.sin(angle)).toFixed(3)},${(-50 * share2 * Math.cos(angle)).toFixed(3)}`;
|
|
5480
|
+
};
|
|
5481
|
+
const share = (value) => Math.min(1, Math.max(0, 1 - this._y(value) / 100));
|
|
5482
|
+
const rows = this.labels.map((_, i) => i);
|
|
5483
|
+
const ring = (r) => `M${rows.map((i) => at(i, r)).join("L")}Z`;
|
|
5484
|
+
const svg = svgNode("svg", { viewBox: "-50 -50 100 100", "aria-hidden": "true", focusable: "false" });
|
|
5485
|
+
svg.append(svgNode("path", {
|
|
5486
|
+
class: "radar-chart-grid",
|
|
5487
|
+
d: Array.from({ length: RINGS }, (_, r) => ring((r + 1) / RINGS)).join("") + rows.map((i) => `M0,0L${at(i, 1)}`).join("")
|
|
5488
|
+
}));
|
|
5489
|
+
const points = this.el.classList.contains("points");
|
|
5490
|
+
this.series.forEach((series, s) => {
|
|
5491
|
+
const group = svgNode("g", {
|
|
5492
|
+
class: `radar-chart-series ${series.className}`.trim(),
|
|
5493
|
+
"data-series": String(s % COLORS + 1)
|
|
5494
|
+
});
|
|
5495
|
+
const drawn = rows.filter((i) => series.values[i] !== null);
|
|
5496
|
+
const corners = drawn.map((i) => at(i, share(series.values[i])));
|
|
5497
|
+
group.append(svgNode("path", { class: "radar-chart-shape", d: corners.length ? `M${corners.join("L")}Z` : "" }));
|
|
5498
|
+
if (points) group.append(svgNode("path", { class: "radar-chart-points", d: corners.map((c) => `M${c}h0`).join("") }));
|
|
5499
|
+
svg.append(group);
|
|
5500
|
+
});
|
|
5501
|
+
const labels = document.createElement("ol");
|
|
5502
|
+
labels.className = "radar-chart-axes";
|
|
5503
|
+
labels.setAttribute("aria-hidden", "true");
|
|
5504
|
+
this.labels.forEach((text, i) => {
|
|
5505
|
+
const li = document.createElement("li");
|
|
5506
|
+
li.textContent = text;
|
|
5507
|
+
li.style.setProperty("--turn", String(this._turn(i)));
|
|
5508
|
+
labels.append(li);
|
|
5509
|
+
});
|
|
5510
|
+
const web = document.createElement("div");
|
|
5511
|
+
web.className = "radar-chart-web";
|
|
5512
|
+
web.append(svg, labels);
|
|
5513
|
+
return web;
|
|
5514
|
+
}
|
|
5515
|
+
};
|
|
3793
5516
|
|
|
3794
|
-
// src/ts/components/
|
|
3795
|
-
var
|
|
3796
|
-
var
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
const maxLength = parseInt(this.el.getAttribute("maxlength")), actualLength = this.el.value.length;
|
|
3801
|
-
this.isValidLength = actualLength <= maxLength;
|
|
3802
|
-
let counterString = actualLength.toString();
|
|
3803
|
-
if (maxLength) {
|
|
3804
|
-
counterString += "/" + maxLength;
|
|
3805
|
-
this._validateInput();
|
|
3806
|
-
}
|
|
3807
|
-
this.counterEl.textContent = counterString;
|
|
3808
|
-
};
|
|
3809
|
-
this.el["Expressive_CharacterCounter"] = this;
|
|
3810
|
-
this.options = {
|
|
3811
|
-
..._CharacterCounter.defaults,
|
|
3812
|
-
...options
|
|
3813
|
-
};
|
|
3814
|
-
this.isInvalid = false;
|
|
3815
|
-
this.isValidLength = false;
|
|
3816
|
-
this._setupCounter();
|
|
3817
|
-
this._setupEventHandlers();
|
|
5517
|
+
// src/ts/components/radialChart.ts
|
|
5518
|
+
var RADIUS2 = 46;
|
|
5519
|
+
var FILL = 0.8;
|
|
5520
|
+
var RadialChart = class extends LineChart {
|
|
5521
|
+
get _type() {
|
|
5522
|
+
return "radial-chart";
|
|
3818
5523
|
}
|
|
3819
|
-
|
|
3820
|
-
return
|
|
5524
|
+
get _key() {
|
|
5525
|
+
return "Expressive_RadialChart";
|
|
5526
|
+
}
|
|
5527
|
+
get _points() {
|
|
5528
|
+
return false;
|
|
5529
|
+
}
|
|
5530
|
+
get _circular() {
|
|
5531
|
+
return true;
|
|
5532
|
+
}
|
|
5533
|
+
get _gauge() {
|
|
5534
|
+
return this.el.classList.contains("gauge");
|
|
3821
5535
|
}
|
|
3822
|
-
/**
|
|
3823
|
-
* Initializes instances of CharacterCounter.
|
|
3824
|
-
* @param els HTML elements.
|
|
3825
|
-
* @param options Component options.
|
|
3826
|
-
*/
|
|
3827
5536
|
static init(els, options = {}) {
|
|
3828
|
-
return super.init(els, options
|
|
5537
|
+
return super.init(els, options);
|
|
3829
5538
|
}
|
|
3830
5539
|
static getInstance(el) {
|
|
3831
|
-
return el["
|
|
5540
|
+
return el["Expressive_RadialChart"];
|
|
5541
|
+
}
|
|
5542
|
+
show(index) {
|
|
5543
|
+
if (!this._tooltip || index === this.activeIndex) return;
|
|
5544
|
+
super.show(index);
|
|
5545
|
+
this._rings.forEach((ring, i) => ring.classList.toggle("active", i === index));
|
|
5546
|
+
if (this._tooltip.hidden) return;
|
|
5547
|
+
const color = this._color(index);
|
|
5548
|
+
this._tooltip.querySelectorAll("[data-series]").forEach((span) => {
|
|
5549
|
+
span.dataset.series = color;
|
|
5550
|
+
});
|
|
3832
5551
|
}
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
this.
|
|
3836
|
-
this.
|
|
5552
|
+
/** The tooltip sits where the ring's fill ends. */
|
|
5553
|
+
_place(index) {
|
|
5554
|
+
const turn = this._turn(this._fractions[index]);
|
|
5555
|
+
const r = this._radii[index];
|
|
5556
|
+
const [x, y] = [r * Math.sin(turn * 2 * Math.PI), -r * Math.cos(turn * 2 * Math.PI)];
|
|
5557
|
+
this._tooltip.style.left = `${50 + x}%`;
|
|
5558
|
+
this._tooltip.style.top = `${(50 + y) / (this._gauge ? 50 : 100) * 100}%`;
|
|
5559
|
+
this._tooltip.classList.toggle("end", x > 0);
|
|
5560
|
+
}
|
|
5561
|
+
_color(index) {
|
|
5562
|
+
return String(index % COLORS + 1);
|
|
5563
|
+
}
|
|
5564
|
+
/** A fraction of the ring as a turn clockwise from the top. */
|
|
5565
|
+
_turn(fraction) {
|
|
5566
|
+
return this._gauge ? fraction / 2 - 0.25 : fraction;
|
|
5567
|
+
}
|
|
5568
|
+
/** For more than one row, a legend of every row with its color and cell. */
|
|
5569
|
+
_guides() {
|
|
5570
|
+
if (this.labels.length < 2) return [];
|
|
5571
|
+
const legend = document.createElement("ul");
|
|
5572
|
+
legend.className = "radial-chart-legend";
|
|
5573
|
+
legend.setAttribute("aria-hidden", "true");
|
|
5574
|
+
const text = this.series[0].text;
|
|
5575
|
+
legend.append(...this.labels.map((label, i) => {
|
|
5576
|
+
const li = document.createElement("li");
|
|
5577
|
+
li.dataset.series = this._color(i);
|
|
5578
|
+
const [name, value] = [label, text[i]].map((content) => {
|
|
5579
|
+
const span = document.createElement("span");
|
|
5580
|
+
span.textContent = content;
|
|
5581
|
+
return span;
|
|
5582
|
+
});
|
|
5583
|
+
li.append(name, value);
|
|
5584
|
+
return li;
|
|
5585
|
+
}));
|
|
5586
|
+
return [legend];
|
|
5587
|
+
}
|
|
5588
|
+
/** The ring under the pointer by its distance from the middle, or -1. */
|
|
5589
|
+
_indexAt(e) {
|
|
5590
|
+
const box = this._plot.getBoundingClientRect();
|
|
5591
|
+
const [x, y] = [e.clientX - box.left - box.width / 2, e.clientY - box.top - box.width / 2];
|
|
5592
|
+
if (this._gauge && y > 0) return -1;
|
|
5593
|
+
const distance = Math.hypot(x, y) / box.width * 100;
|
|
5594
|
+
const step = this._radii.length > 1 ? this._radii[0] - this._radii[1] : this._width;
|
|
5595
|
+
return this._radii.findIndex((r) => Math.abs(distance - r) <= step / 2);
|
|
3837
5596
|
}
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
5597
|
+
/**
|
|
5598
|
+
* An SVG of rings in a -50 to 50 box, the top half for a gauge. Each row
|
|
5599
|
+
* is a track circle and a fill circle whose dash is its share; CSS draws
|
|
5600
|
+
* them from --radial-chart-dash. The total is HTML over it.
|
|
5601
|
+
*/
|
|
5602
|
+
_draw() {
|
|
5603
|
+
const style = getComputedStyle(this.el);
|
|
5604
|
+
const token = (name, fallback) => {
|
|
5605
|
+
const value = parseFloat(style.getPropertyValue(`--md-comp-radial-chart-${name}`));
|
|
5606
|
+
return Number.isFinite(value) ? value : fallback;
|
|
5607
|
+
};
|
|
5608
|
+
const count = this.labels.length;
|
|
5609
|
+
let [width, gap] = [Math.max(0, token("thickness", 8)), Math.max(0, token("gap", 2))];
|
|
5610
|
+
const scale = Math.min(1, RADIUS2 * FILL / (count * width + (count - 1) * gap || 1));
|
|
5611
|
+
[width, gap] = [width * scale, gap * scale];
|
|
5612
|
+
this._width = width;
|
|
5613
|
+
this._radii = this.labels.map((_, i) => RADIUS2 - width / 2 - i * (width + gap));
|
|
5614
|
+
const max = [parseFloat(this.el.dataset.max ?? ""), this.options.max ?? NaN, 100].find((value) => value > 0);
|
|
5615
|
+
this._fractions = this.series[0].values.map((value) => Math.min(1, Math.max(0, (value ?? 0) / max)));
|
|
5616
|
+
const gauge = this._gauge;
|
|
5617
|
+
const svg = svgNode("svg", { viewBox: gauge ? "-50 -50 100 50" : "-50 -50 100 100", focusable: "false" });
|
|
5618
|
+
const transform = gauge ? "rotate(180)" : "rotate(-90)";
|
|
5619
|
+
const span = gauge ? 0.5 : 1;
|
|
5620
|
+
this._rings = this._radii.map((r, i) => {
|
|
5621
|
+
const length = 2 * Math.PI * r;
|
|
5622
|
+
const circle = (name, fraction, capped) => {
|
|
5623
|
+
const ring2 = svgNode("circle", { class: name, r: r.toFixed(3), transform, "stroke-width": width.toFixed(3) });
|
|
5624
|
+
const dash = Math.max(1e-3, fraction * span * length - (capped ? width : 0));
|
|
5625
|
+
ring2.style.setProperty("--radial-chart-dash", `${dash.toFixed(3)} ${length.toFixed(3)}`);
|
|
5626
|
+
if (capped) ring2.style.setProperty("--radial-chart-offset", (-width / 2).toFixed(3));
|
|
5627
|
+
return ring2;
|
|
5628
|
+
};
|
|
5629
|
+
const ring = circle("radial-chart-ring", this._fractions[i], true);
|
|
5630
|
+
ring.dataset.series = this._color(i);
|
|
5631
|
+
if (!this._fractions[i]) ring.classList.add("empty");
|
|
5632
|
+
svg.append(circle("radial-chart-track", 1, gauge), ring);
|
|
5633
|
+
return ring;
|
|
5634
|
+
});
|
|
5635
|
+
const rings = document.createElement("div");
|
|
5636
|
+
rings.className = "radial-chart-rings";
|
|
5637
|
+
rings.setAttribute("aria-hidden", "true");
|
|
5638
|
+
rings.style.setProperty("--radial-chart-middle", `${(this._radii[count - 1] - this._width / 2) * 2}%`);
|
|
5639
|
+
rings.append(svg);
|
|
5640
|
+
const total = this._total("radial-chart-total");
|
|
5641
|
+
if (total) rings.append(total);
|
|
5642
|
+
return rings;
|
|
3841
5643
|
}
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
5644
|
+
};
|
|
5645
|
+
|
|
5646
|
+
// src/ts/components/mixedChart.ts
|
|
5647
|
+
var has = (series, name) => series.className.split(/\s+/).includes(name);
|
|
5648
|
+
var MixedChart = class extends ColumnChart {
|
|
5649
|
+
get _type() {
|
|
5650
|
+
return "mixed-chart";
|
|
3845
5651
|
}
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
this.counterEl.classList.add("character-counter");
|
|
3849
|
-
this.counterEl.setAttribute("aria-live", "polite");
|
|
3850
|
-
this.counterEl.setAttribute("aria-atomic", "true");
|
|
3851
|
-
this.counterEl.style.float = "right";
|
|
3852
|
-
this.counterEl.style.fontSize = "12px";
|
|
3853
|
-
this.counterEl.style.height = "1";
|
|
3854
|
-
this.el.parentElement.appendChild(this.counterEl);
|
|
5652
|
+
get _key() {
|
|
5653
|
+
return "Expressive_MixedChart";
|
|
3855
5654
|
}
|
|
3856
|
-
|
|
3857
|
-
|
|
5655
|
+
get _points() {
|
|
5656
|
+
return true;
|
|
3858
5657
|
}
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
5658
|
+
static init(els, options = {}) {
|
|
5659
|
+
return super.init(els, options);
|
|
5660
|
+
}
|
|
5661
|
+
static getInstance(el) {
|
|
5662
|
+
return el["Expressive_MixedChart"];
|
|
5663
|
+
}
|
|
5664
|
+
/** The start scale, then the end scale; each includes zero if it has columns. */
|
|
5665
|
+
_scale(series, min, max) {
|
|
5666
|
+
const end = series.filter((each) => has(each, "end"));
|
|
5667
|
+
const start = series.filter((each) => !end.includes(each));
|
|
5668
|
+
const columns = (list) => list.some((each) => has(each, "column"));
|
|
5669
|
+
super._scale(end, this._number("endMin"), this._number("endMax"), columns(end));
|
|
5670
|
+
return super._scale(start, min, max, columns(start));
|
|
5671
|
+
}
|
|
5672
|
+
/** Columns behind, then the lines and areas over them. */
|
|
5673
|
+
_draw() {
|
|
5674
|
+
const marks = document.createElement("div");
|
|
5675
|
+
marks.className = "mixed-chart-marks";
|
|
5676
|
+
const columns = super._draw(this.series.filter((series) => has(series, "column")));
|
|
5677
|
+
columns.className = "mixed-chart-columns";
|
|
5678
|
+
marks.append(columns, LineChart.prototype["_draw"].call(this, this.series.filter((series) => !has(series, "column"))));
|
|
5679
|
+
return marks;
|
|
3868
5680
|
}
|
|
3869
5681
|
};
|
|
3870
5682
|
|
|
3871
5683
|
// src/ts/components/select.ts
|
|
3872
|
-
var
|
|
5684
|
+
var _defaults15 = {
|
|
3873
5685
|
classes: "",
|
|
3874
5686
|
menuOptions: {}
|
|
3875
5687
|
};
|
|
@@ -3951,7 +5763,7 @@ var FormSelect = class _FormSelect extends Component {
|
|
|
3951
5763
|
this._setupEventHandlers();
|
|
3952
5764
|
}
|
|
3953
5765
|
static get defaults() {
|
|
3954
|
-
return
|
|
5766
|
+
return _defaults15;
|
|
3955
5767
|
}
|
|
3956
5768
|
/**
|
|
3957
5769
|
* Initializes instances of FormSelect.
|
|
@@ -4335,8 +6147,7 @@ var FormSelect = class _FormSelect extends Component {
|
|
|
4335
6147
|
this._values.forEach((value) => this._deselectValue(value));
|
|
4336
6148
|
}
|
|
4337
6149
|
_isValueSelected(value) {
|
|
4338
|
-
|
|
4339
|
-
return realValues.some((realValue) => realValue === value.el.value);
|
|
6150
|
+
return value.el.selected;
|
|
4340
6151
|
}
|
|
4341
6152
|
_toggleEntryFromArray(value) {
|
|
4342
6153
|
if (this._isValueSelected(value)) this._deselectValue(value);
|
|
@@ -4377,7 +6188,7 @@ var FormSelect = class _FormSelect extends Component {
|
|
|
4377
6188
|
};
|
|
4378
6189
|
|
|
4379
6190
|
// src/ts/plugins/dockedDisplayPlugin.ts
|
|
4380
|
-
var
|
|
6191
|
+
var _defaults16 = {
|
|
4381
6192
|
margin: 5,
|
|
4382
6193
|
transition: 10,
|
|
4383
6194
|
duration: 250,
|
|
@@ -4426,7 +6237,7 @@ var DockedDisplayPlugin = class _DockedDisplayPlugin {
|
|
|
4426
6237
|
};
|
|
4427
6238
|
this.el = el;
|
|
4428
6239
|
this.options = {
|
|
4429
|
-
...
|
|
6240
|
+
..._defaults16,
|
|
4430
6241
|
...options
|
|
4431
6242
|
};
|
|
4432
6243
|
this.container = document.createElement("div");
|
|
@@ -4447,7 +6258,7 @@ var DockedDisplayPlugin = class _DockedDisplayPlugin {
|
|
|
4447
6258
|
};
|
|
4448
6259
|
|
|
4449
6260
|
// src/ts/components/datepicker.ts
|
|
4450
|
-
var
|
|
6261
|
+
var _defaults17 = {
|
|
4451
6262
|
// the default output format for the input field value
|
|
4452
6263
|
format: "mmm dd, yyyy",
|
|
4453
6264
|
// Used to create date object from current input string
|
|
@@ -4561,9 +6372,7 @@ var _Datepicker = class _Datepicker extends Component {
|
|
|
4561
6372
|
this._randId = null;
|
|
4562
6373
|
this._focusedDate = null;
|
|
4563
6374
|
this._handleInputClick = (e) => {
|
|
4564
|
-
if (e.type == "date")
|
|
4565
|
-
e.preventDefault();
|
|
4566
|
-
}
|
|
6375
|
+
if (e.target.type == "date" && (this.options.openByDefault || this.options.container)) e.preventDefault();
|
|
4567
6376
|
this._batchDraws(() => {
|
|
4568
6377
|
this.setDateFromInput(e.target);
|
|
4569
6378
|
this.draw();
|
|
@@ -4673,26 +6482,8 @@ var _Datepicker = class _Datepicker extends Component {
|
|
|
4673
6482
|
this.gotoYear(e.target.value);
|
|
4674
6483
|
};
|
|
4675
6484
|
this._handleInputChange = (e) => {
|
|
4676
|
-
let date;
|
|
4677
|
-
const el = e.target;
|
|
4678
6485
|
if (e["detail"]?.firedBy === this) return;
|
|
4679
|
-
|
|
4680
|
-
if (this.options.parse) {
|
|
4681
|
-
date = this.options.parse(
|
|
4682
|
-
e.target.value,
|
|
4683
|
-
typeof this.options.format === "function" ? this.options.format(new Date(this.el.value)) : this.options.format
|
|
4684
|
-
);
|
|
4685
|
-
} else {
|
|
4686
|
-
date = _Datepicker._parseDate(e.target.value);
|
|
4687
|
-
}
|
|
4688
|
-
if (_Datepicker._isDate(date)) {
|
|
4689
|
-
if (el == this.endDateEl && date < this.date) return this.setInputValue(el, this.endDate);
|
|
4690
|
-
this.setDate(date, false, el == this.endDateEl, true);
|
|
4691
|
-
if (e.type == "date") {
|
|
4692
|
-
this.setDataDate(e, date);
|
|
4693
|
-
this.setInputValues();
|
|
4694
|
-
}
|
|
4695
|
-
}
|
|
6486
|
+
this.setDateFromInput(e.target);
|
|
4696
6487
|
};
|
|
4697
6488
|
this._finishSelection = () => {
|
|
4698
6489
|
this.setInputValues();
|
|
@@ -4724,7 +6515,7 @@ var _Datepicker = class _Datepicker extends Component {
|
|
|
4724
6515
|
this._insertHTMLIntoDOM();
|
|
4725
6516
|
this._setupEventHandlers();
|
|
4726
6517
|
if (!this.options.defaultDate) {
|
|
4727
|
-
this.options.defaultDate =
|
|
6518
|
+
this.options.defaultDate = this._parseInput(this.el.value);
|
|
4728
6519
|
}
|
|
4729
6520
|
const defDate = this.options.defaultDate;
|
|
4730
6521
|
if (_Datepicker._isDate(defDate)) {
|
|
@@ -4752,7 +6543,7 @@ var _Datepicker = class _Datepicker extends Component {
|
|
|
4752
6543
|
}
|
|
4753
6544
|
}
|
|
4754
6545
|
static get defaults() {
|
|
4755
|
-
return
|
|
6546
|
+
return _defaults17;
|
|
4756
6547
|
}
|
|
4757
6548
|
/**
|
|
4758
6549
|
* Initializes instances of Datepicker.
|
|
@@ -4783,6 +6574,13 @@ var _Datepicker = class _Datepicker extends Component {
|
|
|
4783
6574
|
const date = /* @__PURE__ */ new Date(value + "T00:00:00");
|
|
4784
6575
|
return date.getDate() === Number(value.slice(-2)) ? date : /* @__PURE__ */ new Date(NaN);
|
|
4785
6576
|
}
|
|
6577
|
+
_parseInput(value) {
|
|
6578
|
+
if (!value.trim() || !this.options.parse) return _Datepicker._parseDate(value);
|
|
6579
|
+
return this.options.parse(
|
|
6580
|
+
value,
|
|
6581
|
+
typeof this.options.format === "string" ? this.options.format : ""
|
|
6582
|
+
);
|
|
6583
|
+
}
|
|
4786
6584
|
static _isWeekend(date) {
|
|
4787
6585
|
const day = date.getDay();
|
|
4788
6586
|
return day === 0 || day === 6;
|
|
@@ -4892,8 +6690,22 @@ var _Datepicker = class _Datepicker extends Component {
|
|
|
4892
6690
|
* Sets date from input field.
|
|
4893
6691
|
*/
|
|
4894
6692
|
setDateFromInput(el) {
|
|
4895
|
-
|
|
4896
|
-
this.
|
|
6693
|
+
if (el.type === "date") this.setDataDate(el, _Datepicker._parseDate(el.value));
|
|
6694
|
+
if ((this.options.isDateRange || !this.options.isMultipleSelection) && !el.value.trim()) {
|
|
6695
|
+
this.setSingleDate(null, el === this.endDateEl);
|
|
6696
|
+
this.draw();
|
|
6697
|
+
return;
|
|
6698
|
+
}
|
|
6699
|
+
const parsedDate = this._parseInput(el.value);
|
|
6700
|
+
if (!_Datepicker._isDate(parsedDate)) return;
|
|
6701
|
+
const date = new Date(parsedDate.getTime());
|
|
6702
|
+
_Datepicker._setToStartOfDay(date);
|
|
6703
|
+
const isEndDate = el === this.endDateEl;
|
|
6704
|
+
if (this.options.isDateRange && (isEndDate ? this.date && date < this.date : this.endDate && date > this.endDate)) {
|
|
6705
|
+
this.setInputValue(el, isEndDate ? this.endDate : this.date);
|
|
6706
|
+
return;
|
|
6707
|
+
}
|
|
6708
|
+
this.setDate(date, false, isEndDate, true);
|
|
4897
6709
|
}
|
|
4898
6710
|
/**
|
|
4899
6711
|
* Set a date on the datepicker.
|
|
@@ -4965,7 +6777,7 @@ var _Datepicker = class _Datepicker extends Component {
|
|
|
4965
6777
|
* Sets the data-date attribute on the date input field
|
|
4966
6778
|
*/
|
|
4967
6779
|
setDataDate(el, date) {
|
|
4968
|
-
el.setAttribute("data-date", this.toString(date));
|
|
6780
|
+
el.setAttribute("data-date", _Datepicker._isDate(date) ? this.toString(date) : "");
|
|
4969
6781
|
}
|
|
4970
6782
|
/**
|
|
4971
6783
|
* Sets dates on the input values.
|
|
@@ -4991,16 +6803,15 @@ var _Datepicker = class _Datepicker extends Component {
|
|
|
4991
6803
|
*/
|
|
4992
6804
|
setInputValue(el, date) {
|
|
4993
6805
|
if (!el) return;
|
|
6806
|
+
if (el.type == "date") this.setDataDate(el, date);
|
|
4994
6807
|
if (!_Datepicker._isDate(date)) {
|
|
4995
6808
|
el.value = "";
|
|
4996
|
-
if (el.type === "date") el.setAttribute("data-date", "");
|
|
4997
6809
|
} else if (el.type == "date") {
|
|
4998
|
-
this.setDataDate(el, date);
|
|
4999
6810
|
el.value = this.formatDate(date, "yyyy-mm-dd");
|
|
5000
6811
|
} else {
|
|
5001
6812
|
el.value = this.toString(date);
|
|
5002
6813
|
}
|
|
5003
|
-
|
|
6814
|
+
el.dispatchEvent(
|
|
5004
6815
|
new CustomEvent("change", {
|
|
5005
6816
|
bubbles: true,
|
|
5006
6817
|
cancelable: true,
|
|
@@ -5091,7 +6902,7 @@ var _Datepicker = class _Datepicker extends Component {
|
|
|
5091
6902
|
cells += 7 - after;
|
|
5092
6903
|
let isWeekSelected = false;
|
|
5093
6904
|
for (let i = 0, r = 0; i < cells; i++) {
|
|
5094
|
-
const day = new Date(year, month, 1 + (i - before)), isToday = _Datepicker._compareDates(day, now), hasEvent = this.options.events.indexOf(day.toDateString()) !== -1, isEmpty = i < before || i >= days + before, isStartRange = this.options.startRange && _Datepicker._compareDates(this.options.startRange, day), isEndRange = this.options.endRange && _Datepicker._compareDates(this.options.endRange, day), isInRange = this.options.startRange && this.options.endRange && this.options.startRange < day && day < this.options.endRange, isDisabled = this.options.minDate && day < this.options.minDate || this.options.maxDate && day > this.options.maxDate || this.options.disableWeekends && _Datepicker._isWeekend(day) || this.options.disableDayFn && this.options.disableDayFn(day), isDateRangeStart = this.options.isDateRange && this.date && this.endDate && _Datepicker._compareDates(this.date, day), isDateRangeEnd = this.options.isDateRange && this.endDate && _Datepicker._compareDates(this.endDate, day), isDateRange = this.options.isDateRange && _Datepicker._isDate(this.endDate) && _Datepicker._compareWithinRange(day, this.date, this.endDate);
|
|
6905
|
+
const day = new Date(year, month, 1 + (i - before)), isToday = _Datepicker._compareDates(day, now), hasEvent = this.options.events.indexOf(day.toDateString()) !== -1, isEmpty = i < before || i >= days + before, isStartRange = this.options.startRange && _Datepicker._compareDates(this.options.startRange, day), isEndRange = this.options.endRange && _Datepicker._compareDates(this.options.endRange, day), isInRange = this.options.startRange && this.options.endRange && this.options.startRange < day && day < this.options.endRange, isDisabled = this.options.minDate && day < this.options.minDate || this.options.maxDate && day > this.options.maxDate || this.options.disableWeekends && _Datepicker._isWeekend(day) || this.options.disableDayFn && this.options.disableDayFn(day), isDateRangeStart = this.options.isDateRange && this.date && this.endDate && _Datepicker._compareDates(this.date, day), isDateRangeEnd = this.options.isDateRange && this.endDate && _Datepicker._compareDates(this.endDate, day), isDateRange = this.options.isDateRange && _Datepicker._isDate(this.date) && _Datepicker._isDate(this.endDate) && _Datepicker._compareWithinRange(day, this.date, this.endDate);
|
|
5095
6906
|
let dayNumber = 1 + (i - before), monthNumber = month, yearNumber = year;
|
|
5096
6907
|
let isSelected = false;
|
|
5097
6908
|
if (_Datepicker._isDate(this.date)) {
|
|
@@ -5526,6 +7337,7 @@ var _Datepicker = class _Datepicker extends Component {
|
|
|
5526
7337
|
createDateInput() {
|
|
5527
7338
|
const dateInput = this.el.cloneNode(true);
|
|
5528
7339
|
dateInput.removeAttribute("id");
|
|
7340
|
+
dateInput.classList.add("no-autoinit");
|
|
5529
7341
|
dateInput.addEventListener("click", this._handleInputClick);
|
|
5530
7342
|
dateInput.addEventListener("keydown", this._handleInputKeydown);
|
|
5531
7343
|
dateInput.addEventListener("change", this._handleInputChange);
|
|
@@ -5546,8 +7358,402 @@ _Datepicker._template = `
|
|
|
5546
7358
|
</div>`;
|
|
5547
7359
|
var Datepicker = _Datepicker;
|
|
5548
7360
|
|
|
7361
|
+
// src/ts/components/messageScroller.ts
|
|
7362
|
+
var _defaults18 = {
|
|
7363
|
+
autoScroll: true,
|
|
7364
|
+
defaultScrollPosition: "end",
|
|
7365
|
+
peek: 64
|
|
7366
|
+
};
|
|
7367
|
+
var EDGE = 2;
|
|
7368
|
+
var UP_KEYS = /* @__PURE__ */ new Set(["ArrowUp", "PageUp", "Home"]);
|
|
7369
|
+
var RELEASE_EVENTS = ["pointerup", "pointercancel", "touchend", "touchcancel"];
|
|
7370
|
+
var MessageScroller = class _MessageScroller extends Component {
|
|
7371
|
+
constructor(el, options) {
|
|
7372
|
+
super(el, options, _MessageScroller);
|
|
7373
|
+
this._opened = false;
|
|
7374
|
+
this._following = false;
|
|
7375
|
+
/** A press is down: it may be a tap or a scrollbar drag. */
|
|
7376
|
+
this._holding = false;
|
|
7377
|
+
this._lastTop = 0;
|
|
7378
|
+
/** Where a programmatic scroll is heading; null when none is running. */
|
|
7379
|
+
this._target = null;
|
|
7380
|
+
/** Distance left to the target at the last scroll event. */
|
|
7381
|
+
this._distance = Infinity;
|
|
7382
|
+
/** The first visible message and its offset from the viewport top. */
|
|
7383
|
+
this._ref = null;
|
|
7384
|
+
this._refOffset = 0;
|
|
7385
|
+
/** The latest anchored turn, which the spacer lets reach the top. */
|
|
7386
|
+
this._anchor = null;
|
|
7387
|
+
this._spacer = 0;
|
|
7388
|
+
this._last = null;
|
|
7389
|
+
this._resizeObserver = null;
|
|
7390
|
+
this._mutationObserver = null;
|
|
7391
|
+
// A programmatic scroll ends when it arrives, or when a scroll moves away
|
|
7392
|
+
// from its target because something else took over.
|
|
7393
|
+
this._handleScroll = () => {
|
|
7394
|
+
if (this._target !== null) {
|
|
7395
|
+
const distance = Math.abs(this.viewport.scrollTop - this._resolve(this._target));
|
|
7396
|
+
if (distance > EDGE && distance < this._distance) {
|
|
7397
|
+
this._distance = distance;
|
|
7398
|
+
return;
|
|
7399
|
+
}
|
|
7400
|
+
this._target = null;
|
|
7401
|
+
}
|
|
7402
|
+
this._settle();
|
|
7403
|
+
};
|
|
7404
|
+
this._handleUserScroll = (e) => {
|
|
7405
|
+
if (e.type === "wheel" && e.deltaY >= 0) return;
|
|
7406
|
+
if (e.type === "keydown" && !UP_KEYS.has(e.key)) return;
|
|
7407
|
+
this._target = null;
|
|
7408
|
+
if (e.type === "pointerdown" || e.type === "touchstart") this._holding = true;
|
|
7409
|
+
else this._following = false;
|
|
7410
|
+
};
|
|
7411
|
+
this._handleRelease = () => {
|
|
7412
|
+
if (!this._holding) return;
|
|
7413
|
+
this._holding = false;
|
|
7414
|
+
if (this._following) this.viewport.scrollTop = this.viewport.scrollHeight;
|
|
7415
|
+
this._sync();
|
|
7416
|
+
};
|
|
7417
|
+
this._handleButtonClick = () => this.scrollToEnd();
|
|
7418
|
+
this._handleMutations = (records) => {
|
|
7419
|
+
for (const record of records) {
|
|
7420
|
+
record.addedNodes.forEach((node) => {
|
|
7421
|
+
if (node instanceof Element) this._resizeObserver?.observe(node);
|
|
7422
|
+
});
|
|
7423
|
+
record.removedNodes.forEach((node) => {
|
|
7424
|
+
if (node instanceof Element) this._resizeObserver?.unobserve(node);
|
|
7425
|
+
});
|
|
7426
|
+
}
|
|
7427
|
+
const previous = this._last;
|
|
7428
|
+
this._last = this.viewport.lastElementChild;
|
|
7429
|
+
if (!this._opened) return;
|
|
7430
|
+
if (!this._following && this._target === null) this._restore();
|
|
7431
|
+
if (!this.options.autoScroll) return;
|
|
7432
|
+
if (previous && previous.parentElement !== this.viewport) return;
|
|
7433
|
+
for (let item = this._last; item && item !== previous; item = item.previousElementSibling) {
|
|
7434
|
+
if (item.hasAttribute("data-scroll-anchor")) {
|
|
7435
|
+
this._anchor = item;
|
|
7436
|
+
this._updateSpacer();
|
|
7437
|
+
this._following = true;
|
|
7438
|
+
this._scrollTo(this._top(item) - this.options.peek);
|
|
7439
|
+
return;
|
|
7440
|
+
}
|
|
7441
|
+
}
|
|
7442
|
+
};
|
|
7443
|
+
this._handleResize = () => {
|
|
7444
|
+
if (!this._opened) {
|
|
7445
|
+
if (this.viewport.clientHeight > 0) this._open();
|
|
7446
|
+
return;
|
|
7447
|
+
}
|
|
7448
|
+
this._updateSpacer();
|
|
7449
|
+
const v = this.viewport;
|
|
7450
|
+
if (this._target === "end") this._scrollTo("end");
|
|
7451
|
+
else if (this._target === null) {
|
|
7452
|
+
if (this._following && !this._holding) v.scrollTop = v.scrollHeight;
|
|
7453
|
+
else this._restore();
|
|
7454
|
+
}
|
|
7455
|
+
this._sync();
|
|
7456
|
+
};
|
|
7457
|
+
this.el["Expressive_MessageScroller"] = this;
|
|
7458
|
+
this.options = {
|
|
7459
|
+
..._MessageScroller.defaults,
|
|
7460
|
+
...options
|
|
7461
|
+
};
|
|
7462
|
+
this.viewport = el.querySelector(":scope > .message-scroller-viewport");
|
|
7463
|
+
this.button = el.querySelector(":scope > .message-scroller-button");
|
|
7464
|
+
if (!this.viewport) {
|
|
7465
|
+
console.error("MessageScroller needs a .message-scroller-viewport child.");
|
|
7466
|
+
return;
|
|
7467
|
+
}
|
|
7468
|
+
this._last = this.viewport.lastElementChild;
|
|
7469
|
+
if (this.viewport.clientHeight > 0) this._open();
|
|
7470
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
7471
|
+
this._resizeObserver = new ResizeObserver(this._handleResize);
|
|
7472
|
+
this._resizeObserver.observe(this.viewport, { box: "border-box" });
|
|
7473
|
+
for (const item of Array.from(this.viewport.children)) this._resizeObserver.observe(item);
|
|
7474
|
+
}
|
|
7475
|
+
this._mutationObserver = new MutationObserver(this._handleMutations);
|
|
7476
|
+
this._mutationObserver.observe(this.viewport, { childList: true });
|
|
7477
|
+
this.viewport.addEventListener("scroll", this._handleScroll, { passive: true });
|
|
7478
|
+
for (const type of ["wheel", "touchstart", "pointerdown", "keydown"]) {
|
|
7479
|
+
this.viewport.addEventListener(type, this._handleUserScroll, { passive: true });
|
|
7480
|
+
}
|
|
7481
|
+
for (const type of RELEASE_EVENTS) document.addEventListener(type, this._handleRelease);
|
|
7482
|
+
this.button?.addEventListener("click", this._handleButtonClick);
|
|
7483
|
+
}
|
|
7484
|
+
static get defaults() {
|
|
7485
|
+
return _defaults18;
|
|
7486
|
+
}
|
|
7487
|
+
/**
|
|
7488
|
+
* Initializes instances of MessageScroller.
|
|
7489
|
+
* @param els HTML elements.
|
|
7490
|
+
* @param options Component options.
|
|
7491
|
+
*/
|
|
7492
|
+
static init(els, options = {}) {
|
|
7493
|
+
return super.init(els, options, _MessageScroller);
|
|
7494
|
+
}
|
|
7495
|
+
static getInstance(el) {
|
|
7496
|
+
return el["Expressive_MessageScroller"];
|
|
7497
|
+
}
|
|
7498
|
+
destroy() {
|
|
7499
|
+
this.el["Expressive_MessageScroller"] = void 0;
|
|
7500
|
+
if (!this.viewport) return;
|
|
7501
|
+
this._resizeObserver?.disconnect();
|
|
7502
|
+
this._mutationObserver?.disconnect();
|
|
7503
|
+
this.viewport.removeEventListener("scroll", this._handleScroll);
|
|
7504
|
+
for (const type of ["wheel", "touchstart", "pointerdown", "keydown"]) {
|
|
7505
|
+
this.viewport.removeEventListener(type, this._handleUserScroll);
|
|
7506
|
+
}
|
|
7507
|
+
for (const type of RELEASE_EVENTS) document.removeEventListener(type, this._handleRelease);
|
|
7508
|
+
this.viewport.style.removeProperty("--md-comp-message-scroller-spacer");
|
|
7509
|
+
if (this.button) {
|
|
7510
|
+
this.button.removeEventListener("click", this._handleButtonClick);
|
|
7511
|
+
this.button.inert = false;
|
|
7512
|
+
}
|
|
7513
|
+
}
|
|
7514
|
+
/** Scrolls to the latest message and resumes following. */
|
|
7515
|
+
scrollToEnd() {
|
|
7516
|
+
this._following = this.options.autoScroll;
|
|
7517
|
+
this._scrollTo("end");
|
|
7518
|
+
}
|
|
7519
|
+
/** Scrolls to the first message. */
|
|
7520
|
+
scrollToStart() {
|
|
7521
|
+
this._following = false;
|
|
7522
|
+
this._scrollTo(0);
|
|
7523
|
+
}
|
|
7524
|
+
/**
|
|
7525
|
+
* Scrolls the element with this id to the top of the viewport, below the
|
|
7526
|
+
* peek, and stops following.
|
|
7527
|
+
* @returns false when no such element is inside the viewport.
|
|
7528
|
+
*/
|
|
7529
|
+
scrollToMessage(id) {
|
|
7530
|
+
const target = Utils.getElementById(this.viewport, id);
|
|
7531
|
+
if (!target || !this.viewport.contains(target)) return false;
|
|
7532
|
+
this._following = false;
|
|
7533
|
+
this._scrollTo(this._top(target) - this.options.peek);
|
|
7534
|
+
return true;
|
|
7535
|
+
}
|
|
7536
|
+
_open() {
|
|
7537
|
+
this._opened = true;
|
|
7538
|
+
const v = this.viewport;
|
|
7539
|
+
const position = this.options.defaultScrollPosition;
|
|
7540
|
+
const anchors = v.querySelectorAll(":scope > [data-scroll-anchor]");
|
|
7541
|
+
const anchor = position === "last-anchor" ? anchors[anchors.length - 1] : null;
|
|
7542
|
+
if (position === "start") v.scrollTop = 0;
|
|
7543
|
+
else if (anchor && this._contentEnd() - this._top(anchor) + this.options.peek > v.clientHeight) {
|
|
7544
|
+
v.scrollTop = this._top(anchor) - this.options.peek;
|
|
7545
|
+
} else v.scrollTop = v.scrollHeight;
|
|
7546
|
+
this._settle();
|
|
7547
|
+
}
|
|
7548
|
+
/**
|
|
7549
|
+
* Records where the reader is once no programmatic scroll is running.
|
|
7550
|
+
* Content can grow between a scroll and the resize that re-pins the end,
|
|
7551
|
+
* so only moving toward the start releases following.
|
|
7552
|
+
*/
|
|
7553
|
+
_settle() {
|
|
7554
|
+
const top = this.viewport.scrollTop;
|
|
7555
|
+
const kept = this._following && top >= this._lastTop - EDGE;
|
|
7556
|
+
this._following = this.options.autoScroll && (kept || this._atEnd());
|
|
7557
|
+
this._lastTop = top;
|
|
7558
|
+
this._capture();
|
|
7559
|
+
this._sync();
|
|
7560
|
+
}
|
|
7561
|
+
_scrollTo(target) {
|
|
7562
|
+
const v = this.viewport;
|
|
7563
|
+
if (Math.abs(v.scrollTop - this._resolve(target)) <= EDGE) {
|
|
7564
|
+
this._target = null;
|
|
7565
|
+
this._settle();
|
|
7566
|
+
return;
|
|
7567
|
+
}
|
|
7568
|
+
this._target = target;
|
|
7569
|
+
this._distance = Infinity;
|
|
7570
|
+
const reduce = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
7571
|
+
v.scrollTo({
|
|
7572
|
+
top: target === "end" ? v.scrollHeight : target,
|
|
7573
|
+
behavior: reduce ? "auto" : "smooth"
|
|
7574
|
+
});
|
|
7575
|
+
}
|
|
7576
|
+
_resolve(target) {
|
|
7577
|
+
const max = this.viewport.scrollHeight - this.viewport.clientHeight;
|
|
7578
|
+
return target === "end" ? max : Math.max(0, Math.min(target, max));
|
|
7579
|
+
}
|
|
7580
|
+
_atEnd() {
|
|
7581
|
+
const v = this.viewport;
|
|
7582
|
+
return v.scrollHeight - v.scrollTop - v.clientHeight <= EDGE;
|
|
7583
|
+
}
|
|
7584
|
+
/** Offset of an element from the top of the scrolled content. */
|
|
7585
|
+
_top(el) {
|
|
7586
|
+
const v = this.viewport;
|
|
7587
|
+
return el.getBoundingClientRect().top - v.getBoundingClientRect().top - v.clientTop + v.scrollTop;
|
|
7588
|
+
}
|
|
7589
|
+
/** Bottom of the content without the spacer, padding included. */
|
|
7590
|
+
_contentEnd() {
|
|
7591
|
+
const last = this.viewport.lastElementChild;
|
|
7592
|
+
if (!last) return 0;
|
|
7593
|
+
const padding = parseFloat(getComputedStyle(this.viewport).paddingBlockEnd) - this._spacer;
|
|
7594
|
+
return this._top(last) + last.getBoundingClientRect().height + padding;
|
|
7595
|
+
}
|
|
7596
|
+
/** Remembers the first visible message so prepends can keep it in place. */
|
|
7597
|
+
_capture() {
|
|
7598
|
+
const items = this.viewport.children;
|
|
7599
|
+
const top = this.viewport.getBoundingClientRect().top + this.viewport.clientTop;
|
|
7600
|
+
let low = 0;
|
|
7601
|
+
let high = items.length - 1;
|
|
7602
|
+
while (low < high) {
|
|
7603
|
+
const mid = low + high >> 1;
|
|
7604
|
+
if (items[mid].getBoundingClientRect().bottom > top) high = mid;
|
|
7605
|
+
else low = mid + 1;
|
|
7606
|
+
}
|
|
7607
|
+
this._ref = items[low] ?? null;
|
|
7608
|
+
this._refOffset = this._ref ? this._ref.getBoundingClientRect().top - top : 0;
|
|
7609
|
+
}
|
|
7610
|
+
/** Moves the remembered message back to where the reader last saw it. */
|
|
7611
|
+
_restore() {
|
|
7612
|
+
if (this._ref?.parentElement !== this.viewport) {
|
|
7613
|
+
this._capture();
|
|
7614
|
+
return;
|
|
7615
|
+
}
|
|
7616
|
+
const top = this.viewport.getBoundingClientRect().top + this.viewport.clientTop;
|
|
7617
|
+
const delta = this._ref.getBoundingClientRect().top - top - this._refOffset;
|
|
7618
|
+
if (Math.abs(delta) >= 1) this.viewport.scrollTop += delta;
|
|
7619
|
+
}
|
|
7620
|
+
/** Sizes the blank space that lets the latest anchored turn reach the top. */
|
|
7621
|
+
_updateSpacer() {
|
|
7622
|
+
let space = 0;
|
|
7623
|
+
if (this._anchor?.parentElement === this.viewport) {
|
|
7624
|
+
const room = this._top(this._anchor) - this.options.peek + this.viewport.clientHeight;
|
|
7625
|
+
space = Math.max(0, Math.ceil(room - this._contentEnd()));
|
|
7626
|
+
} else this._anchor = null;
|
|
7627
|
+
if (space === this._spacer) return;
|
|
7628
|
+
this._spacer = space;
|
|
7629
|
+
if (space) this.viewport.style.setProperty("--md-comp-message-scroller-spacer", `${space}px`);
|
|
7630
|
+
else this.viewport.style.removeProperty("--md-comp-message-scroller-spacer");
|
|
7631
|
+
}
|
|
7632
|
+
/** The button is inert while there is nothing below. */
|
|
7633
|
+
_sync() {
|
|
7634
|
+
if (!this.button) return;
|
|
7635
|
+
const idle = this._atEnd();
|
|
7636
|
+
const root = this.button.getRootNode();
|
|
7637
|
+
if (idle && root.activeElement === this.button) this.viewport.focus({ preventScroll: true });
|
|
7638
|
+
this.button.inert = idle;
|
|
7639
|
+
}
|
|
7640
|
+
};
|
|
7641
|
+
|
|
7642
|
+
// src/ts/components/richTextEditor.ts
|
|
7643
|
+
var _defaults19 = {
|
|
7644
|
+
Editor: null,
|
|
7645
|
+
extensions: []
|
|
7646
|
+
};
|
|
7647
|
+
var NAMING = ["aria-label", "aria-labelledby", "aria-describedby"];
|
|
7648
|
+
var RichTextEditor = class _RichTextEditor extends Component {
|
|
7649
|
+
constructor(el, options) {
|
|
7650
|
+
super(el, options, _RichTextEditor);
|
|
7651
|
+
/** The Tiptap editor. */
|
|
7652
|
+
this.editor = null;
|
|
7653
|
+
/** The element the editor is mounted in. */
|
|
7654
|
+
this.content = null;
|
|
7655
|
+
this._actions = /* @__PURE__ */ new Map();
|
|
7656
|
+
this._label = null;
|
|
7657
|
+
this._handleClick = (e) => {
|
|
7658
|
+
const button = e.target.closest("button[data-command]");
|
|
7659
|
+
const action = this._actions.get(button);
|
|
7660
|
+
if (action && !button.disabled) this.editor.chain().focus()[action.command](action.attributes).run();
|
|
7661
|
+
};
|
|
7662
|
+
this._handleLabelClick = () => {
|
|
7663
|
+
this.editor.chain().focus().run();
|
|
7664
|
+
};
|
|
7665
|
+
this._handleUpdate = () => {
|
|
7666
|
+
this.textarea.value = this.editor.isEmpty ? "" : this.editor.getHTML();
|
|
7667
|
+
this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
7668
|
+
};
|
|
7669
|
+
this._handleTransaction = () => {
|
|
7670
|
+
const can = this.editor.isEditable && this.editor.can();
|
|
7671
|
+
for (const [button, { command, attributes, active }] of this._actions) {
|
|
7672
|
+
button.disabled = !can || !(command in this.editor.commands) || !can[command](attributes);
|
|
7673
|
+
if (!active) continue;
|
|
7674
|
+
const on = this.editor.isActive(active, attributes);
|
|
7675
|
+
button.setAttribute("aria-pressed", String(on));
|
|
7676
|
+
button.classList.toggle("active", on);
|
|
7677
|
+
}
|
|
7678
|
+
};
|
|
7679
|
+
this.el["Expressive_RichTextEditor"] = this;
|
|
7680
|
+
this.options = {
|
|
7681
|
+
..._RichTextEditor.defaults,
|
|
7682
|
+
...options
|
|
7683
|
+
};
|
|
7684
|
+
this.textarea = el.querySelector(":scope > textarea");
|
|
7685
|
+
if (!this.textarea || !this.options.Editor) {
|
|
7686
|
+
console.error("RichTextEditor needs a <textarea> child and the Tiptap Editor class in options.Editor.");
|
|
7687
|
+
return;
|
|
7688
|
+
}
|
|
7689
|
+
for (const button of el.querySelectorAll("button[data-command]")) {
|
|
7690
|
+
const command = button.dataset.command;
|
|
7691
|
+
const attributes2 = button.dataset.attributes ? JSON.parse(button.dataset.attributes) : void 0;
|
|
7692
|
+
const active = command.startsWith("toggle") ? command[6].toLowerCase() + command.slice(7) : void 0;
|
|
7693
|
+
this._actions.set(button, { command, attributes: attributes2, active });
|
|
7694
|
+
}
|
|
7695
|
+
const attributes = { role: "textbox", "aria-multiline": "true" };
|
|
7696
|
+
for (const name of NAMING) {
|
|
7697
|
+
const value = this.textarea.getAttribute(name);
|
|
7698
|
+
if (value) attributes[name] = value;
|
|
7699
|
+
}
|
|
7700
|
+
this._label = this.textarea.labels?.[0] ?? null;
|
|
7701
|
+
if (this._label && !attributes["aria-labelledby"]) {
|
|
7702
|
+
if (!this._label.id) this._label.id = `rich-text-editor-label-${Utils.guid()}`;
|
|
7703
|
+
attributes["aria-labelledby"] = this._label.id;
|
|
7704
|
+
this._label.addEventListener("click", this._handleLabelClick);
|
|
7705
|
+
}
|
|
7706
|
+
this.content = document.createElement("div");
|
|
7707
|
+
this.content.className = "rich-text-editor-content";
|
|
7708
|
+
this.textarea.after(this.content);
|
|
7709
|
+
this.textarea.hidden = true;
|
|
7710
|
+
this.editor = new this.options.Editor({
|
|
7711
|
+
element: this.content,
|
|
7712
|
+
extensions: this.options.extensions,
|
|
7713
|
+
content: this.textarea.value,
|
|
7714
|
+
editable: !(this.textarea.disabled || this.textarea.readOnly),
|
|
7715
|
+
editorProps: { attributes }
|
|
7716
|
+
});
|
|
7717
|
+
this.editor.on("update", this._handleUpdate);
|
|
7718
|
+
this.editor.on("transaction", this._handleTransaction);
|
|
7719
|
+
this._handleTransaction();
|
|
7720
|
+
el.addEventListener("click", this._handleClick);
|
|
7721
|
+
}
|
|
7722
|
+
static get defaults() {
|
|
7723
|
+
return _defaults19;
|
|
7724
|
+
}
|
|
7725
|
+
/**
|
|
7726
|
+
* Initializes instances of RichTextEditor.
|
|
7727
|
+
* @param els HTML elements.
|
|
7728
|
+
* @param options Component options.
|
|
7729
|
+
*/
|
|
7730
|
+
static init(els, options = {}) {
|
|
7731
|
+
return super.init(els, options, _RichTextEditor);
|
|
7732
|
+
}
|
|
7733
|
+
static getInstance(el) {
|
|
7734
|
+
return el["Expressive_RichTextEditor"];
|
|
7735
|
+
}
|
|
7736
|
+
destroy() {
|
|
7737
|
+
this.el["Expressive_RichTextEditor"] = void 0;
|
|
7738
|
+
if (!this.editor) return;
|
|
7739
|
+
this.el.removeEventListener("click", this._handleClick);
|
|
7740
|
+
this._label?.removeEventListener("click", this._handleLabelClick);
|
|
7741
|
+
this.editor.off("update", this._handleUpdate);
|
|
7742
|
+
this.editor.off("transaction", this._handleTransaction);
|
|
7743
|
+
this.editor.destroy();
|
|
7744
|
+
this.editor = null;
|
|
7745
|
+
this.content.remove();
|
|
7746
|
+
this.textarea.hidden = false;
|
|
7747
|
+
for (const button of this._actions.keys()) {
|
|
7748
|
+
button.disabled = false;
|
|
7749
|
+
button.classList.remove("active");
|
|
7750
|
+
button.removeAttribute("aria-pressed");
|
|
7751
|
+
}
|
|
7752
|
+
}
|
|
7753
|
+
};
|
|
7754
|
+
|
|
5549
7755
|
// src/ts/components/lightbox.ts
|
|
5550
|
-
var
|
|
7756
|
+
var _defaults20 = {
|
|
5551
7757
|
inDuration: 275,
|
|
5552
7758
|
outDuration: 200,
|
|
5553
7759
|
onOpenStart: null,
|
|
@@ -5696,7 +7902,7 @@ var _Lightbox = class _Lightbox extends Component {
|
|
|
5696
7902
|
this._setupEventHandlers();
|
|
5697
7903
|
}
|
|
5698
7904
|
static get defaults() {
|
|
5699
|
-
return
|
|
7905
|
+
return _defaults20;
|
|
5700
7906
|
}
|
|
5701
7907
|
/**
|
|
5702
7908
|
* Initializes instances of Lightbox.
|
|
@@ -5902,7 +8108,7 @@ var Lightbox = _Lightbox;
|
|
|
5902
8108
|
|
|
5903
8109
|
// src/ts/components/scrollspy.ts
|
|
5904
8110
|
var _defaultGetActiveElement = (id) => `a[href="#${id}"]`;
|
|
5905
|
-
var
|
|
8111
|
+
var _defaults21 = {
|
|
5906
8112
|
scrollOffset: 200,
|
|
5907
8113
|
activeClass: "active",
|
|
5908
8114
|
getActiveElement: _defaultGetActiveElement,
|
|
@@ -5938,7 +8144,7 @@ var _ScrollSpy = class _ScrollSpy extends Component {
|
|
|
5938
8144
|
}
|
|
5939
8145
|
}
|
|
5940
8146
|
static get defaults() {
|
|
5941
|
-
return
|
|
8147
|
+
return _defaults21;
|
|
5942
8148
|
}
|
|
5943
8149
|
static init(els, options = {}) {
|
|
5944
8150
|
return super.init(els, options, _ScrollSpy);
|
|
@@ -6028,7 +8234,7 @@ _ScrollSpy._active = /* @__PURE__ */ new Map();
|
|
|
6028
8234
|
var ScrollSpy = _ScrollSpy;
|
|
6029
8235
|
|
|
6030
8236
|
// src/ts/components/navigationRail.ts
|
|
6031
|
-
var
|
|
8237
|
+
var _defaults22 = {
|
|
6032
8238
|
onOpenStart: null,
|
|
6033
8239
|
onOpenEnd: null,
|
|
6034
8240
|
onCloseStart: null,
|
|
@@ -6088,8 +8294,19 @@ var NavigationRail = class _NavigationRail extends Component {
|
|
|
6088
8294
|
}
|
|
6089
8295
|
};
|
|
6090
8296
|
this._onKeyDown = (e) => {
|
|
6091
|
-
if (e.key !== "Escape") return;
|
|
6092
|
-
if (this.isExpanded && this._isModal())
|
|
8297
|
+
if (e.key !== "Escape" || e.defaultPrevented) return;
|
|
8298
|
+
if (this.isExpanded && this._isModal()) {
|
|
8299
|
+
const root = this.el.getRootNode();
|
|
8300
|
+
const restoreFocus = this.el.contains(root.activeElement);
|
|
8301
|
+
let focused = root.activeElement;
|
|
8302
|
+
while (focused?.shadowRoot?.activeElement) focused = focused.shadowRoot.activeElement;
|
|
8303
|
+
this.collapse();
|
|
8304
|
+
let active = root.activeElement;
|
|
8305
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
8306
|
+
if (!this.isExpanded && restoreFocus && active === focused) {
|
|
8307
|
+
this._toggle?.focus();
|
|
8308
|
+
}
|
|
8309
|
+
}
|
|
6093
8310
|
};
|
|
6094
8311
|
this.el["Expressive_NavigationRail"] = this;
|
|
6095
8312
|
this.options = {
|
|
@@ -6104,7 +8321,7 @@ var NavigationRail = class _NavigationRail extends Component {
|
|
|
6104
8321
|
document.addEventListener("keydown", this._onKeyDown);
|
|
6105
8322
|
}
|
|
6106
8323
|
static get defaults() {
|
|
6107
|
-
return
|
|
8324
|
+
return _defaults22;
|
|
6108
8325
|
}
|
|
6109
8326
|
static init(els, options = {}) {
|
|
6110
8327
|
return super.init(els, options, _NavigationRail);
|
|
@@ -6134,7 +8351,7 @@ var NavigationRail = class _NavigationRail extends Component {
|
|
|
6134
8351
|
};
|
|
6135
8352
|
|
|
6136
8353
|
// src/ts/components/tabs.ts
|
|
6137
|
-
var
|
|
8354
|
+
var _defaults23 = {
|
|
6138
8355
|
duration: 300,
|
|
6139
8356
|
onShow: null,
|
|
6140
8357
|
swipeable: false,
|
|
@@ -6221,7 +8438,7 @@ var Tabs = class _Tabs extends Component {
|
|
|
6221
8438
|
this._setupEventHandlers();
|
|
6222
8439
|
}
|
|
6223
8440
|
static get defaults() {
|
|
6224
|
-
return
|
|
8441
|
+
return _defaults23;
|
|
6225
8442
|
}
|
|
6226
8443
|
/**
|
|
6227
8444
|
* Initializes instances of Tabs.
|
|
@@ -6408,7 +8625,7 @@ var Tabs = class _Tabs extends Component {
|
|
|
6408
8625
|
};
|
|
6409
8626
|
|
|
6410
8627
|
// src/ts/components/timepicker.ts
|
|
6411
|
-
var
|
|
8628
|
+
var _defaults24 = {
|
|
6412
8629
|
dialRadius: 135,
|
|
6413
8630
|
outerRadius: 105,
|
|
6414
8631
|
innerRadius: 70,
|
|
@@ -6449,6 +8666,7 @@ var _Timepicker = class _Timepicker extends Component {
|
|
|
6449
8666
|
/** Whether the dial (ticks + SVG + AM/PM buttons) has been built yet. */
|
|
6450
8667
|
this._clockBuilt = false;
|
|
6451
8668
|
this._handleInputClick = () => {
|
|
8669
|
+
this._updateTimeFromInput();
|
|
6452
8670
|
this._ensureClockBuilt();
|
|
6453
8671
|
if (this.displayPlugin) this.displayPlugin.show();
|
|
6454
8672
|
this.inputHours.focus();
|
|
@@ -6457,10 +8675,7 @@ var _Timepicker = class _Timepicker extends Component {
|
|
|
6457
8675
|
this._handleInputKeydown = (e) => {
|
|
6458
8676
|
if (e.key === Utils.keys.ENTER) {
|
|
6459
8677
|
e.preventDefault();
|
|
6460
|
-
this.
|
|
6461
|
-
if (this.displayPlugin) this.displayPlugin.show();
|
|
6462
|
-
this.inputHours.focus();
|
|
6463
|
-
if (typeof this.options.onInputInteraction === "function") this.options.onInputInteraction.call(this);
|
|
8678
|
+
this._handleInputClick();
|
|
6464
8679
|
}
|
|
6465
8680
|
};
|
|
6466
8681
|
this._handleClockClickStart = (e) => {
|
|
@@ -6504,7 +8719,9 @@ var _Timepicker = class _Timepicker extends Component {
|
|
|
6504
8719
|
this.inputMinutes.focus();
|
|
6505
8720
|
this.showView("minutes", this.options.duration / 2);
|
|
6506
8721
|
} else {
|
|
6507
|
-
|
|
8722
|
+
clearTimeout(this._autoSubmitTimer);
|
|
8723
|
+
this._autoSubmitTimer = setTimeout(() => {
|
|
8724
|
+
this._autoSubmitTimer = null;
|
|
6508
8725
|
if (this.options.autoSubmit) this.done();
|
|
6509
8726
|
}, this.options.duration / 2);
|
|
6510
8727
|
}
|
|
@@ -6556,6 +8773,7 @@ var _Timepicker = class _Timepicker extends Component {
|
|
|
6556
8773
|
const input = event.target;
|
|
6557
8774
|
const isHours = input === this.inputHours;
|
|
6558
8775
|
if (!isHours && input !== this.inputMinutes) return;
|
|
8776
|
+
this._cancelClockReset();
|
|
6559
8777
|
const value = Number(input.value);
|
|
6560
8778
|
const min = isHours && this.options.twelveHour ? 1 : 0;
|
|
6561
8779
|
const max = isHours ? this.options.twelveHour ? 12 : 23 : 59;
|
|
@@ -6603,7 +8821,7 @@ var _Timepicker = class _Timepicker extends Component {
|
|
|
6603
8821
|
}
|
|
6604
8822
|
}
|
|
6605
8823
|
static get defaults() {
|
|
6606
|
-
return
|
|
8824
|
+
return _defaults24;
|
|
6607
8825
|
}
|
|
6608
8826
|
/**
|
|
6609
8827
|
* Initializes instances of Timepicker.
|
|
@@ -6624,6 +8842,10 @@ var _Timepicker = class _Timepicker extends Component {
|
|
|
6624
8842
|
return el["Expressive_Timepicker"];
|
|
6625
8843
|
}
|
|
6626
8844
|
destroy() {
|
|
8845
|
+
clearTimeout(this._autoSubmitTimer);
|
|
8846
|
+
clearTimeout(this.toggleViewTimer);
|
|
8847
|
+
clearTimeout(this.vibrateTimer);
|
|
8848
|
+
this._cancelClockReset();
|
|
6627
8849
|
this._removeEventHandlers();
|
|
6628
8850
|
this.displayPlugin?.destroy();
|
|
6629
8851
|
this.containerEl.remove();
|
|
@@ -6810,6 +9032,7 @@ var _Timepicker = class _Timepicker extends Component {
|
|
|
6810
9032
|
}
|
|
6811
9033
|
}
|
|
6812
9034
|
_updateTimeFromInput() {
|
|
9035
|
+
this._cancelClockReset();
|
|
6813
9036
|
let value = ((this.el.value || this.options.defaultTime || "") + "").trim().toUpperCase().split(":");
|
|
6814
9037
|
const period = value[1]?.match(/AM|PM/)?.[0];
|
|
6815
9038
|
value[1] = value[1]?.replace(/AM|PM/, "");
|
|
@@ -6827,12 +9050,17 @@ var _Timepicker = class _Timepicker extends Component {
|
|
|
6827
9050
|
this.inputMinutes.value = _Timepicker._addLeadingZero(this.minutes);
|
|
6828
9051
|
this._updateAmPmView();
|
|
6829
9052
|
}
|
|
9053
|
+
_cancelClockReset() {
|
|
9054
|
+
clearTimeout(this._clockResetTimer);
|
|
9055
|
+
this._clockResetTimer = null;
|
|
9056
|
+
this._canvas?.classList.remove("timepicker-canvas-out");
|
|
9057
|
+
}
|
|
6830
9058
|
resetClock(delay) {
|
|
9059
|
+
this._cancelClockReset();
|
|
6831
9060
|
const view = this.currentView, value = this[view], isHours = view === "hours", unit = Math.PI / (isHours ? 6 : 30), radian = value * unit, radius = isHours && value > 0 && value < 13 ? this.options.innerRadius : this.options.outerRadius, x = Math.sin(radian) * radius, y = -Math.cos(radian) * radius;
|
|
6832
9061
|
if (delay) {
|
|
6833
9062
|
this._canvas?.classList.add("timepicker-canvas-out");
|
|
6834
|
-
setTimeout(() => {
|
|
6835
|
-
this._canvas?.classList.remove("timepicker-canvas-out");
|
|
9063
|
+
this._clockResetTimer = setTimeout(() => {
|
|
6836
9064
|
this.setHand(x, y);
|
|
6837
9065
|
}, delay);
|
|
6838
9066
|
} else {
|
|
@@ -6851,6 +9079,7 @@ var _Timepicker = class _Timepicker extends Component {
|
|
|
6851
9079
|
this.setClockAttributes(radian, radius);
|
|
6852
9080
|
}
|
|
6853
9081
|
setHand(x, y, roundBy5 = false) {
|
|
9082
|
+
this._cancelClockReset();
|
|
6854
9083
|
const isHours = this.currentView === "hours", unit = Math.PI / (isHours || roundBy5 ? 6 : 30), z = Math.sqrt(x * x + y * y), inner = isHours && z < (this.options.outerRadius + this.options.innerRadius) / 2;
|
|
6855
9084
|
let radian = Math.atan2(x, -y), radius = inner ? this.options.innerRadius : this.options.outerRadius;
|
|
6856
9085
|
if (this.options.twelveHour) {
|
|
@@ -6954,7 +9183,7 @@ _Timepicker._template = `<div class="timepicker-container">
|
|
|
6954
9183
|
var Timepicker = _Timepicker;
|
|
6955
9184
|
|
|
6956
9185
|
// src/ts/components/snackbar.ts
|
|
6957
|
-
var
|
|
9186
|
+
var _defaults25 = {
|
|
6958
9187
|
text: "",
|
|
6959
9188
|
action: "",
|
|
6960
9189
|
onAction: null,
|
|
@@ -6971,6 +9200,7 @@ var _Snackbar = class _Snackbar {
|
|
|
6971
9200
|
constructor(options = {}) {
|
|
6972
9201
|
this._dismissed = false;
|
|
6973
9202
|
this._returnFocus = null;
|
|
9203
|
+
this._restoreElement = null;
|
|
6974
9204
|
this.options = {
|
|
6975
9205
|
..._Snackbar.defaults,
|
|
6976
9206
|
...options
|
|
@@ -6985,27 +9215,25 @@ var _Snackbar = class _Snackbar {
|
|
|
6985
9215
|
while (_Snackbar._snackbars.length > 0) {
|
|
6986
9216
|
const snackbar = _Snackbar._snackbars.shift();
|
|
6987
9217
|
snackbar._dismissed = true;
|
|
6988
|
-
|
|
6989
|
-
snackbar._restoreFocus();
|
|
6990
|
-
snackbar.el.remove();
|
|
9218
|
+
snackbar._restoreFocus(true);
|
|
6991
9219
|
}
|
|
6992
9220
|
let focused = document.activeElement;
|
|
6993
9221
|
while (focused?.shadowRoot?.activeElement) focused = focused.shadowRoot.activeElement;
|
|
6994
9222
|
this._returnFocus = focused instanceof HTMLElement ? focused : null;
|
|
9223
|
+
this.el = this._createSnackbar();
|
|
6995
9224
|
const root = Utils.portalRoot(this.options.root ?? document.body);
|
|
6996
9225
|
if (!_Snackbar._container || !_Snackbar._container.isConnected || _Snackbar._container.parentNode !== root) {
|
|
6997
9226
|
if (_Snackbar._container) _Snackbar._removeContainer();
|
|
6998
9227
|
_Snackbar._createContainer(root);
|
|
6999
9228
|
}
|
|
9229
|
+
_Snackbar._container.appendChild(this.el);
|
|
9230
|
+
this.el["Expressive_Snackbar"] = this;
|
|
7000
9231
|
_Snackbar._snackbars.push(this);
|
|
7001
|
-
const snackbarElement = this._createSnackbar();
|
|
7002
|
-
snackbarElement["Expressive_Snackbar"] = this;
|
|
7003
|
-
this.el = snackbarElement;
|
|
7004
9232
|
this._animateIn();
|
|
7005
9233
|
this._setTimer();
|
|
7006
9234
|
}
|
|
7007
9235
|
static get defaults() {
|
|
7008
|
-
return
|
|
9236
|
+
return _defaults25;
|
|
7009
9237
|
}
|
|
7010
9238
|
static getInstance(el) {
|
|
7011
9239
|
return el["Expressive_Snackbar"];
|
|
@@ -7042,6 +9270,7 @@ var _Snackbar = class _Snackbar {
|
|
|
7042
9270
|
snackbar.el.style.transition = "";
|
|
7043
9271
|
_Snackbar._dragPointerId = e.pointerId;
|
|
7044
9272
|
snackbar.startingXPos = e.clientX;
|
|
9273
|
+
snackbar.velocityX = 0;
|
|
7045
9274
|
snackbar.time = Date.now();
|
|
7046
9275
|
snackbar.xPos = e.clientX;
|
|
7047
9276
|
}
|
|
@@ -7070,7 +9299,8 @@ var _Snackbar = class _Snackbar {
|
|
|
7070
9299
|
snackbar.el.classList.remove("panning");
|
|
7071
9300
|
const totalDeltaX = snackbar.xPos - snackbar.startingXPos;
|
|
7072
9301
|
const activationDistance = snackbar.el.offsetWidth * snackbar.options.activationPercent;
|
|
7073
|
-
const
|
|
9302
|
+
const velocity = Date.now() - snackbar.time < 100 ? snackbar.velocityX : 0;
|
|
9303
|
+
const shouldBeDismissed = Math.abs(totalDeltaX) > activationDistance || velocity > 1;
|
|
7074
9304
|
if (shouldBeDismissed) {
|
|
7075
9305
|
snackbar.wasSwiped = true;
|
|
7076
9306
|
snackbar.dismiss();
|
|
@@ -7114,7 +9344,23 @@ var _Snackbar = class _Snackbar {
|
|
|
7114
9344
|
if (snackbar instanceof HTMLTemplateElement) {
|
|
7115
9345
|
const node = snackbar.content.cloneNode(true);
|
|
7116
9346
|
snackbar = node.firstElementChild;
|
|
9347
|
+
} else if (snackbar && this.options.snackbarId) {
|
|
9348
|
+
const placeholder = document.createComment("");
|
|
9349
|
+
snackbar.before(placeholder);
|
|
9350
|
+
const children = [...snackbar.childNodes];
|
|
9351
|
+
const attributes = ["class", "style", "role", "aria-live", "aria-atomic", "inert"].map((name) => [name, snackbar.getAttribute(name)]);
|
|
9352
|
+
this._restoreElement = () => {
|
|
9353
|
+
snackbar.replaceChildren(...children);
|
|
9354
|
+
for (const [name, value] of attributes) {
|
|
9355
|
+
if (value === null) snackbar.removeAttribute(name);
|
|
9356
|
+
else snackbar.setAttribute(name, value);
|
|
9357
|
+
}
|
|
9358
|
+
placeholder.replaceWith(snackbar);
|
|
9359
|
+
};
|
|
7117
9360
|
}
|
|
9361
|
+
if (!snackbar) throw new Error("Snackbar body not found");
|
|
9362
|
+
this._events = new (snackbar.ownerDocument.defaultView ?? window).AbortController();
|
|
9363
|
+
const listenerOptions = { signal: this._events.signal };
|
|
7118
9364
|
snackbar.classList.add("snackbar");
|
|
7119
9365
|
snackbar.setAttribute("role", "status");
|
|
7120
9366
|
snackbar.setAttribute("aria-live", "polite");
|
|
@@ -7125,16 +9371,19 @@ var _Snackbar = class _Snackbar {
|
|
|
7125
9371
|
this._returnFocus = e.relatedTarget;
|
|
7126
9372
|
}
|
|
7127
9373
|
this._pauseTimer();
|
|
7128
|
-
});
|
|
9374
|
+
}, listenerOptions);
|
|
7129
9375
|
snackbar.addEventListener("keydown", (e) => {
|
|
7130
9376
|
if (e.key !== "Escape" || e.defaultPrevented || this._dismissed) return;
|
|
7131
9377
|
e.preventDefault();
|
|
7132
9378
|
e.stopPropagation();
|
|
7133
9379
|
this.dismiss();
|
|
7134
|
-
});
|
|
7135
|
-
snackbar.addEventListener("focusout", () =>
|
|
7136
|
-
|
|
7137
|
-
|
|
9380
|
+
}, listenerOptions);
|
|
9381
|
+
snackbar.addEventListener("focusout", () => {
|
|
9382
|
+
clearTimeout(this._focusTimer);
|
|
9383
|
+
this._focusTimer = setTimeout(() => this._resumeTimer(), 0);
|
|
9384
|
+
}, listenerOptions);
|
|
9385
|
+
snackbar.addEventListener("pointerenter", () => this._pauseTimer(), listenerOptions);
|
|
9386
|
+
snackbar.addEventListener("pointerleave", () => this._resumeTimer(), listenerOptions);
|
|
7138
9387
|
if (this.options.classes.length > 0) {
|
|
7139
9388
|
snackbar.classList.add(...this.options.classes.split(" ").filter(Boolean));
|
|
7140
9389
|
}
|
|
@@ -7155,7 +9404,7 @@ var _Snackbar = class _Snackbar {
|
|
|
7155
9404
|
} finally {
|
|
7156
9405
|
this.dismiss();
|
|
7157
9406
|
}
|
|
7158
|
-
});
|
|
9407
|
+
}, listenerOptions);
|
|
7159
9408
|
snackbar.appendChild(action);
|
|
7160
9409
|
}
|
|
7161
9410
|
if (this.options.dismissible) {
|
|
@@ -7171,12 +9420,32 @@ var _Snackbar = class _Snackbar {
|
|
|
7171
9420
|
close.addEventListener("click", (e) => {
|
|
7172
9421
|
e.stopPropagation();
|
|
7173
9422
|
this.dismiss();
|
|
7174
|
-
});
|
|
9423
|
+
}, listenerOptions);
|
|
7175
9424
|
snackbar.appendChild(close);
|
|
7176
9425
|
}
|
|
7177
|
-
_Snackbar._container.appendChild(snackbar);
|
|
7178
9426
|
return snackbar;
|
|
7179
9427
|
}
|
|
9428
|
+
_remove() {
|
|
9429
|
+
if (this._events.signal.aborted) return;
|
|
9430
|
+
this._events.abort();
|
|
9431
|
+
clearTimeout(this.counterTimeout);
|
|
9432
|
+
clearTimeout(this._transitionTimer);
|
|
9433
|
+
clearTimeout(this._dismissTimer);
|
|
9434
|
+
clearTimeout(this._focusTimer);
|
|
9435
|
+
this.counterTimeout = null;
|
|
9436
|
+
if (_Snackbar._draggedSnackbar === this) {
|
|
9437
|
+
_Snackbar._draggedSnackbar = null;
|
|
9438
|
+
_Snackbar._dragPointerId = null;
|
|
9439
|
+
}
|
|
9440
|
+
this.el["Expressive_Snackbar"] = void 0;
|
|
9441
|
+
if (this._restoreElement) this._restoreElement();
|
|
9442
|
+
else this.el.remove();
|
|
9443
|
+
const index = _Snackbar._snackbars.indexOf(this);
|
|
9444
|
+
if (index >= 0) _Snackbar._snackbars.splice(index, 1);
|
|
9445
|
+
if (!_Snackbar._snackbars.length && _Snackbar._container?.childElementCount === 0) {
|
|
9446
|
+
_Snackbar._removeContainer();
|
|
9447
|
+
}
|
|
9448
|
+
}
|
|
7180
9449
|
_animateIn() {
|
|
7181
9450
|
this.el.style.display = "";
|
|
7182
9451
|
this.el.style.opacity = "0";
|
|
@@ -7185,7 +9454,7 @@ var _Snackbar = class _Snackbar {
|
|
|
7185
9454
|
transform ${this.options.inDuration}ms ease,
|
|
7186
9455
|
opacity ${this.options.inDuration}ms ease
|
|
7187
9456
|
`;
|
|
7188
|
-
setTimeout(() => {
|
|
9457
|
+
this._transitionTimer = setTimeout(() => {
|
|
7189
9458
|
this.el.style.transform = "";
|
|
7190
9459
|
this.el.style.opacity = "1";
|
|
7191
9460
|
}, 1);
|
|
@@ -7214,12 +9483,12 @@ var _Snackbar = class _Snackbar {
|
|
|
7214
9483
|
if (this._dismissed || this.panning || this.el.matches(":hover, :focus-within") || this.counterTimeout != null) return;
|
|
7215
9484
|
this._setTimer();
|
|
7216
9485
|
}
|
|
7217
|
-
_restoreFocus() {
|
|
9486
|
+
_restoreFocus(remove = false) {
|
|
7218
9487
|
const focused = this.el.getRootNode().activeElement;
|
|
7219
|
-
|
|
7220
|
-
this._returnFocus.focus({ preventScroll: true });
|
|
7221
|
-
}
|
|
9488
|
+
const returnFocus = focused && this.el.contains(focused) ? this._returnFocus : null;
|
|
7222
9489
|
this.el.inert = true;
|
|
9490
|
+
if (remove) this._remove();
|
|
9491
|
+
if (returnFocus?.isConnected) returnFocus.focus({ preventScroll: true });
|
|
7223
9492
|
}
|
|
7224
9493
|
/**
|
|
7225
9494
|
* Dismiss snackbar with animation.
|
|
@@ -7230,6 +9499,7 @@ var _Snackbar = class _Snackbar {
|
|
|
7230
9499
|
clearTimeout(this.counterTimeout);
|
|
7231
9500
|
this.counterTimeout = null;
|
|
7232
9501
|
this._restoreFocus();
|
|
9502
|
+
if (this._events.signal.aborted) return;
|
|
7233
9503
|
const activationDistance = this.el.offsetWidth * this.options.activationPercent;
|
|
7234
9504
|
if (this.wasSwiped) {
|
|
7235
9505
|
this.el.style.transition = "transform .05s, opacity .05s";
|
|
@@ -7239,22 +9509,16 @@ var _Snackbar = class _Snackbar {
|
|
|
7239
9509
|
this.el.style.transition = `
|
|
7240
9510
|
margin ${this.options.outDuration}ms ease,
|
|
7241
9511
|
opacity ${this.options.outDuration}ms ease`;
|
|
7242
|
-
|
|
9512
|
+
clearTimeout(this._transitionTimer);
|
|
9513
|
+
this._transitionTimer = setTimeout(() => {
|
|
7243
9514
|
this.el.style.opacity = "0";
|
|
7244
9515
|
this.el.style.marginTop = "-40px";
|
|
7245
9516
|
}, 1);
|
|
7246
|
-
setTimeout(() => {
|
|
9517
|
+
this._dismissTimer = setTimeout(() => {
|
|
9518
|
+
this._remove();
|
|
7247
9519
|
if (typeof this.options.completeCallback === "function") {
|
|
7248
9520
|
this.options.completeCallback();
|
|
7249
9521
|
}
|
|
7250
|
-
if (this.el.id != this.options.snackbarId) {
|
|
7251
|
-
this.el.remove();
|
|
7252
|
-
const index = _Snackbar._snackbars.indexOf(this);
|
|
7253
|
-
if (index >= 0) _Snackbar._snackbars.splice(index, 1);
|
|
7254
|
-
if (_Snackbar._snackbars.length === 0 && _Snackbar._container && _Snackbar._container.childElementCount === 0) {
|
|
7255
|
-
_Snackbar._removeContainer();
|
|
7256
|
-
}
|
|
7257
|
-
}
|
|
7258
9522
|
}, this.options.outDuration);
|
|
7259
9523
|
}
|
|
7260
9524
|
};
|
|
@@ -7266,7 +9530,7 @@ _Snackbar._draggedSnackbar = null;
|
|
|
7266
9530
|
var Snackbar = _Snackbar;
|
|
7267
9531
|
|
|
7268
9532
|
// src/ts/components/tooltip.ts
|
|
7269
|
-
var
|
|
9533
|
+
var _defaults26 = {
|
|
7270
9534
|
exitDelay: 200,
|
|
7271
9535
|
enterDelay: 0,
|
|
7272
9536
|
text: "",
|
|
@@ -7283,9 +9547,8 @@ var Tooltip = class _Tooltip extends Component {
|
|
|
7283
9547
|
/**
|
|
7284
9548
|
* Show tooltip.
|
|
7285
9549
|
*/
|
|
7286
|
-
this.open = (isManual) => {
|
|
9550
|
+
this.open = (isManual = true) => {
|
|
7287
9551
|
if (this.isOpen) return;
|
|
7288
|
-
isManual = isManual === void 0 ? true : void 0;
|
|
7289
9552
|
clearTimeout(this._exitDelayTimeout);
|
|
7290
9553
|
clearTimeout(this._animationTimeout);
|
|
7291
9554
|
this.isOpen = true;
|
|
@@ -7349,7 +9612,7 @@ var Tooltip = class _Tooltip extends Component {
|
|
|
7349
9612
|
this._setupEventHandlers();
|
|
7350
9613
|
}
|
|
7351
9614
|
static get defaults() {
|
|
7352
|
-
return
|
|
9615
|
+
return _defaults26;
|
|
7353
9616
|
}
|
|
7354
9617
|
/**
|
|
7355
9618
|
* Initializes instances of Tooltip.
|
|
@@ -7369,8 +9632,21 @@ var Tooltip = class _Tooltip extends Component {
|
|
|
7369
9632
|
this.isOpen = false;
|
|
7370
9633
|
this.isHovered = false;
|
|
7371
9634
|
this.isFocused = false;
|
|
7372
|
-
|
|
7373
|
-
|
|
9635
|
+
const descriptions = this.el.getAttribute("aria-describedby")?.split(/\s+/);
|
|
9636
|
+
if (descriptions?.includes(this.tooltipEl.id)) {
|
|
9637
|
+
const remaining = descriptions.filter((id) => id && id !== this.tooltipEl.id).join(" ");
|
|
9638
|
+
if (remaining) this.el.setAttribute("aria-describedby", remaining);
|
|
9639
|
+
else this.el.removeAttribute("aria-describedby");
|
|
9640
|
+
}
|
|
9641
|
+
if (this._originalContent) {
|
|
9642
|
+
const { el, marker, display, priority, hidden, contentClass } = this._originalContent;
|
|
9643
|
+
el.style.setProperty("display", display, priority);
|
|
9644
|
+
if (hidden === null) el.removeAttribute("hidden");
|
|
9645
|
+
else el.setAttribute("hidden", hidden);
|
|
9646
|
+
el.classList.toggle("tooltip-content", contentClass);
|
|
9647
|
+
if (!el.className) el.removeAttribute("class");
|
|
9648
|
+
marker.replaceWith(el);
|
|
9649
|
+
this._originalContent = null;
|
|
7374
9650
|
}
|
|
7375
9651
|
this.tooltipEl.remove();
|
|
7376
9652
|
this._removeEventHandlers();
|
|
@@ -7381,10 +9657,21 @@ var Tooltip = class _Tooltip extends Component {
|
|
|
7381
9657
|
this.tooltipEl.classList.add("tooltip");
|
|
7382
9658
|
this.tooltipEl.id = `tooltip-${Utils.guid()}`;
|
|
7383
9659
|
this.tooltipEl.setAttribute("role", "tooltip");
|
|
7384
|
-
this.el.setAttribute("aria-describedby", this.tooltipEl.id);
|
|
9660
|
+
this.el.setAttribute("aria-describedby", [this.el.getAttribute("aria-describedby"), this.tooltipEl.id].filter(Boolean).join(" "));
|
|
7385
9661
|
const tooltipContentEl = this.options.tooltipId ? Utils.getElementById(this.el, this.options.tooltipId) : document.createElement("div");
|
|
7386
9662
|
if (this.options.tooltipId) {
|
|
7387
9663
|
this.tooltipEl.classList.add("rich");
|
|
9664
|
+
const marker = document.createComment("");
|
|
9665
|
+
tooltipContentEl.before(marker);
|
|
9666
|
+
this._originalContent = {
|
|
9667
|
+
el: tooltipContentEl,
|
|
9668
|
+
marker,
|
|
9669
|
+
display: tooltipContentEl.style.display,
|
|
9670
|
+
priority: tooltipContentEl.style.getPropertyPriority("display"),
|
|
9671
|
+
hidden: tooltipContentEl.getAttribute("hidden"),
|
|
9672
|
+
contentClass: tooltipContentEl.classList.contains("tooltip-content")
|
|
9673
|
+
};
|
|
9674
|
+
tooltipContentEl.removeAttribute("hidden");
|
|
7388
9675
|
}
|
|
7389
9676
|
tooltipContentEl.style.display = "";
|
|
7390
9677
|
tooltipContentEl.classList.add("tooltip-content");
|
|
@@ -7544,11 +9831,31 @@ var AUTO_INIT_COMPONENTS = {
|
|
|
7544
9831
|
},
|
|
7545
9832
|
Carousel: { component: Carousel, selector: ".carousel" },
|
|
7546
9833
|
Chips: { component: Chips, selector: ".chips" },
|
|
9834
|
+
ColumnChart: { component: ColumnChart, selector: ".column-chart" },
|
|
9835
|
+
BarChart: { component: BarChart, selector: ".bar-chart" },
|
|
9836
|
+
PieChart: { component: PieChart, selector: ".pie-chart" },
|
|
9837
|
+
HeatmapChart: { component: HeatmapChart, selector: ".heatmap-chart" },
|
|
9838
|
+
RadarChart: { component: RadarChart, selector: ".radar-chart" },
|
|
9839
|
+
RadialChart: { component: RadialChart, selector: ".radial-chart" },
|
|
9840
|
+
MixedChart: { component: MixedChart, selector: ".mixed-chart" },
|
|
9841
|
+
CommandPalette: {
|
|
9842
|
+
component: CommandPalette,
|
|
9843
|
+
selector: "dialog.command-palette"
|
|
9844
|
+
},
|
|
9845
|
+
Questionnaire: {
|
|
9846
|
+
component: Questionnaire,
|
|
9847
|
+
selector: "form.questionnaire"
|
|
9848
|
+
},
|
|
7547
9849
|
Datepicker: {
|
|
7548
9850
|
component: Datepicker,
|
|
7549
9851
|
selector: ".datepicker, .date-picker"
|
|
7550
9852
|
},
|
|
9853
|
+
LineChart: { component: LineChart, selector: ".line-chart" },
|
|
7551
9854
|
Menu: { component: Menu, selector: ".menu-trigger" },
|
|
9855
|
+
MessageScroller: {
|
|
9856
|
+
component: MessageScroller,
|
|
9857
|
+
selector: ".message-scroller"
|
|
9858
|
+
},
|
|
7552
9859
|
Lightbox: { component: Lightbox, selector: ".lightboxed" },
|
|
7553
9860
|
ScrollSpy: { component: ScrollSpy, selector: ".scrollspy" },
|
|
7554
9861
|
FormSelect: { component: FormSelect, selector: "select" },
|
|
@@ -7582,7 +9889,7 @@ function AutoInit(context = document.body, options) {
|
|
|
7582
9889
|
}
|
|
7583
9890
|
|
|
7584
9891
|
// src/ts/modular.ts
|
|
7585
|
-
var version = "0.
|
|
9892
|
+
var version = "0.13.1";
|
|
7586
9893
|
|
|
7587
9894
|
// src/ts/index.ts
|
|
7588
9895
|
Forms.Init();
|