@stackoverflow/stacks 1.7.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/README.md +1 -1
  2. package/dist/components/activity-indicator/activity-indicator.a11y.test.d.ts +1 -0
  3. package/dist/components/activity-indicator/activity-indicator.visual.test.d.ts +1 -0
  4. package/dist/components/avatar/avatar.a11y.test.d.ts +1 -0
  5. package/dist/components/avatar/avatar.visual.test.d.ts +1 -0
  6. package/dist/{controllers/s-banner.d.ts → components/banner/banner.d.ts} +1 -1
  7. package/dist/components/banner/banner.test.d.ts +1 -0
  8. package/dist/components/banner/banner.visual.test.d.ts +1 -0
  9. package/dist/components/button/button.a11y.test.d.ts +1 -0
  10. package/dist/components/button/button.visual.test.d.ts +1 -0
  11. package/dist/{controllers/s-expandable-control.d.ts → components/expandable/expandable.d.ts} +1 -1
  12. package/dist/components/expandable/expandable.test.d.ts +1 -0
  13. package/dist/{controllers/s-modal.d.ts → components/modal/modal.d.ts} +1 -1
  14. package/dist/{controllers/s-navigation-tablist.d.ts → components/navigation/navigation.d.ts} +1 -1
  15. package/dist/{controllers/s-popover.d.ts → components/popover/popover.d.ts} +1 -1
  16. package/dist/{controllers/s-tooltip.d.ts → components/popover/tooltip.d.ts} +1 -1
  17. package/dist/components/popover/tooltip.test.d.ts +1 -0
  18. package/dist/components/popover/tooltip.visual.test.d.ts +1 -0
  19. package/dist/{controllers/s-table.d.ts → components/table/table.d.ts} +1 -1
  20. package/dist/{controllers/s-toast.d.ts → components/toast/toast.d.ts} +1 -1
  21. package/dist/components/toast/toast.test.d.ts +1 -0
  22. package/dist/components/toast/toast.visual.test.d.ts +1 -0
  23. package/dist/{controllers/s-uploader.d.ts → components/uploader/uploader.d.ts} +1 -1
  24. package/dist/controllers.d.ts +9 -0
  25. package/dist/css/stacks.css +1351 -1171
  26. package/dist/css/stacks.min.css +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/js/stacks.js +545 -545
  29. package/dist/js/stacks.min.js +1 -1
  30. package/dist/test/test-utils.d.ts +136 -0
  31. package/lib/{css/atomic/borders.less → atomic/border.less} +18 -0
  32. package/lib/base/fieldset.less +5 -0
  33. package/lib/{css/base/icons.less → base/icon.less} +0 -9
  34. package/lib/components/activity-indicator/activity-indicator.a11y.test.ts +21 -0
  35. package/lib/components/activity-indicator/activity-indicator.visual.test.ts +23 -0
  36. package/lib/components/avatar/avatar.a11y.test.ts +36 -0
  37. package/lib/components/avatar/avatar.visual.test.ts +54 -0
  38. package/lib/components/banner/banner.less +51 -0
  39. package/lib/{test/s-banner.test.ts → components/banner/banner.test.ts} +7 -3
  40. package/lib/{ts/controllers/s-banner.ts → components/banner/banner.ts} +1 -1
  41. package/lib/components/banner/banner.visual.test.ts +36 -0
  42. package/lib/components/button/button.a11y.test.ts +32 -0
  43. package/lib/{css/components/buttons.less → components/button/button.less} +7 -6
  44. package/lib/components/button/button.visual.test.ts +52 -0
  45. package/lib/{css/components/cards.less → components/card/card.less} +1 -1
  46. package/lib/components/check-control/check-control.less +17 -0
  47. package/lib/components/check-group/check-group.less +19 -0
  48. package/lib/components/checkbox_radio/checkbox_radio.less +158 -0
  49. package/lib/components/description/description.less +9 -0
  50. package/lib/{css/components → components/expandable}/expandable.less +3 -0
  51. package/lib/components/expandable/expandable.test.ts +53 -0
  52. package/lib/{ts/controllers/s-expandable-control.ts → components/expandable/expandable.ts} +1 -1
  53. package/lib/components/input-fill/input-fill.less +35 -0
  54. package/lib/components/input-icon/input-icon.less +45 -0
  55. package/lib/components/input-message/input-message.less +48 -0
  56. package/lib/components/input_textarea/input_textarea.less +166 -0
  57. package/lib/{css/components/labels.less → components/label/label.less} +4 -4
  58. package/lib/{css/components → components/link}/link.less +9 -2
  59. package/lib/{ts/controllers/s-modal.ts → components/modal/modal.ts} +1 -1
  60. package/lib/{ts/controllers/s-navigation-tablist.ts → components/navigation/navigation.ts} +1 -1
  61. package/lib/{css/components/notices.less → components/notice/notice.less} +0 -89
  62. package/lib/{css/components/popovers.less → components/popover/popover.less} +1 -0
  63. package/lib/{ts/controllers/s-popover.ts → components/popover/popover.ts} +1 -1
  64. package/lib/{test/s-tooltip.test.ts → components/popover/tooltip.test.ts} +6 -2
  65. package/lib/{ts/controllers/s-tooltip.ts → components/popover/tooltip.ts} +2 -2
  66. package/lib/{test/s-tooltip.visual.test.ts → components/popover/tooltip.visual.test.ts} +2 -2
  67. package/lib/{css/components → components/post-summary}/post-summary.less +6 -2
  68. package/lib/components/select/select.less +148 -0
  69. package/lib/{css/components/sidebar-widgets.less → components/sidebar-widget/sidebar-widget.less} +0 -1
  70. package/lib/{css/components → components/table}/table.less +0 -5
  71. package/lib/{ts/controllers/s-table.ts → components/table/table.ts} +1 -1
  72. package/lib/components/table-container/table-container.less +4 -0
  73. package/lib/{css/components/tags.less → components/tag/tag.less} +3 -3
  74. package/lib/components/toast/toast.less +35 -0
  75. package/lib/{test/s-toast.test.ts → components/toast/toast.test.ts} +7 -3
  76. package/lib/{ts/controllers/s-toast.ts → components/toast/toast.ts} +1 -1
  77. package/lib/components/toast/toast.visual.test.ts +27 -0
  78. package/lib/{css/components/toggle-switches.less → components/toggle-switch/toggle-switch.less} +8 -0
  79. package/lib/{ts/controllers/s-uploader.ts → components/uploader/uploader.ts} +1 -1
  80. package/lib/controllers.ts +33 -0
  81. package/lib/{css/exports → exports}/mixins.less +73 -11
  82. package/lib/{ts/index.ts → index.ts} +1 -1
  83. package/lib/input-utils.less +44 -0
  84. package/lib/{css/stacks-dynamic.less → stacks-dynamic.less} +1 -2
  85. package/lib/stacks-static.less +93 -0
  86. package/lib/test/test-utils.ts +444 -0
  87. package/lib/tsconfig.json +1 -1
  88. package/package.json +26 -25
  89. package/dist/controllers/index.d.ts +0 -9
  90. package/lib/css/components/inputs.less +0 -666
  91. package/lib/css/stacks-static.less +0 -97
  92. package/lib/test/s-avatar.test.ts +0 -74
  93. package/lib/test/s-banner.visual.test.ts +0 -61
  94. package/lib/test/s-button.visual.test.ts +0 -12
  95. package/lib/test/s-toast.visual.test.ts +0 -48
  96. package/lib/ts/controllers/index.ts +0 -17
  97. /package/lib/{css/atomic/colors.less → atomic/color.less} +0 -0
  98. /package/lib/{css/atomic → atomic}/flex.less +0 -0
  99. /package/lib/{css/atomic → atomic}/gap.less +0 -0
  100. /package/lib/{css/atomic → atomic}/grid.less +0 -0
  101. /package/lib/{css/atomic → atomic}/misc.less +0 -0
  102. /package/lib/{css/atomic → atomic}/spacing.less +0 -0
  103. /package/lib/{css/atomic → atomic}/typography.less +0 -0
  104. /package/lib/{css/atomic → atomic}/width-height.less +0 -0
  105. /package/lib/{css/base → base}/body.less +0 -0
  106. /package/lib/{css/base → base}/configuration-static.less +0 -0
  107. /package/lib/{css/base/internals.less → base/internal.less} +0 -0
  108. /package/lib/{css/base → base}/reset-meyer.less +0 -0
  109. /package/lib/{css/base → base}/reset-normalize.less +0 -0
  110. /package/lib/{css/base → base}/reset.less +0 -0
  111. /package/lib/{css/components → components/activity-indicator}/activity-indicator.less +0 -0
  112. /package/lib/{css/components/anchors.less → components/anchor/anchor.less} +0 -0
  113. /package/lib/{css/components/avatars.less → components/avatar/avatar.less} +0 -0
  114. /package/lib/{css/components → components/award-bling}/award-bling.less +0 -0
  115. /package/lib/{css/components/badges.less → components/badge/badge.less} +0 -0
  116. /package/lib/{css/components → components/block-link}/block-link.less +0 -0
  117. /package/lib/{css/components → components/breadcrumbs}/breadcrumbs.less +0 -0
  118. /package/lib/{css/components/button-groups.less → components/button-group/button-group.less} +0 -0
  119. /package/lib/{css/components/code-blocks.less → components/code-block/code-block.less} +0 -0
  120. /package/lib/{css/components/empty-states.less → components/empty-state/empty-state.less} +0 -0
  121. /package/lib/{css/components/link-previews.less → components/link-preview/link-preview.less} +0 -0
  122. /package/lib/{css/components → components/menu}/menu.less +0 -0
  123. /package/lib/{css/components/modals.less → components/modal/modal.less} +0 -0
  124. /package/lib/{css/components → components/navigation}/navigation.less +0 -0
  125. /package/lib/{css/components/page-titles.less → components/page-title/page-title.less} +0 -0
  126. /package/lib/{css/components → components/pagination}/pagination.less +0 -0
  127. /package/lib/{css/components/progress-bars.less → components/progress-bar/progress-bar.less} +0 -0
  128. /package/lib/{css/components → components/prose}/prose.less +0 -0
  129. /package/lib/{css/components → components/spinner}/spinner.less +0 -0
  130. /package/lib/{css/components → components/topbar}/topbar.less +0 -0
  131. /package/lib/{css/components → components/uploader}/uploader.less +0 -0
  132. /package/lib/{css/components/user-cards.less → components/user-card/user-card.less} +0 -0
  133. /package/lib/{css/exports → exports}/constants-colors.less +0 -0
  134. /package/lib/{css/exports → exports}/constants-helpers.less +0 -0
  135. /package/lib/{css/exports → exports}/constants-type.less +0 -0
  136. /package/lib/{css/exports → exports}/exports.less +0 -0
  137. /package/lib/{css/stacks.less → stacks.less} +0 -0
  138. /package/lib/{ts/stacks.ts → stacks.ts} +0 -0
