@ulu/frontend-vue 0.2.0-beta.8 → 0.3.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 (174) hide show
  1. package/dist/components/collapsible/UluAccordionGroup.vue.d.ts +2 -2
  2. package/dist/components/collapsible/UluAccordionGroup.vue.d.ts.map +1 -1
  3. package/dist/components/collapsible/UluAccordionGroup.vue.js +22 -19
  4. package/dist/components/collapsible/UluDropdown.vue.d.ts +1 -1
  5. package/dist/components/collapsible/UluDropdown.vue.d.ts.map +1 -1
  6. package/dist/components/collapsible/UluDropdown.vue.js +22 -15
  7. package/dist/components/collapsible/UluModal.vue.d.ts +43 -248
  8. package/dist/components/collapsible/UluModal.vue.d.ts.map +1 -1
  9. package/dist/components/collapsible/UluModal.vue.js +139 -191
  10. package/dist/components/collapsible/UluTabGroup.vue.d.ts +2 -0
  11. package/dist/components/collapsible/UluTabGroup.vue.d.ts.map +1 -1
  12. package/dist/components/collapsible/UluTabGroup.vue.js +23 -14
  13. package/dist/components/elements/UluAlert.vue.d.ts +29 -144
  14. package/dist/components/elements/UluAlert.vue.d.ts.map +1 -1
  15. package/dist/components/elements/UluAlert.vue.js +39 -50
  16. package/dist/components/elements/UluBadge.vue.d.ts +6 -6
  17. package/dist/components/elements/UluBadgeStack.vue.d.ts +1 -1
  18. package/dist/components/elements/UluBadgeStack.vue.d.ts.map +1 -1
  19. package/dist/components/elements/UluBadgeStack.vue.js +12 -9
  20. package/dist/components/elements/UluButton.vue.d.ts +47 -177
  21. package/dist/components/elements/UluButton.vue.d.ts.map +1 -1
  22. package/dist/components/elements/UluButton.vue.js +59 -72
  23. package/dist/components/elements/UluButtonVerbose.vue.d.ts +38 -123
  24. package/dist/components/elements/UluButtonVerbose.vue.d.ts.map +1 -1
  25. package/dist/components/elements/UluButtonVerbose.vue.js +52 -65
  26. package/dist/components/elements/UluCallout.vue.d.ts +20 -25
  27. package/dist/components/elements/UluCallout.vue.d.ts.map +1 -1
  28. package/dist/components/elements/UluCallout.vue.js +11 -16
  29. package/dist/components/elements/UluCaptionedFigure.vue.d.ts +25 -0
  30. package/dist/components/elements/UluCaptionedFigure.vue.d.ts.map +1 -0
  31. package/dist/components/elements/UluCaptionedFigure.vue.js +48 -0
  32. package/dist/components/elements/UluCard.vue.d.ts +2 -2
  33. package/dist/components/elements/UluDefinitionList.vue.d.ts +4 -2
  34. package/dist/components/elements/UluDefinitionList.vue.d.ts.map +1 -1
  35. package/dist/components/elements/UluDefinitionList.vue.js +32 -28
  36. package/dist/components/elements/UluExternalLink.vue.d.ts +2 -2
  37. package/dist/components/elements/UluImage.vue.d.ts +14 -0
  38. package/dist/components/elements/UluImage.vue.d.ts.map +1 -0
  39. package/dist/components/elements/UluImage.vue.js +53 -0
  40. package/dist/components/elements/UluList.vue.d.ts.map +1 -1
  41. package/dist/components/elements/UluList.vue.js +14 -13
  42. package/dist/components/elements/UluOverflowScroller.vue.d.ts +49 -0
  43. package/dist/components/elements/UluOverflowScroller.vue.d.ts.map +1 -0
  44. package/dist/components/elements/UluOverflowScroller.vue.js +138 -0
  45. package/dist/components/elements/UluScrollSlider.vue.d.ts +38 -0
  46. package/dist/components/elements/UluScrollSlider.vue.d.ts.map +1 -0
  47. package/dist/components/elements/UluScrollSlider.vue.js +146 -0
  48. package/dist/components/elements/UluSlider.vue.d.ts +57 -0
  49. package/dist/components/elements/UluSlider.vue.d.ts.map +1 -0
  50. package/dist/components/elements/UluSlider.vue.js +277 -0
  51. package/dist/components/forms/UluFormFile.vue.d.ts +2 -2
  52. package/dist/components/forms/UluFormRadio.vue.d.ts +4 -4
  53. package/dist/components/index.d.ts +6 -0
  54. package/dist/components/layout/UluTitleRail.vue.d.ts +29 -87
  55. package/dist/components/layout/UluTitleRail.vue.d.ts.map +1 -1
  56. package/dist/components/layout/UluTitleRail.vue.js +51 -46
  57. package/dist/components/navigation/UluBreadcrumb.vue.d.ts +27 -68
  58. package/dist/components/navigation/UluBreadcrumb.vue.d.ts.map +1 -1
  59. package/dist/components/navigation/UluBreadcrumb.vue.js +51 -54
  60. package/dist/components/navigation/UluMenu.vue.d.ts +30 -138
  61. package/dist/components/navigation/UluMenu.vue.d.ts.map +1 -1
  62. package/dist/components/navigation/UluMenu.vue.js +85 -84
  63. package/dist/components/navigation/UluMenuStack.vue.d.ts +12 -2
  64. package/dist/components/navigation/UluMenuStack.vue.d.ts.map +1 -1
  65. package/dist/components/navigation/UluMenuStack.vue.js +26 -18
  66. package/dist/components/navigation/UluNavStrip.vue.d.ts +22 -134
  67. package/dist/components/navigation/UluNavStrip.vue.d.ts.map +1 -1
  68. package/dist/components/navigation/UluNavStrip.vue.js +43 -31
  69. package/dist/components/systems/facets/UluFacetsSidebarLayout.vue.js +10 -10
  70. package/dist/components/systems/facets/useFacets.d.ts +3 -0
  71. package/dist/components/systems/facets/useFacets.d.ts.map +1 -1
  72. package/dist/components/systems/facets/useFacets.js +124 -112
  73. package/dist/components/systems/index.d.ts +0 -3
  74. package/dist/components/systems/scroll-anchors/UluScrollAnchors.vue.d.ts +2 -2
  75. package/dist/components/systems/table-sticky/UluTableSticky.vue.d.ts +504 -432
  76. package/dist/components/systems/table-sticky/UluTableSticky.vue.d.ts.map +1 -1
  77. package/dist/components/systems/table-sticky/UluTableSticky.vue.js +313 -456
  78. package/dist/components/systems/table-sticky/UluTableStickyRows.vue.d.ts +40 -31
  79. package/dist/components/systems/table-sticky/UluTableStickyRows.vue.d.ts.map +1 -1
  80. package/dist/components/systems/table-sticky/UluTableStickyRows.vue.js +43 -45
  81. package/dist/components/systems/table-sticky/UluTableStickyTable.vue.d.ts +60 -146
  82. package/dist/components/systems/table-sticky/UluTableStickyTable.vue.d.ts.map +1 -1
  83. package/dist/components/systems/table-sticky/UluTableStickyTable.vue.js +156 -175
  84. package/dist/components/utils/UluAction.vue.d.ts +36 -0
  85. package/dist/components/utils/UluAction.vue.d.ts.map +1 -0
  86. package/dist/components/utils/UluAction.vue.js +59 -0
  87. package/dist/components/utils/UluConditionalText.vue.d.ts +7 -26
  88. package/dist/components/utils/UluConditionalText.vue.d.ts.map +1 -1
  89. package/dist/components/utils/UluConditionalText.vue.js +12 -14
  90. package/dist/components/utils/UluConditionalWrapper.vue.d.ts.map +1 -1
  91. package/dist/components/utils/UluConditionalWrapper.vue.js +11 -9
  92. package/dist/components/utils/UluPlaceholderImage.vue.d.ts +12 -57
  93. package/dist/components/utils/UluPlaceholderImage.vue.d.ts.map +1 -1
  94. package/dist/components/utils/UluPlaceholderImage.vue.js +18 -26
  95. package/dist/components/utils/UluPlaceholderText.vue.d.ts +6 -20
  96. package/dist/components/utils/UluPlaceholderText.vue.js +12 -14
  97. package/dist/components/utils/UluRouteAnnouncer.vue.d.ts +9 -58
  98. package/dist/components/utils/UluRouteAnnouncer.vue.d.ts.map +1 -1
  99. package/dist/components/utils/UluRouteAnnouncer.vue.js +28 -28
  100. package/dist/components/visualizations/UluAnimateNumber.vue.d.ts +20 -14
  101. package/dist/components/visualizations/UluAnimateNumber.vue.d.ts.map +1 -1
  102. package/dist/components/visualizations/UluAnimateNumber.vue.js +18 -26
  103. package/dist/components/visualizations/UluProgressCircle.vue.d.ts +2 -2
  104. package/dist/composables/useModifiers.d.ts +20 -25
  105. package/dist/composables/useModifiers.d.ts.map +1 -1
  106. package/dist/index.js +206 -200
  107. package/dist/plugins/modals/UluModalsDisplay.vue.d.ts +3 -12
  108. package/dist/plugins/modals/UluModalsDisplay.vue.js +24 -45
  109. package/dist/plugins/modals/index.js +6 -6
  110. package/dist/plugins/toast/UluToast.vue.d.ts +24 -49
  111. package/dist/plugins/toast/UluToast.vue.d.ts.map +1 -1
  112. package/dist/plugins/toast/UluToast.vue.js +68 -77
  113. package/dist/plugins/toast/UluToastDisplay.vue.d.ts +1 -9
  114. package/dist/plugins/toast/UluToastDisplay.vue.js +27 -35
  115. package/dist/plugins/toast/defaults.d.ts +40 -35
  116. package/dist/plugins/toast/defaults.js +2 -2
  117. package/dist/plugins/toast/index.js +4 -4
  118. package/dist/plugins/toast/store.d.ts +40 -35
  119. package/dist/plugins/toast/store.d.ts.map +1 -1
  120. package/dist/utils/props.d.ts +7 -0
  121. package/dist/utils/props.d.ts.map +1 -0
  122. package/dist/utils/props.js +6 -0
  123. package/lib/components/collapsible/UluAccordionGroup.vue +4 -1
  124. package/lib/components/collapsible/UluDropdown.vue +5 -1
  125. package/lib/components/collapsible/UluModal.vue +278 -298
  126. package/lib/components/collapsible/UluTabGroup.vue +21 -6
  127. package/lib/components/elements/UluAlert.vue +38 -51
  128. package/lib/components/elements/UluBadgeStack.vue +4 -1
  129. package/lib/components/elements/UluButton.vue +105 -129
  130. package/lib/components/elements/UluButtonVerbose.vue +67 -89
  131. package/lib/components/elements/UluCallout.vue +15 -19
  132. package/lib/components/elements/UluCaptionedFigure.vue +40 -0
  133. package/lib/components/elements/UluDefinitionList.vue +27 -6
  134. package/lib/components/elements/UluImage.vue +56 -0
  135. package/lib/components/elements/UluList.vue +1 -0
  136. package/lib/components/elements/UluOverflowScroller.vue +140 -0
  137. package/lib/components/elements/UluScrollSlider.vue +150 -0
  138. package/lib/components/elements/UluSlider.vue +488 -0
  139. package/lib/components/index.js +10 -0
  140. package/lib/components/layout/UluTitleRail.vue +55 -48
  141. package/lib/components/navigation/UluBreadcrumb.vue +29 -34
  142. package/lib/components/navigation/UluMenu.vue +60 -71
  143. package/lib/components/navigation/UluMenuStack.vue +6 -1
  144. package/lib/components/navigation/UluNavStrip.vue +43 -31
  145. package/lib/components/systems/facets/useFacets.js +33 -17
  146. package/lib/components/systems/index.js +0 -4
  147. package/lib/components/systems/table-sticky/UluTableSticky.vue +602 -576
  148. package/lib/components/systems/table-sticky/UluTableStickyRows.vue +16 -27
  149. package/lib/components/systems/table-sticky/UluTableStickyTable.vue +95 -96
  150. package/lib/components/utils/UluAction.vue +81 -0
  151. package/lib/components/utils/UluConditionalText.vue +13 -16
  152. package/lib/components/utils/UluConditionalWrapper.vue +5 -1
  153. package/lib/components/utils/UluPlaceholderImage.vue +44 -46
  154. package/lib/components/utils/UluPlaceholderText.vue +10 -13
  155. package/lib/components/utils/UluRouteAnnouncer.vue +59 -47
  156. package/lib/components/visualizations/UluAnimateNumber.vue +23 -30
  157. package/lib/composables/useModifiers.js +21 -26
  158. package/lib/plugins/modals/UluModalsDisplay.vue +44 -45
  159. package/lib/plugins/toast/UluToast.vue +28 -34
  160. package/lib/plugins/toast/UluToastDisplay.vue +9 -15
  161. package/lib/utils/props.js +8 -0
  162. package/package.json +9 -5
  163. package/dist/components/systems/slider/UluImageSlideShow.vue.d.ts +0 -130
  164. package/dist/components/systems/slider/UluImageSlideShow.vue.d.ts.map +0 -1
  165. package/dist/components/systems/slider/UluImageSlideShow.vue.js +0 -73
  166. package/dist/components/systems/slider/UluSlideShow.vue.d.ts +0 -205
  167. package/dist/components/systems/slider/UluSlideShow.vue.d.ts.map +0 -1
  168. package/dist/components/systems/slider/UluSlideShow.vue.js +0 -292
  169. package/dist/components/systems/slider/UluSlideShowSlide.vue.d.ts +0 -17
  170. package/dist/components/systems/slider/UluSlideShowSlide.vue.d.ts.map +0 -1
  171. package/dist/components/systems/slider/UluSlideShowSlide.vue.js +0 -26
  172. package/lib/components/systems/slider/UluImageSlideShow.vue +0 -75
  173. package/lib/components/systems/slider/UluSlideShow.vue +0 -336
  174. package/lib/components/systems/slider/UluSlideShowSlide.vue +0 -25