package/dist/js/stacks.js CHANGED
@@ -2488,7 +2488,7 @@ Controller.values = {};
2488
2488
 
2489
2489
 
2490
2490
 
2491
- ;// CONCATENATED MODULE: ./lib/ts/stacks.ts
2491
+ ;// CONCATENATED MODULE: ./lib/stacks.ts
2492
2492
 
2493
2493
  class StacksApplication extends Application {
2494
2494
  load(head, ...rest) {
@@ -2571,7 +2571,115 @@ function addController(name, controller) {
2571
2571
  application.register(name, createController(controller));
2572
2572
  }
2573
2573
 
2574
- ;// CONCATENATED MODULE: ./lib/ts/controllers/s-expandable-control.ts
2574
+ ;// CONCATENATED MODULE: ./lib/components/banner/banner.ts
2575
+
2576
+ class BannerController extends StacksController {
2577
+ /**
2578
+ * Toggles the visibility of the banner
2579
+ */
2580
+ toggle(dispatcher = null) {
2581
+ this._toggle(undefined, dispatcher);
2582
+ }
2583
+ /**
2584
+ * Shows the banner
2585
+ */
2586
+ show(dispatcher = null) {
2587
+ this._toggle(true, dispatcher);
2588
+ }
2589
+ /**
2590
+ * Hides the banner
2591
+ */
2592
+ hide(dispatcher = null) {
2593
+ this._toggle(false, dispatcher);
2594
+ }
2595
+ /**
2596
+ * Toggles the visibility of the banner element
2597
+ * @param show Optional parameter that force shows/hides the element or toggles it if left undefined
2598
+ */
2599
+ _toggle(show, dispatcher = null) {
2600
+ let toShow = show;
2601
+ const isVisible = this.bannerTarget.getAttribute("aria-hidden") === "false";
2602
+ // if we're letting the class toggle, we need to figure out if the banner is visible manually
2603
+ if (typeof toShow === "undefined") {
2604
+ toShow = !isVisible;
2605
+ }
2606
+ // if the state matches the disired state, return without changing anything
2607
+ if ((toShow && isVisible) || (!toShow && !isVisible)) {
2608
+ return;
2609
+ }
2610
+ const dispatchingElement = this.getDispatcher(dispatcher);
2611
+ // show/hide events trigger before toggling the class
2612
+ const triggeredEvent = this.triggerEvent(toShow ? "show" : "hide", {
2613
+ dispatcher: this.getDispatcher(dispatchingElement),
2614
+ }, this.bannerTarget);
2615
+ // if this pre-show/hide event was prevented, don't attempt to continue changing the banner state
2616
+ if (triggeredEvent.defaultPrevented) {
2617
+ return;
2618
+ }
2619
+ this.bannerTarget.setAttribute("aria-hidden", toShow ? "false" : "true");
2620
+ if (!toShow) {
2621
+ this.removeBannerOnHide();
2622
+ }
2623
+ this.triggerEvent(toShow ? "shown" : "hidden", {
2624
+ dispatcher: dispatchingElement,
2625
+ }, this.bannerTarget);
2626
+ }
2627
+ /**
2628
+ * Remove the element on hide if the `remove-when-hidden` flag is set
2629
+ */
2630
+ removeBannerOnHide() {
2631
+ if (this.data.get("remove-when-hidden") !== "true") {
2632
+ return;
2633
+ }
2634
+ this.bannerTarget.addEventListener("s-banner:hidden", () => {
2635
+ this.element.remove();
2636
+ }, { once: true });
2637
+ }
2638
+ /**
2639
+ * Determines the correct dispatching element from a potential input
2640
+ * @param dispatcher The event or element to get the dispatcher from
2641
+ */
2642
+ getDispatcher(dispatcher = null) {
2643
+ if (dispatcher instanceof Event) {
2644
+ return dispatcher.target;
2645
+ }
2646
+ else if (dispatcher instanceof Element) {
2647
+ return dispatcher;
2648
+ }
2649
+ else {
2650
+ return this.element;
2651
+ }
2652
+ }
2653
+ }
2654
+ BannerController.targets = ["banner"];
2655
+ /**
2656
+ * Helper to manually show an s-banner element via external JS
2657
+ * @param element the element the `data-controller="s-banner"` attribute is on
2658
+ */
2659
+ function showBanner(element) {
2660
+ toggleBanner(element, true);
2661
+ }
2662
+ /**
2663
+ * Helper to manually hide an s-banner element via external JS
2664
+ * @param element the element the `data-controller="s-banner"` attribute is on
2665
+ */
2666
+ function hideBanner(element) {
2667
+ toggleBanner(element, false);
2668
+ }
2669
+ /**
2670
+ * Helper to manually show an s-banner element via external JS
2671
+ * @param element the element the `data-controller="s-banner"` attribute is on
2672
+ * @param show whether to force show/hide the banner; toggles the banner if left undefined
2673
+ */
2674
+ function toggleBanner(element, show) {
2675
+ const controller = application.getControllerForElementAndIdentifier(element, "s-banner");
2676
+ if (!controller) {
2677
+ throw "Unable to get s-banner controller from element";
2678
+ }
2679
+ show ? controller.show() : controller.hide();
2680
+ }
2681
+
2682
+ ;// CONCATENATED MODULE: ./lib/components/expandable/expandable.ts
2575
2683
 
2576
2684
  // Radio buttons only trigger a change event when they're *checked*, but not when
2577
2685
  // they're *unchecked*. Therefore, if we have an active `s-expandable-control` in
@@ -2765,7 +2873,7 @@ class ExpandableController extends StacksController {
2765
2873
  }
2766
2874
  }
2767
2875
 
2768
- ;// CONCATENATED MODULE: ./lib/ts/controllers/s-modal.ts
2876
+ ;// CONCATENATED MODULE: ./lib/components/modal/modal.ts
2769
2877
 
2770
2878
  class ModalController extends StacksController {
2771
2879
  connect() {
@@ -3046,453 +3154,87 @@ function toggleModal(element, show) {
3046
3154
  show ? controller.show() : controller.hide();
3047
3155
  }
3048
3156
 
3049
- ;// CONCATENATED MODULE: ./lib/ts/controllers/s-banner.ts
3157
+ ;// CONCATENATED MODULE: ./lib/components/navigation/navigation.ts
3050
3158
 
3051
- class BannerController extends StacksController {
3052
- /**
3053
- * Toggles the visibility of the banner
3054
- */
3055
- toggle(dispatcher = null) {
3056
- this._toggle(undefined, dispatcher);
3159
+ class TabListController extends StacksController {
3160
+ connect() {
3161
+ super.connect();
3162
+ this.boundSelectTab = this.selectTab.bind(this);
3163
+ this.boundHandleKeydown = this.handleKeydown.bind(this);
3164
+ for (const tab of this.tabTargets) {
3165
+ tab.addEventListener("click", this.boundSelectTab);
3166
+ tab.addEventListener("keydown", this.boundHandleKeydown);
3167
+ }
3168
+ }
3169
+ disconnect() {
3170
+ super.disconnect();
3171
+ for (const tab of this.tabTargets) {
3172
+ tab.removeEventListener("click", this.boundSelectTab);
3173
+ tab.removeEventListener("keydown", this.boundHandleKeydown);
3174
+ }
3057
3175
  }
3058
3176
  /**
3059
- * Shows the banner
3177
+ * Gets all tabs within the controller.
3060
3178
  */
3061
- show(dispatcher = null) {
3062
- this._toggle(true, dispatcher);
3179
+ get tabTargets() {
3180
+ return Array.from(this.element.querySelectorAll("[role=tab]"));
3063
3181
  }
3064
3182
  /**
3065
- * Hides the banner
3183
+ * Handles click events on individual tabs, causing them to be selected.
3066
3184
  */
3067
- hide(dispatcher = null) {
3068
- this._toggle(false, dispatcher);
3185
+ selectTab(event) {
3186
+ this.switchToTab(event.currentTarget);
3069
3187
  }
3070
3188
  /**
3071
- * Toggles the visibility of the banner element
3072
- * @param show Optional parameter that force shows/hides the element or toggles it if left undefined
3189
+ * Handles left and right arrow keydown events on individual tabs,
3190
+ * selecting the adjacent tab corresponding to the event.
3073
3191
  */
3074
- _toggle(show, dispatcher = null) {
3075
- let toShow = show;
3076
- const isVisible = this.bannerTarget.getAttribute("aria-hidden") === "false";
3077
- // if we're letting the class toggle, we need to figure out if the banner is visible manually
3078
- if (typeof toShow === "undefined") {
3079
- toShow = !isVisible;
3192
+ handleKeydown(event) {
3193
+ var _a;
3194
+ let tabElement = event.currentTarget;
3195
+ const tabs = this.tabTargets;
3196
+ let tabIndex = tabs.indexOf(tabElement);
3197
+ if (event.key === "ArrowRight") {
3198
+ tabIndex++;
3080
3199
  }
3081
- // if the state matches the disired state, return without changing anything
3082
- if ((toShow && isVisible) || (!toShow && !isVisible)) {
3083
- return;
3200
+ else if (event.key === "ArrowLeft") {
3201
+ tabIndex--;
3084
3202
  }
3085
- const dispatchingElement = this.getDispatcher(dispatcher);
3086
- // show/hide events trigger before toggling the class
3087
- const triggeredEvent = this.triggerEvent(toShow ? "show" : "hide", {
3088
- dispatcher: this.getDispatcher(dispatchingElement),
3089
- }, this.bannerTarget);
3090
- // if this pre-show/hide event was prevented, don't attempt to continue changing the banner state
3091
- if (triggeredEvent.defaultPrevented) {
3203
+ else {
3092
3204
  return;
3093
3205
  }
3094
- this.bannerTarget.setAttribute("aria-hidden", toShow ? "false" : "true");
3095
- if (!toShow) {
3096
- this.removeBannerOnHide();
3206
+ // Use circular navigation when users go past the first or last tab.
3207
+ if (tabIndex < 0) {
3208
+ tabIndex = tabs.length - 1;
3097
3209
  }
3098
- this.triggerEvent(toShow ? "shown" : "hidden", {
3099
- dispatcher: dispatchingElement,
3100
- }, this.bannerTarget);
3101
- }
3102
- /**
3103
- * Remove the element on hide if the `remove-when-hidden` flag is set
3104
- */
3105
- removeBannerOnHide() {
3106
- if (this.data.get("remove-when-hidden") !== "true") {
3107
- return;
3210
+ if (tabIndex >= tabs.length) {
3211
+ tabIndex = 0;
3108
3212
  }
3109
- this.bannerTarget.addEventListener("s-banner:hidden", () => {
3110
- this.element.remove();
3111
- }, { once: true });
3213
+ tabElement = tabs[tabIndex];
3214
+ this.switchToTab(tabElement);
3215
+ // Focus the newly selected tab so it can receive keyboard events.
3216
+ (_a = this.selectedTab) === null || _a === void 0 ? void 0 : _a.focus();
3112
3217
  }
3113
3218
  /**
3114
- * Determines the correct dispatching element from a potential input
3115
- * @param dispatcher The event or element to get the dispatcher from
3219
+ * Attempts to switch to a new tab, doing nothing if the tab is already selected or
3220
+ * the s-navigation-tablist:select event is prevented.
3116
3221
  */
3117
- getDispatcher(dispatcher = null) {
3118
- if (dispatcher instanceof Event) {
3119
- return dispatcher.target;
3120
- }
3121
- else if (dispatcher instanceof Element) {
3122
- return dispatcher;
3222
+ switchToTab(newTab) {
3223
+ const oldTab = this.selectedTab;
3224
+ if (oldTab === newTab) {
3225
+ return;
3123
3226
  }
3124
- else {
3125
- return this.element;
3227
+ if (this.triggerEvent("select", { oldTab, newTab }).defaultPrevented) {
3228
+ return;
3126
3229
  }
3127
- }
3128
- }
3129
- BannerController.targets = ["banner"];
3130
- /**
3131
- * Helper to manually show an s-banner element via external JS
3132
- * @param element the element the `data-controller="s-banner"` attribute is on
3133
- */
3134
- function showBanner(element) {
3135
- toggleBanner(element, true);
3136
- }
3137
- /**
3138
- * Helper to manually hide an s-banner element via external JS
3139
- * @param element the element the `data-controller="s-banner"` attribute is on
3140
- */
3141
- function hideBanner(element) {
3142
- toggleBanner(element, false);
3143
- }
3144
- /**
3145
- * Helper to manually show an s-banner element via external JS
3146
- * @param element the element the `data-controller="s-banner"` attribute is on
3147
- * @param show whether to force show/hide the banner; toggles the banner if left undefined
3148
- */
3149
- function toggleBanner(element, show) {
3150
- const controller = application.getControllerForElementAndIdentifier(element, "s-banner");
3151
- if (!controller) {
3152
- throw "Unable to get s-banner controller from element";
3153
- }
3154
- show ? controller.show() : controller.hide();
3155
- }
3156
-
3157
- ;// CONCATENATED MODULE: ./lib/ts/controllers/s-toast.ts
3158
-
3159
- class ToastController extends StacksController {
3160
- connect() {
3161
- this.validate();
3230
+ this.selectedTab = newTab;
3231
+ this.triggerEvent("selected", { oldTab, newTab });
3162
3232
  }
3163
3233
  /**
3164
- * Disconnects all added event listeners on controller disconnect
3234
+ * Returns the currently selected tab or null if no tabs are selected.
3165
3235
  */
3166
- disconnect() {
3167
- this.unbindDocumentEvents();
3168
- }
3169
- /**
3170
- * Toggles the visibility of the toast
3171
- */
3172
- toggle(dispatcher = null) {
3173
- this._toggle(undefined, dispatcher);
3174
- }
3175
- /**
3176
- * Shows the toast
3177
- */
3178
- show(dispatcher = null) {
3179
- this._toggle(true, dispatcher);
3180
- }
3181
- /**
3182
- * Hides the toast
3183
- */
3184
- hide(dispatcher = null) {
3185
- this._toggle(false, dispatcher);
3186
- }
3187
- /**
3188
- * Validates the toast settings and attempts to set necessary internal variables
3189
- */
3190
- validate() {
3191
- // check for returnElement support
3192
- const returnElementSelector = this.data.get("return-element");
3193
- if (returnElementSelector) {
3194
- this.returnElement = (document.querySelector(returnElementSelector));
3195
- if (!this.returnElement) {
3196
- throw ("Unable to find element by return-element selector: " +
3197
- returnElementSelector);
3198
- }
3199
- }
3200
- }
3201
- /**
3202
- * Toggles the visibility of the toast element
3203
- * @param show Optional parameter that force shows/hides the element or toggles it if left undefined
3204
- */
3205
- _toggle(show, dispatcher = null) {
3206
- let toShow = show;
3207
- const isVisible = this.toastTarget.getAttribute("aria-hidden") === "false";
3208
- // if we're letting the class toggle, we need to figure out if the toast is visible manually
3209
- if (typeof toShow === "undefined") {
3210
- toShow = !isVisible;
3211
- }
3212
- // if the state matches the disired state, return without changing anything
3213
- if ((toShow && isVisible) || (!toShow && !isVisible)) {
3214
- return;
3215
- }
3216
- const dispatchingElement = this.getDispatcher(dispatcher);
3217
- // show/hide events trigger before toggling the class
3218
- const triggeredEvent = this.triggerEvent(toShow ? "show" : "hide", {
3219
- returnElement: this.returnElement,
3220
- dispatcher: this.getDispatcher(dispatchingElement),
3221
- }, this.toastTarget);
3222
- // if this pre-show/hide event was prevented, don't attempt to continue changing the toast state
3223
- if (triggeredEvent.defaultPrevented) {
3224
- return;
3225
- }
3226
- this.returnElement = triggeredEvent.detail.returnElement;
3227
- this.toastTarget.setAttribute("aria-hidden", toShow ? "false" : "true");
3228
- if (toShow) {
3229
- this.bindDocumentEvents();
3230
- this.hideAfterTimeout();
3231
- if (this.data.get("prevent-focus-capture") !== "true") {
3232
- this.focusInsideToast();
3233
- }
3234
- }
3235
- else {
3236
- this.unbindDocumentEvents();
3237
- this.focusReturnElement();
3238
- this.removeToastOnHide();
3239
- this.clearActiveTimeout();
3240
- }
3241
- // check for transitionend support
3242
- const supportsTransitionEnd = this.toastTarget.ontransitionend !== undefined;
3243
- // shown/hidden events trigger after toggling the class
3244
- if (supportsTransitionEnd) {
3245
- // wait until after the toast finishes transitioning to fire the event
3246
- this.toastTarget.addEventListener("transitionend", () => {
3247
- //TODO this is firing waaay to soon?
3248
- this.triggerEvent(toShow ? "shown" : "hidden", {
3249
- dispatcher: dispatchingElement,
3250
- }, this.toastTarget);
3251
- }, { once: true });
3252
- }
3253
- else {
3254
- this.triggerEvent(toShow ? "shown" : "hidden", {
3255
- dispatcher: dispatchingElement,
3256
- }, this.toastTarget);
3257
- }
3258
- }
3259
- /**
3260
- * Listens for the s-toast:hidden event and focuses the returnElement when it is fired
3261
- */
3262
- focusReturnElement() {
3263
- if (!this.returnElement) {
3264
- return;
3265
- }
3266
- this.toastTarget.addEventListener("s-toast:hidden", () => {
3267
- // double check the element still exists when the event is called
3268
- if (this.returnElement &&
3269
- document.body.contains(this.returnElement)) {
3270
- this.returnElement.focus();
3271
- }
3272
- }, { once: true });
3273
- }
3274
- /**
3275
- * Remove the element on hide if the `remove-when-hidden` flag is set
3276
- */
3277
- removeToastOnHide() {
3278
- if (this.data.get("remove-when-hidden") !== "true") {
3279
- return;
3280
- }
3281
- this.toastTarget.addEventListener("s-toast:hidden", () => {
3282
- this.element.remove();
3283
- }, { once: true });
3284
- }
3285
- /**
3286
- * Hide the element after a delay
3287
- */
3288
- hideAfterTimeout() {
3289
- if (this.data.get("prevent-auto-hide") === "true" ||
3290
- this.data.get("hide-after-timeout") === "0") {
3291
- return;
3292
- }
3293
- const timeout = parseInt(this.data.get("hide-after-timeout"), 10) || 3000;
3294
- this.activeTimeout = window.setTimeout(() => this.hide(), timeout);
3295
- }
3296
- /**
3297
- * Cancels the activeTimeout
3298
- */
3299
- clearActiveTimeout() {
3300
- clearTimeout(this.activeTimeout);
3301
- }
3302
- /**
3303
- * Gets all elements within the toast that could receive keyboard focus.
3304
- */
3305
- getAllTabbables() {
3306
- return Array.from(this.toastTarget.querySelectorAll("[href], input, select, textarea, button, [tabindex]")).filter((el) => el.matches(":not([disabled]):not([tabindex='-1'])"));
3307
- }
3308
- /**
3309
- * Returns the first visible element in an array or `undefined` if no elements are visible.
3310
- */
3311
- firstVisible(elements) {
3312
- // https://stackoverflow.com/a/21696585
3313
- return elements === null || elements === void 0 ? void 0 : elements.find((el) => el.offsetParent !== null);
3314
- }
3315
- /**
3316
- * Attempts to shift keyboard focus into the toast.
3317
- * If elements with `data-s-toast-target="initialFocus"` are present and visible, one of those will be selected.
3318
- * Otherwise, the first visible focusable element will receive focus.
3319
- */
3320
- focusInsideToast() {
3321
- this.toastTarget.addEventListener("s-toast:shown", () => {
3322
- var _a;
3323
- const initialFocus = (_a = this.firstVisible(this.initialFocusTargets)) !== null && _a !== void 0 ? _a : this.firstVisible(this.getAllTabbables());
3324
- initialFocus === null || initialFocus === void 0 ? void 0 : initialFocus.focus();
3325
- }, { once: true });
3326
- }
3327
- /**
3328
- * Binds global events to the document for hiding toasts on user interaction
3329
- */
3330
- bindDocumentEvents() {
3331
- // in order for removeEventListener to remove the right event, this bound function needs a constant reference
3332
- this._boundClickFn =
3333
- this._boundClickFn || this.hideOnOutsideClick.bind(this);
3334
- this._boundKeypressFn =
3335
- this._boundKeypressFn || this.hideOnEscapePress.bind(this);
3336
- document.addEventListener("mousedown", this._boundClickFn);
3337
- document.addEventListener("keyup", this._boundKeypressFn);
3338
- }
3339
- /**
3340
- * Unbinds global events to the document for hiding toasts on user interaction
3341
- */
3342
- unbindDocumentEvents() {
3343
- document.removeEventListener("mousedown", this._boundClickFn);
3344
- document.removeEventListener("keyup", this._boundKeypressFn);
3345
- }
3346
- /**
3347
- * Forces the toast to hide if a user clicks outside of it or its reference element
3348
- */
3349
- hideOnOutsideClick(e) {
3350
- var _a;
3351
- const target = e.target;
3352
- // check if the document was clicked inside either the toggle element or the toast itself
3353
- // note: .contains also returns true if the node itself matches the target element
3354
- if (!((_a = this.toastTarget) === null || _a === void 0 ? void 0 : _a.contains(target)) &&
3355
- document.body.contains(target) &&
3356
- this.data.get("hide-on-outside-click") !== "false") {
3357
- this._toggle(false, e);
3358
- }
3359
- }
3360
- /**
3361
- * Forces the toast to hide if the user presses escape while it, one of its childen, or the reference element are focused
3362
- */
3363
- hideOnEscapePress(e) {
3364
- // if the ESC key (27) wasn't pressed or if no toasts are showing, return
3365
- if (e.which !== 27 ||
3366
- this.toastTarget.getAttribute("aria-hidden") === "true") {
3367
- return;
3368
- }
3369
- this._toggle(false, e);
3370
- }
3371
- /**
3372
- * Determines the correct dispatching element from a potential input
3373
- * @param dispatcher The event or element to get the dispatcher from
3374
- */
3375
- getDispatcher(dispatcher = null) {
3376
- if (dispatcher instanceof Event) {
3377
- return dispatcher.target;
3378
- }
3379
- else if (dispatcher instanceof Element) {
3380
- return dispatcher;
3381
- }
3382
- else {
3383
- return this.element;
3384
- }
3385
- }
3386
- }
3387
- ToastController.targets = ["toast", "initialFocus"];
3388
- /**
3389
- * Helper to manually show an s-toast element via external JS
3390
- * @param element the element the `data-controller="s-toast"` attribute is on
3391
- */
3392
- function showToast(element) {
3393
- toggleToast(element, true);
3394
- }
3395
- /**
3396
- * Helper to manually hide an s-toast element via external JS
3397
- * @param element the element the `data-controller="s-toast"` attribute is on
3398
- */
3399
- function hideToast(element) {
3400
- toggleToast(element, false);
3401
- }
3402
- /**
3403
- * Helper to manually show an s-toast element via external JS
3404
- * @param element the element the `data-controller="s-toast"` attribute is on
3405
- * @param show whether to force show/hide the toast; toggles the toast if left undefined
3406
- */
3407
- function toggleToast(element, show) {
3408
- const controller = application.getControllerForElementAndIdentifier(element, "s-toast");
3409
- if (!controller) {
3410
- throw "Unable to get s-toast controller from element";
3411
- }
3412
- show ? controller.show() : controller.hide();
3413
- }
3414
-
3415
- ;// CONCATENATED MODULE: ./lib/ts/controllers/s-navigation-tablist.ts
3416
-
3417
- class TabListController extends StacksController {
3418
- connect() {
3419
- super.connect();
3420
- this.boundSelectTab = this.selectTab.bind(this);
3421
- this.boundHandleKeydown = this.handleKeydown.bind(this);
3422
- for (const tab of this.tabTargets) {
3423
- tab.addEventListener("click", this.boundSelectTab);
3424
- tab.addEventListener("keydown", this.boundHandleKeydown);
3425
- }
3426
- }
3427
- disconnect() {
3428
- super.disconnect();
3429
- for (const tab of this.tabTargets) {
3430
- tab.removeEventListener("click", this.boundSelectTab);
3431
- tab.removeEventListener("keydown", this.boundHandleKeydown);
3432
- }
3433
- }
3434
- /**
3435
- * Gets all tabs within the controller.
3436
- */
3437
- get tabTargets() {
3438
- return Array.from(this.element.querySelectorAll("[role=tab]"));
3439
- }
3440
- /**
3441
- * Handles click events on individual tabs, causing them to be selected.
3442
- */
3443
- selectTab(event) {
3444
- this.switchToTab(event.currentTarget);
3445
- }
3446
- /**
3447
- * Handles left and right arrow keydown events on individual tabs,
3448
- * selecting the adjacent tab corresponding to the event.
3449
- */
3450
- handleKeydown(event) {
3451
- var _a;
3452
- let tabElement = event.currentTarget;
3453
- const tabs = this.tabTargets;
3454
- let tabIndex = tabs.indexOf(tabElement);
3455
- if (event.key === "ArrowRight") {
3456
- tabIndex++;
3457
- }
3458
- else if (event.key === "ArrowLeft") {
3459
- tabIndex--;
3460
- }
3461
- else {
3462
- return;
3463
- }
3464
- // Use circular navigation when users go past the first or last tab.
3465
- if (tabIndex < 0) {
3466
- tabIndex = tabs.length - 1;
3467
- }
3468
- if (tabIndex >= tabs.length) {
3469
- tabIndex = 0;
3470
- }
3471
- tabElement = tabs[tabIndex];
3472
- this.switchToTab(tabElement);
3473
- // Focus the newly selected tab so it can receive keyboard events.
3474
- (_a = this.selectedTab) === null || _a === void 0 ? void 0 : _a.focus();
3475
- }
3476
- /**
3477
- * Attempts to switch to a new tab, doing nothing if the tab is already selected or
3478
- * the s-navigation-tablist:select event is prevented.
3479
- */
3480
- switchToTab(newTab) {
3481
- const oldTab = this.selectedTab;
3482
- if (oldTab === newTab) {
3483
- return;
3484
- }
3485
- if (this.triggerEvent("select", { oldTab, newTab }).defaultPrevented) {
3486
- return;
3487
- }
3488
- this.selectedTab = newTab;
3489
- this.triggerEvent("selected", { oldTab, newTab });
3490
- }
3491
- /**
3492
- * Returns the currently selected tab or null if no tabs are selected.
3493
- */
3494
- get selectedTab() {
3495
- return (this.tabTargets.find((e) => e.getAttribute("aria-selected") === "true") || null);
3236
+ get selectedTab() {
3237
+ return (this.tabTargets.find((e) => e.getAttribute("aria-selected") === "true") || null);
3496
3238
  }
3497
3239
  /**
3498
3240
  * Switches the tablist to the provided tab, updating the tabs and panels
@@ -5519,7 +5261,7 @@ var popper_createPopper = /*#__PURE__*/popperGenerator({
5519
5261
  // eslint-disable-next-line import/no-unused-modules
5520
5262
 
5521
5263
 
5522
- ;// CONCATENATED MODULE: ./lib/ts/controllers/s-popover.ts
5264
+ ;// CONCATENATED MODULE: ./lib/components/popover/popover.ts
5523
5265
 
5524
5266
 
5525
5267
  class BasePopoverController extends StacksController {
@@ -5998,7 +5740,7 @@ function toggleController(el, controllerName, include) {
5998
5740
  el.setAttribute("data-controller", Array.from(controllers).join(" "));
5999
5741
  }
6000
5742
 
6001
- ;// CONCATENATED MODULE: ./lib/ts/controllers/s-table.ts
5743
+ ;// CONCATENATED MODULE: ./lib/components/table/table.ts
6002
5744
 
6003
5745
  class TableController extends StacksController {
6004
5746
  setCurrentSort(headElem, direction) {
@@ -6090,132 +5832,390 @@ class TableController extends StacksController {
6090
5832
  : parseInt(tuple[0], 10);
6091
5833
  });
6092
5834
  }
6093
- // We don't sort an array of <tr>, but instead an arrays of row *numbers*, because this way we
6094
- // can enforce stable sorting, i.e. rows that compare equal are guaranteed to remain in the same
6095
- // order (the JS standard does not gurantee this for sort()).
6096
- data.sort(function (a, b) {
6097
- // first compare the values (a[0])
6098
- if (a[0] > b[0]) {
6099
- return 1 * direction;
6100
- }
6101
- else if (a[0] < b[0]) {
6102
- return -1 * direction;
6103
- }
6104
- else {
6105
- // if the values are equal, compare the row numbers (a[1]) to guarantee stable sorting
6106
- // (note that this comparison is independent of the sorting direction)
6107
- return a[1] > b[1] ? 1 : -1;
5835
+ // We don't sort an array of <tr>, but instead an arrays of row *numbers*, because this way we
5836
+ // can enforce stable sorting, i.e. rows that compare equal are guaranteed to remain in the same
5837
+ // order (the JS standard does not gurantee this for sort()).
5838
+ data.sort(function (a, b) {
5839
+ // first compare the values (a[0])
5840
+ if (a[0] > b[0]) {
5841
+ return 1 * direction;
5842
+ }
5843
+ else if (a[0] < b[0]) {
5844
+ return -1 * direction;
5845
+ }
5846
+ else {
5847
+ // if the values are equal, compare the row numbers (a[1]) to guarantee stable sorting
5848
+ // (note that this comparison is independent of the sorting direction)
5849
+ return a[1] > b[1] ? 1 : -1;
5850
+ }
5851
+ });
5852
+ // this is the actual reordering of the table rows
5853
+ data.forEach(function (tup) {
5854
+ var _a;
5855
+ const row = rows[tup[1]];
5856
+ (_a = row.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(row);
5857
+ if (firstBottomRow) {
5858
+ tbody.insertBefore(row, firstBottomRow);
5859
+ }
5860
+ else {
5861
+ tbody.appendChild(row);
5862
+ }
5863
+ });
5864
+ // update the UI and set the `data-sort-direction` attribute if appropriate, so that the next click
5865
+ // will cause sorting in descending direction
5866
+ this.setCurrentSort(colHead, direction === 1 ? "asc" : "desc");
5867
+ }
5868
+ }
5869
+ TableController.targets = ["column"];
5870
+ function buildIndex(section) {
5871
+ const result = buildIndexOrGetCellSlot(section);
5872
+ if (!(result instanceof Array)) {
5873
+ throw "shouldn't happen";
5874
+ }
5875
+ return result;
5876
+ }
5877
+ function getCellSlot(cell) {
5878
+ if (!(cell.parentElement &&
5879
+ cell.parentElement.parentElement instanceof HTMLTableSectionElement)) {
5880
+ throw "invalid table";
5881
+ }
5882
+ const result = buildIndexOrGetCellSlot(cell.parentElement.parentElement, cell);
5883
+ if (typeof result !== "number") {
5884
+ throw "shouldn't happen";
5885
+ }
5886
+ return result;
5887
+ }
5888
+ // Just because a <td> is the 4th *child* of its <tr> doesn't mean it belongs to the 4th *column*
5889
+ // of the table. Previous cells may have a colspan; cells in previous rows may have a rowspan.
5890
+ // Because we need to know which header cells and data cells belong together, we have to 1) find out
5891
+ // which column number (or "slot" as we call it here) the header cell has, and 2) for each row find
5892
+ // out which <td> cell corresponds to this slot (because those are the rows we're sorting by).
5893
+ //
5894
+ // That's what the following function does. If the second argument is not given, it returns an index
5895
+ // of the table, which is an array of arrays. Each of the sub-arrays corresponds to a table row. The
5896
+ // indices of the sub-array correspond to column slots; the values are the actual table cell elements.
5897
+ // For example index[4][3] is the <td> or <th> in row 4, column 3 of the table section (<tbody> or <thead>).
5898
+ // Note that this element is not necessarily even in the 4th (zero-based) <tr> -- if it has a rowSpan > 1,
5899
+ // it may also be in a previous <tr>.
5900
+ //
5901
+ // If the second argument is given, it's a <td> or <th> that we're trying to find, and the algorithm
5902
+ // stops as soon as it has found it and the function returns its slot number.
5903
+ function buildIndexOrGetCellSlot(section, findCell) {
5904
+ const index = [];
5905
+ let curRow = section.children[0];
5906
+ // the elements of these two arrays are synchronized; the first array contains table cell elements,
5907
+ // the second one contains a number that indicates for how many more rows this elements will
5908
+ // exist (i.e. the value is initially one less than the cell's rowspan, and will be decreased for each row)
5909
+ const growing = [];
5910
+ const growingRowsLeft = [];
5911
+ // continue while we have actual <tr>'s left *or* we still have rowspan'ed elements that aren't done
5912
+ while (curRow ||
5913
+ growingRowsLeft.some(function (e) {
5914
+ return e !== 0;
5915
+ })) {
5916
+ const curIndexRow = [];
5917
+ index.push(curIndexRow);
5918
+ let curSlot = 0;
5919
+ if (curRow) {
5920
+ for (let curCellInd = 0; curCellInd < curRow.children.length; curCellInd++) {
5921
+ while (growingRowsLeft[curSlot]) {
5922
+ growingRowsLeft[curSlot]--;
5923
+ curIndexRow[curSlot] = growing[curSlot];
5924
+ curSlot++;
5925
+ }
5926
+ const cell = curRow.children[curCellInd];
5927
+ if (!(cell instanceof HTMLTableCellElement)) {
5928
+ throw "invalid table";
5929
+ }
5930
+ if (getComputedStyle(cell).display === "none") {
5931
+ continue;
5932
+ }
5933
+ if (cell === findCell) {
5934
+ return curSlot;
5935
+ }
5936
+ const nextFreeSlot = curSlot + cell.colSpan;
5937
+ for (; curSlot < nextFreeSlot; curSlot++) {
5938
+ growingRowsLeft[curSlot] = cell.rowSpan - 1; // if any of these is already growing, the table is broken -- no guarantees of anything
5939
+ growing[curSlot] = cell;
5940
+ curIndexRow[curSlot] = cell;
5941
+ }
5942
+ }
5943
+ }
5944
+ while (curSlot < growing.length) {
5945
+ if (growingRowsLeft[curSlot]) {
5946
+ growingRowsLeft[curSlot]--;
5947
+ curIndexRow[curSlot] = growing[curSlot];
5948
+ }
5949
+ curSlot++;
5950
+ }
5951
+ if (curRow) {
5952
+ curRow = curRow.nextElementSibling;
5953
+ }
5954
+ }
5955
+ return findCell
5956
+ ? -1
5957
+ : index; /* if findCell was given but we end up here, that means it isn't in this section */
5958
+ }
5959
+
5960
+ ;// CONCATENATED MODULE: ./lib/components/toast/toast.ts
5961
+
5962
+ class ToastController extends StacksController {
5963
+ connect() {
5964
+ this.validate();
5965
+ }
5966
+ /**
5967
+ * Disconnects all added event listeners on controller disconnect
5968
+ */
5969
+ disconnect() {
5970
+ this.unbindDocumentEvents();
5971
+ }
5972
+ /**
5973
+ * Toggles the visibility of the toast
5974
+ */
5975
+ toggle(dispatcher = null) {
5976
+ this._toggle(undefined, dispatcher);
5977
+ }
5978
+ /**
5979
+ * Shows the toast
5980
+ */
5981
+ show(dispatcher = null) {
5982
+ this._toggle(true, dispatcher);
5983
+ }
5984
+ /**
5985
+ * Hides the toast
5986
+ */
5987
+ hide(dispatcher = null) {
5988
+ this._toggle(false, dispatcher);
5989
+ }
5990
+ /**
5991
+ * Validates the toast settings and attempts to set necessary internal variables
5992
+ */
5993
+ validate() {
5994
+ // check for returnElement support
5995
+ const returnElementSelector = this.data.get("return-element");
5996
+ if (returnElementSelector) {
5997
+ this.returnElement = (document.querySelector(returnElementSelector));
5998
+ if (!this.returnElement) {
5999
+ throw ("Unable to find element by return-element selector: " +
6000
+ returnElementSelector);
6001
+ }
6002
+ }
6003
+ }
6004
+ /**
6005
+ * Toggles the visibility of the toast element
6006
+ * @param show Optional parameter that force shows/hides the element or toggles it if left undefined
6007
+ */
6008
+ _toggle(show, dispatcher = null) {
6009
+ let toShow = show;
6010
+ const isVisible = this.toastTarget.getAttribute("aria-hidden") === "false";
6011
+ // if we're letting the class toggle, we need to figure out if the toast is visible manually
6012
+ if (typeof toShow === "undefined") {
6013
+ toShow = !isVisible;
6014
+ }
6015
+ // if the state matches the disired state, return without changing anything
6016
+ if ((toShow && isVisible) || (!toShow && !isVisible)) {
6017
+ return;
6018
+ }
6019
+ const dispatchingElement = this.getDispatcher(dispatcher);
6020
+ // show/hide events trigger before toggling the class
6021
+ const triggeredEvent = this.triggerEvent(toShow ? "show" : "hide", {
6022
+ returnElement: this.returnElement,
6023
+ dispatcher: this.getDispatcher(dispatchingElement),
6024
+ }, this.toastTarget);
6025
+ // if this pre-show/hide event was prevented, don't attempt to continue changing the toast state
6026
+ if (triggeredEvent.defaultPrevented) {
6027
+ return;
6028
+ }
6029
+ this.returnElement = triggeredEvent.detail.returnElement;
6030
+ this.toastTarget.setAttribute("aria-hidden", toShow ? "false" : "true");
6031
+ if (toShow) {
6032
+ this.bindDocumentEvents();
6033
+ this.hideAfterTimeout();
6034
+ if (this.data.get("prevent-focus-capture") !== "true") {
6035
+ this.focusInsideToast();
6036
+ }
6037
+ }
6038
+ else {
6039
+ this.unbindDocumentEvents();
6040
+ this.focusReturnElement();
6041
+ this.removeToastOnHide();
6042
+ this.clearActiveTimeout();
6043
+ }
6044
+ // check for transitionend support
6045
+ const supportsTransitionEnd = this.toastTarget.ontransitionend !== undefined;
6046
+ // shown/hidden events trigger after toggling the class
6047
+ if (supportsTransitionEnd) {
6048
+ // wait until after the toast finishes transitioning to fire the event
6049
+ this.toastTarget.addEventListener("transitionend", () => {
6050
+ //TODO this is firing waaay to soon?
6051
+ this.triggerEvent(toShow ? "shown" : "hidden", {
6052
+ dispatcher: dispatchingElement,
6053
+ }, this.toastTarget);
6054
+ }, { once: true });
6055
+ }
6056
+ else {
6057
+ this.triggerEvent(toShow ? "shown" : "hidden", {
6058
+ dispatcher: dispatchingElement,
6059
+ }, this.toastTarget);
6060
+ }
6061
+ }
6062
+ /**
6063
+ * Listens for the s-toast:hidden event and focuses the returnElement when it is fired
6064
+ */
6065
+ focusReturnElement() {
6066
+ if (!this.returnElement) {
6067
+ return;
6068
+ }
6069
+ this.toastTarget.addEventListener("s-toast:hidden", () => {
6070
+ // double check the element still exists when the event is called
6071
+ if (this.returnElement &&
6072
+ document.body.contains(this.returnElement)) {
6073
+ this.returnElement.focus();
6108
6074
  }
6109
- });
6110
- // this is the actual reordering of the table rows
6111
- data.forEach(function (tup) {
6075
+ }, { once: true });
6076
+ }
6077
+ /**
6078
+ * Remove the element on hide if the `remove-when-hidden` flag is set
6079
+ */
6080
+ removeToastOnHide() {
6081
+ if (this.data.get("remove-when-hidden") !== "true") {
6082
+ return;
6083
+ }
6084
+ this.toastTarget.addEventListener("s-toast:hidden", () => {
6085
+ this.element.remove();
6086
+ }, { once: true });
6087
+ }
6088
+ /**
6089
+ * Hide the element after a delay
6090
+ */
6091
+ hideAfterTimeout() {
6092
+ if (this.data.get("prevent-auto-hide") === "true" ||
6093
+ this.data.get("hide-after-timeout") === "0") {
6094
+ return;
6095
+ }
6096
+ const timeout = parseInt(this.data.get("hide-after-timeout"), 10) || 3000;
6097
+ this.activeTimeout = window.setTimeout(() => this.hide(), timeout);
6098
+ }
6099
+ /**
6100
+ * Cancels the activeTimeout
6101
+ */
6102
+ clearActiveTimeout() {
6103
+ clearTimeout(this.activeTimeout);
6104
+ }
6105
+ /**
6106
+ * Gets all elements within the toast that could receive keyboard focus.
6107
+ */
6108
+ getAllTabbables() {
6109
+ return Array.from(this.toastTarget.querySelectorAll("[href], input, select, textarea, button, [tabindex]")).filter((el) => el.matches(":not([disabled]):not([tabindex='-1'])"));
6110
+ }
6111
+ /**
6112
+ * Returns the first visible element in an array or `undefined` if no elements are visible.
6113
+ */
6114
+ firstVisible(elements) {
6115
+ // https://stackoverflow.com/a/21696585
6116
+ return elements === null || elements === void 0 ? void 0 : elements.find((el) => el.offsetParent !== null);
6117
+ }
6118
+ /**
6119
+ * Attempts to shift keyboard focus into the toast.
6120
+ * If elements with `data-s-toast-target="initialFocus"` are present and visible, one of those will be selected.
6121
+ * Otherwise, the first visible focusable element will receive focus.
6122
+ */
6123
+ focusInsideToast() {
6124
+ this.toastTarget.addEventListener("s-toast:shown", () => {
6112
6125
  var _a;
6113
- const row = rows[tup[1]];
6114
- (_a = row.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(row);
6115
- if (firstBottomRow) {
6116
- tbody.insertBefore(row, firstBottomRow);
6117
- }
6118
- else {
6119
- tbody.appendChild(row);
6120
- }
6121
- });
6122
- // update the UI and set the `data-sort-direction` attribute if appropriate, so that the next click
6123
- // will cause sorting in descending direction
6124
- this.setCurrentSort(colHead, direction === 1 ? "asc" : "desc");
6126
+ const initialFocus = (_a = this.firstVisible(this.initialFocusTargets)) !== null && _a !== void 0 ? _a : this.firstVisible(this.getAllTabbables());
6127
+ initialFocus === null || initialFocus === void 0 ? void 0 : initialFocus.focus();
6128
+ }, { once: true });
6125
6129
  }
6126
- }
6127
- TableController.targets = ["column"];
6128
- function buildIndex(section) {
6129
- const result = buildIndexOrGetCellSlot(section);
6130
- if (!(result instanceof Array)) {
6131
- throw "shouldn't happen";
6130
+ /**
6131
+ * Binds global events to the document for hiding toasts on user interaction
6132
+ */
6133
+ bindDocumentEvents() {
6134
+ // in order for removeEventListener to remove the right event, this bound function needs a constant reference
6135
+ this._boundClickFn =
6136
+ this._boundClickFn || this.hideOnOutsideClick.bind(this);
6137
+ this._boundKeypressFn =
6138
+ this._boundKeypressFn || this.hideOnEscapePress.bind(this);
6139
+ document.addEventListener("mousedown", this._boundClickFn);
6140
+ document.addEventListener("keyup", this._boundKeypressFn);
6132
6141
  }
6133
- return result;
6134
- }
6135
- function getCellSlot(cell) {
6136
- if (!(cell.parentElement &&
6137
- cell.parentElement.parentElement instanceof HTMLTableSectionElement)) {
6138
- throw "invalid table";
6142
+ /**
6143
+ * Unbinds global events to the document for hiding toasts on user interaction
6144
+ */
6145
+ unbindDocumentEvents() {
6146
+ document.removeEventListener("mousedown", this._boundClickFn);
6147
+ document.removeEventListener("keyup", this._boundKeypressFn);
6139
6148
  }
6140
- const result = buildIndexOrGetCellSlot(cell.parentElement.parentElement, cell);
6141
- if (typeof result !== "number") {
6142
- throw "shouldn't happen";
6149
+ /**
6150
+ * Forces the toast to hide if a user clicks outside of it or its reference element
6151
+ */
6152
+ hideOnOutsideClick(e) {
6153
+ var _a;
6154
+ const target = e.target;
6155
+ // check if the document was clicked inside either the toggle element or the toast itself
6156
+ // note: .contains also returns true if the node itself matches the target element
6157
+ if (!((_a = this.toastTarget) === null || _a === void 0 ? void 0 : _a.contains(target)) &&
6158
+ document.body.contains(target) &&
6159
+ this.data.get("hide-on-outside-click") !== "false") {
6160
+ this._toggle(false, e);
6161
+ }
6143
6162
  }
6144
- return result;
6145
- }
6146
- // Just because a <td> is the 4th *child* of its <tr> doesn't mean it belongs to the 4th *column*
6147
- // of the table. Previous cells may have a colspan; cells in previous rows may have a rowspan.
6148
- // Because we need to know which header cells and data cells belong together, we have to 1) find out
6149
- // which column number (or "slot" as we call it here) the header cell has, and 2) for each row find
6150
- // out which <td> cell corresponds to this slot (because those are the rows we're sorting by).
6151
- //
6152
- // That's what the following function does. If the second argument is not given, it returns an index
6153
- // of the table, which is an array of arrays. Each of the sub-arrays corresponds to a table row. The
6154
- // indices of the sub-array correspond to column slots; the values are the actual table cell elements.
6155
- // For example index[4][3] is the <td> or <th> in row 4, column 3 of the table section (<tbody> or <thead>).
6156
- // Note that this element is not necessarily even in the 4th (zero-based) <tr> -- if it has a rowSpan > 1,
6157
- // it may also be in a previous <tr>.
6158
- //
6159
- // If the second argument is given, it's a <td> or <th> that we're trying to find, and the algorithm
6160
- // stops as soon as it has found it and the function returns its slot number.
6161
- function buildIndexOrGetCellSlot(section, findCell) {
6162
- const index = [];
6163
- let curRow = section.children[0];
6164
- // the elements of these two arrays are synchronized; the first array contains table cell elements,
6165
- // the second one contains a number that indicates for how many more rows this elements will
6166
- // exist (i.e. the value is initially one less than the cell's rowspan, and will be decreased for each row)
6167
- const growing = [];
6168
- const growingRowsLeft = [];
6169
- // continue while we have actual <tr>'s left *or* we still have rowspan'ed elements that aren't done
6170
- while (curRow ||
6171
- growingRowsLeft.some(function (e) {
6172
- return e !== 0;
6173
- })) {
6174
- const curIndexRow = [];
6175
- index.push(curIndexRow);
6176
- let curSlot = 0;
6177
- if (curRow) {
6178
- for (let curCellInd = 0; curCellInd < curRow.children.length; curCellInd++) {
6179
- while (growingRowsLeft[curSlot]) {
6180
- growingRowsLeft[curSlot]--;
6181
- curIndexRow[curSlot] = growing[curSlot];
6182
- curSlot++;
6183
- }
6184
- const cell = curRow.children[curCellInd];
6185
- if (!(cell instanceof HTMLTableCellElement)) {
6186
- throw "invalid table";
6187
- }
6188
- if (getComputedStyle(cell).display === "none") {
6189
- continue;
6190
- }
6191
- if (cell === findCell) {
6192
- return curSlot;
6193
- }
6194
- const nextFreeSlot = curSlot + cell.colSpan;
6195
- for (; curSlot < nextFreeSlot; curSlot++) {
6196
- growingRowsLeft[curSlot] = cell.rowSpan - 1; // if any of these is already growing, the table is broken -- no guarantees of anything
6197
- growing[curSlot] = cell;
6198
- curIndexRow[curSlot] = cell;
6199
- }
6200
- }
6163
+ /**
6164
+ * Forces the toast to hide if the user presses escape while it, one of its childen, or the reference element are focused
6165
+ */
6166
+ hideOnEscapePress(e) {
6167
+ // if the ESC key (27) wasn't pressed or if no toasts are showing, return
6168
+ if (e.which !== 27 ||
6169
+ this.toastTarget.getAttribute("aria-hidden") === "true") {
6170
+ return;
6201
6171
  }
6202
- while (curSlot < growing.length) {
6203
- if (growingRowsLeft[curSlot]) {
6204
- growingRowsLeft[curSlot]--;
6205
- curIndexRow[curSlot] = growing[curSlot];
6206
- }
6207
- curSlot++;
6172
+ this._toggle(false, e);
6173
+ }
6174
+ /**
6175
+ * Determines the correct dispatching element from a potential input
6176
+ * @param dispatcher The event or element to get the dispatcher from
6177
+ */
6178
+ getDispatcher(dispatcher = null) {
6179
+ if (dispatcher instanceof Event) {
6180
+ return dispatcher.target;
6208
6181
  }
6209
- if (curRow) {
6210
- curRow = curRow.nextElementSibling;
6182
+ else if (dispatcher instanceof Element) {
6183
+ return dispatcher;
6184
+ }
6185
+ else {
6186
+ return this.element;
6211
6187
  }
6212
6188
  }
6213
- return findCell
6214
- ? -1
6215
- : index; /* if findCell was given but we end up here, that means it isn't in this section */
6189
+ }
6190
+ ToastController.targets = ["toast", "initialFocus"];
6191
+ /**
6192
+ * Helper to manually show an s-toast element via external JS
6193
+ * @param element the element the `data-controller="s-toast"` attribute is on
6194
+ */
6195
+ function showToast(element) {
6196
+ toggleToast(element, true);
6197
+ }
6198
+ /**
6199
+ * Helper to manually hide an s-toast element via external JS
6200
+ * @param element the element the `data-controller="s-toast"` attribute is on
6201
+ */
6202
+ function hideToast(element) {
6203
+ toggleToast(element, false);
6204
+ }
6205
+ /**
6206
+ * Helper to manually show an s-toast element via external JS
6207
+ * @param element the element the `data-controller="s-toast"` attribute is on
6208
+ * @param show whether to force show/hide the toast; toggles the toast if left undefined
6209
+ */
6210
+ function toggleToast(element, show) {
6211
+ const controller = application.getControllerForElementAndIdentifier(element, "s-toast");
6212
+ if (!controller) {
6213
+ throw "Unable to get s-toast controller from element";
6214
+ }
6215
+ show ? controller.show() : controller.hide();
6216
6216
  }
6217
6217
 
6218
- ;// CONCATENATED MODULE: ./lib/ts/controllers/s-tooltip.ts
6218
+ ;// CONCATENATED MODULE: ./lib/components/popover/tooltip.ts
6219
6219
 
6220
6220
 
6221
6221
  class TooltipController extends BasePopoverController {
@@ -6467,7 +6467,7 @@ function applyOptionsAndTitleAttributes(element, options) {
6467
6467
  }
6468
6468
  }
6469
6469
 
6470
- ;// CONCATENATED MODULE: ./lib/ts/controllers/s-uploader.ts
6470
+ ;// CONCATENATED MODULE: ./lib/components/uploader/uploader.ts
6471
6471
 
6472
6472
  class UploaderController extends StacksController {
6473
6473
  connect() {
@@ -6631,7 +6631,7 @@ UploaderController.targets = ["input", "previews", "uploader"];
6631
6631
  UploaderController.FILE_DISPLAY_LIMIT = 10;
6632
6632
  UploaderController.MAX_FILE_SIZE = 1024 * 1024 * 10; // 10 MB
6633
6633
 
6634
- ;// CONCATENATED MODULE: ./lib/ts/controllers/index.ts
6634
+ ;// CONCATENATED MODULE: ./lib/controllers.ts
6635
6635
  // export all controllers *with helpers* so they can be bulk re-exported by the package entry point
6636
6636
 
6637
6637
 
@@ -6643,7 +6643,7 @@ UploaderController.MAX_FILE_SIZE = 1024 * 1024 * 10; // 10 MB
6643
6643
 
6644
6644
 
6645
6645
 
6646
- ;// CONCATENATED MODULE: ./lib/ts/index.ts
6646
+ ;// CONCATENATED MODULE: ./lib/index.ts
6647
6647
 
6648
6648
 
6649
6649