@@ -14,7 +14,6 @@
14
14
  @cancel.prevent="close"
15
15
  @close="handleDialogCloseEvent"
16
16
  @click="handleClick"
17
- @toggle="handleToggle"
18
17
  >
19
18
  <header
20
19
  v-if="hasHeader"
@@ -68,321 +67,302 @@
68
67
  </Teleport>
69
68
  </template>
70
69
 
71
- <script>
72
- import { useSlots, computed } from "vue";
70
+ <script setup>
71
+ import { useSlots, computed, ref, onMounted, onBeforeUnmount, watch, nextTick } from "vue";
73
72
  import UluIcon from "../elements/UluIcon.vue";
74
73
  import { useModifiers } from "../../composables/useModifiers.js";
75
74
  import { wasClickOutside, preventScroll as setupPreventScroll } from "@ulu/utils/browser/dom.js";
76
- import { Resizer } from "@ulu/frontend";
75
+ import { Resizer, observeDialogToggle } from "@ulu/frontend";
77
76
  import { newId } from "../../utils/dom.js";
78
77
 
79
- export default {
80
- name: "UluModal",
81
- components: {
82
- UluIcon
78
+ const emit = defineEmits(["update:modelValue", "close", "open"]);
79
+
80
+ const props = defineProps({
81
+ /**
82
+ * Controls the visibility of the modal (for v-model).
83
+ */
84
+ modelValue: Boolean,
85
+ /**
86
+ * Target for Vue's Teleport. Defaults to 'body'.
87
+ * Set to `false` to disable teleporting (modal renders inline).
88
+ * Set to `null` or `undefined` for `body` fallback with disabled as false.
89
+ */
90
+ teleport: {
91
+ type: [String, Boolean, Object], // Allow string for target selector, or false to disable, or object (Dome node)
92
+ default: "body"
83
93
  },
84
- emits: ['update:modelValue', 'close', 'open'],
85
- props: {
86
- /**
87
- * Controls the visibility of the modal (for v-model).
88
- */
89
- modelValue: Boolean,
90
- /**
91
- * Target for Vue's Teleport. Defaults to 'body'.
92
- * Set to `false` to disable teleporting (modal renders inline).
93
- * Set to `null` or `undefined` for `body` fallback with disabled as false.
94
- */
95
- teleport: {
96
- type: [String, Boolean, Object], // Allow string for target selector, or false to disable, or object (Dome node)
97
- default: 'body'
98
- },
99
- /**
100
- * When open and not non-modal, the body is prevented from scrolling (defaults to true).
101
- */
102
- preventScroll: {
103
- type: Boolean,
104
- default: true
105
- },
106
- /**
107
- * Compensate for layout shift when preventing scroll. Which adds padding equal to scrollbars
108
- * width while dialog is open
109
- */
110
- preventScrollShift: {
111
- type: Boolean,
112
- default: true
113
- },
114
- /**
115
- * Use non-modal interface for dialog
116
- */
117
- nonModal: Boolean,
118
- /**
119
- * Close modal on click outside
120
- */
121
- clickOutsideCloses: {
122
- type: Boolean,
123
- default: true
124
- },
125
- /**
126
- * Enable resizer
127
- */
128
- allowResize: Boolean,
129
- /**
130
- * Position (any position that modal.scss supports)
131
- */
132
- position: {
133
- type: String,
134
- default: "center"
135
- },
136
- /**
137
- * If `true`, the modal body will fill the available space.
138
- */
139
- bodyFills: Boolean,
140
- /**
141
- * If `true`, no backdrop will be displayed behind the modal
142
- */
143
- noBackdrop: Boolean,
144
- /**
145
- * If `true`, the modal will not have a minimum height
146
- */
147
- noMinHeight: Boolean,
148
- /**
149
- * Set aria-labelledby by element id (to add accessible label)
150
- * - Use this if you are not using the default modal title (custom titles)
151
- */
152
- labelledby: String,
153
- /**
154
- * Set aria-describedby by element id (to add accessible description)
155
- * - This is usually content you passed into the modal body (paragraph/etc)
156
- */
157
- describedby: String,
158
- /**
159
- * Text for modal title in header (can use title slot as well for complex markup), if not passed the header will be omitted
160
- */
161
- title: String,
162
- /**
163
- * Optional icon for before title (uses UluIcon interface)
164
- */
165
- titleIcon: String,
166
- /**
167
- * Default icon for resizer
168
- */
169
- resizerIcon: String,
170
- /**
171
- * Default icon for close button (uses UluIcon interface)
172
- */
173
- closeIcon: String,
174
- /**
175
- * Classes for elements ({ container, header, title, body, footer })
176
- * - Any valid class binding value per element
177
- */
178
- classes: {
179
- type: Object,
180
- default: () => ({
181
- close: "button button--icon"
182
- })
183
- },
184
- /**
185
- * Modifiers (to add any modifier classes based on base class [ie. 'tertiary'])
186
- */
187
- modifiers: [String, Array]
94
+ /**
95
+ * When open and not non-modal, the body is prevented from scrolling (defaults to true).
96
+ */
97
+ preventScroll: {
98
+ type: Boolean,
99
+ default: true
188
100
  },
189
- data() {
190
- return {
191
- containerWidth: null,
192
- titleId: newId('ulu-modal-title'),
193
- bodyOverflowValue: null,
194
- bodyPaddingRightValue: null,
195
- isResizing: false,
196
- }
101
+ /**
102
+ * Compensate for layout shift when preventing scroll. Which adds padding equal to scrollbars
103
+ * width while dialog is open
104
+ */
105
+ preventScrollShift: {
106
+ type: Boolean,
107
+ default: true
108
+ },
109
+ /**
110
+ * Use non-modal interface for dialog
111
+ */
112
+ nonModal: Boolean,
113
+ /**
114
+ * Close modal on click outside
115
+ */
116
+ clickOutsideCloses: {
117
+ type: Boolean,
118
+ default: true
119
+ },
120
+ /**
121
+ * Enable resizer
122
+ */
123
+ allowResize: Boolean,
124
+ /**
125
+ * Position (any position that modal.scss supports)
126
+ */
127
+ position: {
128
+ type: String,
129
+ default: "center"
130
+ },
131
+ /**
132
+ * If `true`, the modal body will fill the available space.
133
+ */
134
+ bodyFills: Boolean,
135
+ /**
136
+ * If `true`, no backdrop will be displayed behind the modal
137
+ */
138
+ noBackdrop: Boolean,
139
+ /**
140
+ * If `true`, the modal will not have a minimum height
141
+ */
142
+ noMinHeight: Boolean,
143
+ /**
144
+ * Set aria-labelledby by element id (to add accessible label)
145
+ * - Use this if you are not using the default modal title (custom titles)
146
+ */
147
+ labelledby: String,
148
+ /**
149
+ * Set aria-describedby by element id (to add accessible description)
150
+ * - This is usually content you passed into the modal body (paragraph/etc)
151
+ */
152
+ describedby: String,
153
+ /**
154
+ * Text for modal title in header (can use title slot as well for complex markup), if not passed the header will be omitted
155
+ */
156
+ title: String,
157
+ /**
158
+ * Optional icon for before title (uses UluIcon interface)
159
+ */
160
+ titleIcon: String,
161
+ /**
162
+ * Default icon for resizer
163
+ */
164
+ resizerIcon: String,
165
+ /**
166
+ * Default icon for close button (uses UluIcon interface)
167
+ */
168
+ closeIcon: String,
169
+ /**
170
+ * Classes for elements ({ container, header, title, body, footer })
171
+ * - Any valid class binding value per element
172
+ */
173
+ classes: {
174
+ type: Object,
175
+ default: () => ({
176
+ close: "button button--icon"
177
+ })
197
178
  },
198
- setup(props) {
199
- const slots = useSlots(); // Access slots via useSlots() helper
179
+ /**
180
+ * Modifiers (to add any modifier classes based on base class [ie. 'tertiary'])
181
+ */
182
+ modifiers: [String, Array],
183
+ /**
184
+ * If true, modal is forced to fullscreen on mobile viewports
185
+ */
186
+ fullscreenMobile: Boolean
187
+ });
188
+
189
+ const slots = useSlots();
190
+
191
+ const containerWidth = ref(null);
192
+ const titleId = newId("ulu-modal-title");
193
+ const isResizing = ref(false);
194
+
195
+ const container = ref(null);
196
+ const resizer = ref(null);
197
+
198
+ const hasHeader = computed(() => props.title || slots.title);
200
199
 
201
- // Note: These two computed need to be defined in setup since their used in internalModifiers
202
- const hasHeader = computed(() => props.title || slots.title);
203
- /**
204
- * Flag for if resizer script should be enabled
205
- * - Resizer only available for left and right
206
- */
207
- const resizerEnabled = computed(() => {
208
- const { allowResize, position } = props;
209
- if (!allowResize || !position) return;
200
+ const resizerEnabled = computed(() => {
201
+ const { allowResize, position } = props;
202
+ if (!allowResize || !position) return false;
210
203
 
211
- const resizablePositions = ["left", "right", "center"];
212
- if (resizablePositions.includes(position)) {
213
- return true;
204
+ const resizablePositions = ["left", "right", "center"];
205
+ if (resizablePositions.includes(position)) {
206
+ return true;
207
+ } else {
208
+ console.warn(`Passed invalid position for resize (${ position }), use ${ resizablePositions.join(", ") }`);
209
+ return false;
210
+ }
211
+ });
212
+
213
+ const resizerIconType = computed(() => {
214
+ return props.position === "center" ? "type:resizeBoth" : "type:resizeHorizontal";
215
+ });
216
+
217
+ const internalModifiers = computed(() => ({
218
+ [props.position]: props.position,
219
+ "resize": props.allowResize,
220
+ "no-resize": !props.allowResize,
221
+ "no-header": !hasHeader.value,
222
+ "body-fills": props.bodyFills,
223
+ "no-backdrop": props.noBackdrop,
224
+ "no-min-height": props.noMinHeight,
225
+ "non-modal": props.nonModal,
226
+ "resizer-active": resizerEnabled.value,
227
+ "fullscreen-mobile": props.fullscreenMobile,
228
+ }));
229
+
230
+ const { resolvedModifiers } = useModifiers({
231
+ props: props,
232
+ baseClass: "modal",
233
+ internal: internalModifiers
234
+ });
235
+
236
+ const resolvedLabelledby = computed(() => {
237
+ return props.labelledby ? props.labelledby : titleId;
238
+ });
239
+
240
+ const close = () => {
241
+ emit("update:modelValue", false);
242
+ emit("close");
243
+ };
244
+
245
+ const handleDialogCloseEvent = () => {
246
+ if (props.modelValue) {
247
+ emit("update:modelValue", false);
248
+ emit("close");
249
+ }
250
+ };
251
+
252
+ const handleClick = (event) => {
253
+ if (props.clickOutsideCloses && !isResizing.value) {
254
+ const { target } = event;
255
+ if (target === container.value && wasClickOutside(container.value, event)) {
256
+ close();
257
+ }
258
+ }
259
+ };
260
+
261
+ let toggleObserver = null;
262
+ let restoreScroll = null;
263
+ let resizerInstance = null;
264
+ let handleResizerStart = null;
265
+ let handleResizerEnd = null;
266
+
267
+ const setupToggleObserver = () => {
268
+ if (!props.nonModal && props.preventScroll) {
269
+ toggleObserver = observeDialogToggle(container.value, (isOpen) => {
270
+ if (isOpen) {
271
+ restoreScroll = setupPreventScroll({ preventShift: props.preventScrollShift });
214
272
  } else {
215
- console.warn(`Passed invalid position for resize (${ position }), use ${ resizablePositions.join(", ") }`);
216
- return false;
273
+ destroyPreventScroll();
217
274
  }
218
275
  });
276
+ }
277
+ };
219
278
 
220
- const resizerIconType = computed(() => {
221
- return props.position === 'center' ? 'type:resizeBoth' : 'type:resizeHorizontal';
222
- });
223
-
224
- // Define the internal modifiers object as a computed property (so it can react to changes)
225
- const internalModifiers = computed(() => ({
226
- [props.position]: props.position,
227
- "resize" : props.allowResize,
228
- "no-resize" : !props.allowResize,
229
- "no-header" : !hasHeader.value,
230
- "body-fills" : props.bodyFills,
231
- "no-backdrop" : props.noBackdrop,
232
- "no-min-height" : props.noMinHeight,
233
- "non-modal" : props.nonModal,
234
- "resizer-active" : resizerEnabled.value,
235
- }));
279
+ const destroyToggleObserver = () => {
280
+ if (toggleObserver) {
281
+ toggleObserver.destroy();
282
+ toggleObserver = null;
283
+ }
284
+ };
236
285
 
237
- const { resolvedModifiers } = useModifiers({
238
- props: props,
239
- baseClass: "modal",
240
- internal: internalModifiers
241
- });
286
+ const destroyPreventScroll = () => {
287
+ if (restoreScroll) {
288
+ restoreScroll();
289
+ restoreScroll = null;
290
+ }
291
+ };
242
292
 
243
- return {
244
- resolvedModifiers,
245
- hasHeader,
246
- resizerEnabled,
247
- resizerIconType
293
+ const setupResizer = () => {
294
+ if (resizerEnabled.value) {
295
+ const options = props.position === "center" ?
296
+ { fromX: "right", fromY: "bottom", multiplier: 2 } :
297
+ { fromX: props.position === "right" ? "left" : "right" };
298
+ resizerInstance = new Resizer(container.value, resizer.value, options);
299
+ handleResizerStart = () => {
300
+ isResizing.value = true;
248
301
  };
249
- },
250
- computed: {
251
- resolvedLabelledby() {
252
- const { labelledby, titleId } = this;
253
- return labelledby ? labelledby : titleId;
254
- }
255
- },
256
- watch: {
257
- modelValue: {
258
- // So that it runs on mount (if modelValue is initially true)
259
- immediate: true,
260
- handler(newValue) {
261
- // Use nextTick to ensure the dialog element is in the DOM before calling showModal
262
- this.$nextTick(() => {
263
- const { container } = this.$refs;
264
- if (newValue) {
265
- container[this.nonModal ? "show" : "showModal"]();
266
- this.$emit("open");
267
- } else {
268
- container.close();
269
- }
270
- });
271
- }
272
- },
273
- resizerEnabled: {
274
- immediate: false, // Don't run on initial mount, as setupResizer is called in mounted
275
- handler(newValue) {
276
- if (newValue) {
277
- this.$nextTick(() => {
278
- this.setupResizer();
279
- });
280
- } else {
281
- this.destroyResizer();
282
- }
283
- }
284
- },
285
- position(newValue, oldValue) {
286
- if (newValue !== oldValue) {
287
- this.destroyResizer();
288
- this.$nextTick(() => {
289
- this.setupResizer();
290
- });
291
- }
292
- }
293
- },
294
- methods: {
295
- close() {
296
- // Emit 'update:modelValue' to inform parent to set modelValue to false
297
- // This will trigger the watch handler above to call this.$refs.container.close()
298
- this.$emit("update:modelValue", false);
299
- this.$emit("close"); // Also emit a generic 'close' event for convenience
300
- },
301
- handleDialogCloseEvent() {
302
- // Ensure modelValue is false, primarily for scenarios where dialog is closed
303
- // by user agent (e.g., form submission within dialog) or direct native API call
304
- // that bypasses `this.close()` or Escape key.
305
- if (this.modelValue) { // Only emit if it was open based on modelValue
306
- this.$emit("update:modelValue", false);
307
- this.$emit("close");
308
- }
309
- },
310
- handleClick(event) {
311
- if (this.clickOutsideCloses && !this.isResizing) {
312
- const { target } = event;
313
- const { container } = this.$refs;
314
- if (target === container && wasClickOutside(container, event)) {
315
- this.close();
316
- }
317
- }
318
- },
319
- setupPreventScroll() {
320
- const { body } = document;
321
- this.bodyOverflowValue = body.style.overflow;
322
- this.bodyPaddingRightValue = body.style.paddingRight;
323
- },
324
- destroyPreventScroll() {
325
- if (this.restoreScroll) {
326
- this.restoreScroll();
327
- }
328
- },
329
- handleToggle(event) {
330
- if (!this.nonModal && this.preventScroll) {
331
- const { preventScrollShift: preventShift } = this;
332
- const isOpen = event.newState === "open";
333
- if (isOpen) {
334
- this.restoreScroll = setupPreventScroll({ preventShift });
335
- } else {
336
- this.destroyPreventScroll();
337
- }
338
- }
339
- },
340
- setupResizer() {
341
- const { position, resizerEnabled } = this;
342
- if (resizerEnabled) {
343
- const { container, resizer } = this.$refs;
344
- const options = position === "center" ?
345
- { fromX: "right", fromY: "bottom", multiplier: 2 } :
346
- { fromX: position === "right" ? "left" : "right" };
347
- this.resizerInstance = new Resizer(container, resizer, options);
348
- this.handleResizerStart = () => {
349
- this.isResizing = true;
350
- };
351
- this.handleResizerEnd = () => {
352
- // After click has ended (next in event loop)
353
- setTimeout(() => { this.isResizing = false; }, 0)
354
- };
355
- container.addEventListener("ulu:resizer:start", this.handleResizerStart);
356
- container.addEventListener("ulu:resizer:end", this.handleResizerEnd);
357
- }
358
- },
359
- destroyResizer() {
360
- const { container } = this.$refs;
361
- if (this.resizerInstance) {
362
- this.resizerInstance.destroy();
363
- this.resizerInstance = null;
364
- }
365
- if (this.handleResizerStart) {
366
- container.removeEventListener("ulu:resizer:start", this.handleResizerStart);
367
- }
368
- if (this.handleResizerEnd) {
369
- container.removeEventListener("ulu:resizer:end", this.handleResizerEnd);
302
+ handleResizerEnd = () => {
303
+ setTimeout(() => { isResizing.value = false; }, 0);
304
+ };
305
+ container.value.addEventListener("ulu:resizer:start", handleResizerStart);
306
+ container.value.addEventListener("ulu:resizer:end", handleResizerEnd);
307
+ }
308
+ };
309
+
310
+ const destroyResizer = () => {
311
+ if (resizerInstance) {
312
+ resizerInstance.destroy();
313
+ resizerInstance = null;
314
+ }
315
+ if (handleResizerStart && container.value) {
316
+ container.value.removeEventListener("ulu:resizer:start", handleResizerStart);
317
+ }
318
+ if (handleResizerEnd && container.value) {
319
+ container.value.removeEventListener("ulu:resizer:end", handleResizerEnd);
320
+ }
321
+ };
322
+
323
+ watch(() => props.modelValue, (newValue) => {
324
+ nextTick(() => {
325
+ if (container.value) {
326
+ if (newValue) {
327
+ container.value[props.nonModal ? "show" : "showModal"]();
328
+ emit("open");
329
+ } else {
330
+ container.value.close();
370
331
  }
371
332
  }
372
- },
373
- mounted() {
374
- if (this.preventScroll) {
375
- this.setupPreventScroll();
376
- }
377
- this.setupResizer();
378
- },
379
- beforeUnmount() {
380
- const { container } = this.$refs;
381
- if (container && container.open) {
382
- container.close();
383
- }
384
- this.destroyPreventScroll();
385
- this.destroyResizer();
333
+ });
334
+ }, { immediate: true });
335
+
336
+ watch(resizerEnabled, (newValue) => {
337
+ if (newValue) {
338
+ nextTick(() => {
339
+ setupResizer();
340
+ });
341
+ } else {
342
+ destroyResizer();
386
343
  }
387
- };
344
+ }, { immediate: false });
345
+
346
+ watch(() => props.position, (newValue, oldValue) => {
347
+ if (newValue !== oldValue) {
348
+ destroyResizer();
349
+ nextTick(() => {
350
+ setupResizer();
351
+ });
352
+ }
353
+ });
354
+
355
+ onMounted(() => {
356
+ setupToggleObserver();
357
+ setupResizer();
358
+ });
359
+
360
+ onBeforeUnmount(() => {
361
+ if (container.value && container.value.open) {
362
+ container.value.close();
363
+ }
364
+ destroyToggleObserver();
365
+ destroyPreventScroll();
366
+ destroyResizer();
367
+ });
388
368
  </script>
@@ -1,10 +1,18 @@
1
1
  <template>
2
- <TabGroup v-slot="slotProps" :defaultIndex="defaultIndex" :vertical="vertical">
2
+ <TabGroup
3
+ v-slot="slotProps"
4
+ :defaultIndex="defaultIndex"
5
+ :vertical="vertical"
6
+ >
3
7
  <div
8
+ v-bind="$attrs"
4
9
  class="tabs"
5
- :class="{
6
- 'tabs--vertical' : vertical
7
- }"
10
+ :class="[
11
+ resolvedModifiers,
12
+ {
13
+ 'tabs--vertical' : vertical
14
+ }
15
+ ]"
8
16
  >
9
17
  <slot v-bind="slotProps"/>
10
18
  </div>
@@ -13,12 +21,13 @@
13
21
 
14
22
  <script setup>
15
23
  import { TabGroup } from "@headlessui/vue";
24
+ import { useModifiers } from "../../composables/useModifiers.js";
16
25
 
17
26
  defineOptions({
18
27
  inheritAttrs: false
19
28
  });
20
29
 
21
- defineProps({
30
+ const props = defineProps({
22
31
  /**
23
32
  * Active tab index by default
24
33
  */
@@ -26,6 +35,12 @@
26
35
  /**
27
36
  * Whether or not to use vertical layout
28
37
  */
29
- vertical: Boolean
38
+ vertical: Boolean,
39
+ /**
40
+ * Class modifiers (ie. 'transparent', 'secondary', etc)
41
+ */
42
+ modifiers: [String, Array]
30
43
  });
44
+
45
+ const { resolvedModifiers } = useModifiers({ props, baseClass: "tabs" });
31
46
  </script>