@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
@@ -54,7 +54,6 @@
54
54
  <div class="table-sticky__sticky-wrap table-sticky__sticky-wrap--first-column-header">
55
55
  <UluTableStickyTable
56
56
  v-if="firstColumnSticky"
57
- ref="firstColumnHeader"
58
57
  class="table-sticky__table table-sticky__table--first-column-header"
59
58
  :classes="classes"
60
59
  :caption="caption"
@@ -77,7 +76,6 @@
77
76
  <div
78
77
  class="table-sticky__controls"
79
78
  :class="resolveClasses(classes.controls)"
80
- ref="controls"
81
79
  v-show="controlsShown"
82
80
  >
83
81
  <slot
@@ -150,7 +148,6 @@
150
148
  </div>
151
149
  <UluTableStickyTable
152
150
  v-if="firstColumnSticky"
153
- ref="firstColumn"
154
151
  class="table-sticky__table table-sticky__table--first-column"
155
152
  :classes="classes"
156
153
  :resolveClasses="resolveClasses"
@@ -176,601 +173,630 @@
176
173
  </div>
177
174
  </template>
178
175
 
179
- <script>
176
+ <script setup>
177
+ // Prop specific import (hoisted)
178
+ import { isArrayOfObjects } from "../../../utils/props.js";
179
+
180
+ import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from "vue";
180
181
  import UluTableStickyTable from "./UluTableStickyTable.vue";
181
182
  import { debounce } from "@ulu/utils/performance.js";
182
183
  import { runAfterFramePaint } from "@ulu/utils/browser/performance.js";
183
184
  import cloneDeep from "lodash-es/cloneDeep.js";
184
-
185
- const arrayOfObjects = a => a.every(o => typeof o === "object");
186
- const required = true;
185
+
187
186
  const SSR = import.meta.env.SSR;
188
187
  const getWindowWidth = () => SSR ? 0 : window.innerWidth;
189
188
  // Used to make sure resize events on ios (when address bar collapses)
190
189
  // don't trigger recalculations
191
190
  let windowWidth = getWindowWidth();
192
191
 
193
- /**
194
- * Sticky table component
195
- * - Requires separate stylesheet (included in @ulu/frontend)
196
- * - Requires lodash/cloneDeep
197
- */
198
- export default {
199
- name: "UluTableSticky",
200
- components: {
201
- UluTableStickyTable
192
+ const props = defineProps({
193
+ /**
194
+ * By default you cannot have interactive items in the cloned sticky header and first column (if set)
195
+ * this disables that feature. It was set up that way for accessibility
196
+ */
197
+ allowClickClones: Boolean,
198
+ /**
199
+ * Allows user to pass classes object to add custom classes to parts of the component
200
+ */
201
+ classes: {
202
+ type: Object,
203
+ default: () => ({})
202
204
  },
203
- props: {
204
- /**
205
- * By default you cannot have interactive items in the cloned sticky header and first column (if set)
206
- * this disables that feature. It was set up that way for accessibility
207
- */
208
- allowClickClones: Boolean,
209
- /**
210
- * Allows user to pass classes object to add custom classes to parts of the component
211
- */
212
- classes: {
213
- type: Object,
214
- default: () => ({})
215
- },
216
- /**
217
- * Allow user to pass components
218
- * - Passed the same values as if using a slot
219
- * -
220
- */
221
- controlsComponent: Object,
222
- /**
223
- * Allows user to pass callback to get the row's value
224
- */
225
- getRowValue: Function,
226
- getColumnTitle: Function,
227
- /**
228
- * Hidden caption for accessibility
229
- */
230
- caption: {
231
- type: String,
232
- required
233
- },
234
- /**
235
- * Array of column configurations to convert to list output
236
- *
237
- * @property {Object} column A column config
238
- * @property {String|Boolean} column.title The title to output for the column if set to a falsey value nothing will print
239
- * @property {Array} column.columns Array of child columns
240
- * @property {String} column.key The key that should be usec to grab column's value from rows
241
- * @property {Function} column.value A function that returns the column's value used instead of key passed (row, column)
242
- * @property {String} column.slot Register custom slot name to use as a template for this column. Passing a slot with this name will link them. The slot are passed the ({row, column}). Note this will disable output of the column's value
243
- * @property {String} column.component Pass a component to use for this columns values (<td>)
244
- * @property {String} column.componentHeader Pass a component to use for this columns header (<th>)
245
- * @property {String} column.slotHeader Register custom slot name to use in the header
246
- * @property {String} column.classHeader Custom class(s) to be set to column <th>
247
- * @property {String} column.class Custom class(s) to be set to column's value <td>
248
- * @property {String} column.html Use v-html output for value
249
- * @property {String} column.rowHeader When this column is printed in the <tbody> it should be a header for the row. Note supports multiple row headers from left to right only. No rowspan support for rowHeaders.
250
- */
251
- columns: {
252
- type: Array,
253
- validator: arrayOfObjects,
254
- required
255
- },
256
- /**
257
- * Whether the first column of the table should be sticky
258
- * - Requires that the table's first column header is nested
259
- */
260
- firstColumnSticky: Boolean,
261
- /**
262
- * Prefixed used for id generation
263
- */
264
- idPrefix: {
265
- type: String,
266
- default: "DT"
267
- },
268
- /**
269
- * Array of tables rows (tbody)
270
- * - Each row is an object who's value will matched to columns
271
- */
272
- rows: {
273
- type: Array,
274
- validator: arrayOfObjects,
275
- // required
276
- },
277
- /**
278
- * Array of rows for footer (tfoot)
279
- */
280
- footerRows: {
281
- type: Array,
282
- validator: arrayOfObjects,
283
- },
284
- /**
285
- * Enables the visibility of the scroll controls
286
- * - Scroll controls shift the tables x-axis when the table has overflow-x
287
- * - Can be templated manually using slot named "controlsButtons", slot needs to create layout and call methods
288
- * + scope = { scrollLeft, scrollRight, canScrollLeft, canScrollRight }
289
- * - Scroll controls are transformed with the header (move down as the user scrolls)
290
- */
291
- scrollControls: Boolean,
292
- /**
293
- * Scrollable context DOM Element, if the sticky element is within another
294
- * scrolling parent use this to change the scroll activation handler to use a custom
295
- * scrollable parent element
296
- *
297
- */
298
- scrollContext: {
299
- default: () => SSR ? null : window
300
- },
301
- /**
302
- * Amount to scroll when user uses scroll controls (pixels)
303
- */
304
- scrollControlAmount: {
305
- type: Number,
306
- default: 100
307
- }
205
+ /**
206
+ * Allow user to pass components
207
+ * - Passed the same values as if using a slot
208
+ * -
209
+ */
210
+ controlsComponent: Object,
211
+ /**
212
+ * Allows user to pass callback to get the row's value
213
+ */
214
+ getRowValue: Function,
215
+ getColumnTitle: Function,
216
+ /**
217
+ * Hidden caption for accessibility
218
+ */
219
+ caption: {
220
+ type: String,
221
+ required: true
222
+ },
223
+ /**
224
+ * Array of column configurations to convert to list output
225
+ *
226
+ * @property {Object} column A column config
227
+ * @property {String|Boolean} column.title The title to output for the column if set to a falsey value nothing will print
228
+ * @property {Array} column.columns Array of child columns
229
+ * @property {String} column.key The key that should be usec to grab column's value from rows
230
+ * @property {Function} column.value A function that returns the column's value used instead of key passed (row, column)
231
+ * @property {String} column.slot Register custom slot name to use as a template for this column. Passing a slot with this name will link them. The slot are passed the ({row, column}). Note this will disable output of the column's value
232
+ * @property {String} column.component Pass a component to use for this columns values (<td>)
233
+ * @property {String} column.componentHeader Pass a component to use for this columns header (<th>)
234
+ * @property {String} column.slotHeader Register custom slot name to use in the header
235
+ * @property {String} column.classHeader Custom class(s) to be set to column <th>
236
+ * @property {String} column.class Custom class(s) to be set to column's value <td>
237
+ * @property {String} column.html Use v-html output for value
238
+ * @property {String} column.rowHeader When this column is printed in the <tbody> it should be a header for the row. Note supports multiple row headers from left to right only. No rowspan support for rowHeaders.
239
+ */
240
+ columns: {
241
+ type: Array,
242
+ validator: isArrayOfObjects,
243
+ required: true
308
244
  },
309
- data() {
310
- const currentColumns = this.createColumns();
311
- return {
312
- currentColumns,
313
- currentRows: this.createRows(),
314
- currentFooterRows: this.createRows(true),
315
- headerRows: this.createHeaderRows(currentColumns),
316
- sizesCalculated: false,
317
- tableWidth: "auto",
318
- resizeHandler: debounce(this.onResize.bind(this), 500, true),
319
- resizing: false,
320
- overflownX: false,
321
- canScrollLeft: false,
322
- canScrollRight: false,
323
- displayY: null,
324
- sizesPainted: false,
325
- columnResizeObserver: SSR ? null : new ResizeObserver(() => this.onColumnResize())
326
- };
245
+ /**
246
+ * Whether the first column of the table should be sticky
247
+ * - Requires that the table's first column header is nested
248
+ */
249
+ firstColumnSticky: Boolean,
250
+ /**
251
+ * Prefixed used for id generation
252
+ */
253
+ idPrefix: {
254
+ type: String,
255
+ default: "DT"
327
256
  },
328
- watch: {
329
- columns: {
330
- handler() {
331
- this.currentColumns = this.createColumns();
332
- this.headerRows = this.createHeaderRows(this.currentColumns);
333
- this.refresh();
334
- },
335
- deep: true
336
- },
337
- rows: {
338
- handler() {
339
- this.currentRows = this.createRows();
340
- this.refresh();
341
- },
342
- deep: true
343
- },
344
- footerRows: {
345
- handler() {
346
- this.currentFooterRows = this.createRows(true);
347
- this.refresh();
348
- },
349
- deep: true
350
- }
257
+ /**
258
+ * Array of tables rows (tbody)
259
+ * - Each row is an object who's value will matched to columns
260
+ */
261
+ rows: {
262
+ type: Array,
263
+ validator: isArrayOfObjects,
351
264
  },
352
- computed: {
353
- controlsShown() {
354
- return this.scrollControls && this.overflownX;
355
- },
356
- headerVisibleX() {
357
- return this.sizesCalculated && this.overflownX;
358
- },
359
- headerOpacityX() {
360
- // Only false (0) when translate is 0
361
- return this.headerVisibleX ? "1" : "0";
362
- },
363
- /**
364
- * Used to output the body rows. This is an array of only the deepest child columns
365
- * parent column information can be accessed by reference
366
- */
367
- rowColumns() {
368
- const columns = this.currentColumns;
369
- const rowColumns = [];
370
- const add = c => {
371
- if (c.columns) c.columns.forEach(add);
372
- else rowColumns.push(c);
373
- };
374
- // Create array of actual
375
- columns.forEach(add);
376
- // Iterate over all columns checking for rowHeader
377
- // - If a column has row header create an id function passed current row's index
378
- // - Store callbacks in an array to call on each rows cells
379
- let rowHeaders = [];
380
- rowColumns.forEach((c, columnIndex) => {
381
- // Creating copy of array here so it doesn't include it's own ID and also
382
- // so there can be headers of headers going from left to right only
383
- const thisRowsHeader = rowHeaders.slice();
384
- c.getRowHeaders = rowIndex => thisRowsHeader.map(cb => cb(rowIndex)).join(" ");
385
- // Now we add this columns row header function
386
- // Which will be included in all columns after this iteration
387
- if (c.rowHeader) {
388
- c.getRowHeaderId = rowIndex => `${ this.idPrefix }-rh-${ rowIndex }-${ columnIndex }`;
389
- rowHeaders.push(c.getRowHeaderId);
390
- }
391
- });
392
- return rowColumns;
393
- },
394
- headerHeight() {
395
- // Offset height would be the combination of all the rows height's
396
- return this.headerRows.reduce((a, r) => a + r.boxHeight, 0);
397
- },
398
- /**
399
- * Reduce the array of column header rows to the first row, first column
400
- */
401
- headerRowsFirst() {
402
- const firstRow = this.headerRows[0];
403
- const firstColumn = Object.assign({}, firstRow.columns[0], { rowspan: 1, colspan: 1 });
404
- const columns = [ firstColumn ];
405
- return [{
406
- ...firstRow,
407
- columns,
408
- boxHeight: this.headerHeight,
409
- height: `${ this.headerHeight }px`
410
- }];
411
- },
412
- /**
413
- * Reduce the rowColumn array to only the first column
414
- */
415
- rowColumnsFirst() {
416
- return [ this.rowColumns[0] ];
417
- },
418
- firstColumnSize() {
419
- const height = this.headerRowsFirst[0].height;
420
- const width = this.headerRows[0].columns[0].width;
421
- return { width, height };
422
- }
265
+ /**
266
+ * Array of rows for footer (tfoot)
267
+ */
268
+ footerRows: {
269
+ type: Array,
270
+ validator: isArrayOfObjects,
423
271
  },
424
- methods: {
425
- resetSorts(except) {
426
- const resetSort = cols => {
427
- cols.forEach(col => {
428
- if (except.key !== col.key) {
429
- col.sortApplied = false;
430
- col.sortAscending = false;
431
- }
432
- if (col.columns) {
433
- resetSort(col.columns);
434
- }
435
- });
436
- };
437
- resetSort(this.currentColumns);
438
- },
439
- applySort(column) {
440
- this.resetSorts(column);
441
- if (column.sortApplied) {
442
- column.sortAscending = !column.sortAscending;
443
- } else {
444
- column.sortApplied = true;
445
- }
446
- this.$emit("column-sort", column);
447
- },
448
- onColumnResize() {
449
- if (this.sizesPainted) {
450
- this.refresh();
451
- }
452
- },
453
- headerAdded(el) {
454
- if (!SSR) {
455
- this.columnResizeObserver.observe(el);
456
- }
457
- },
458
- headerRemoved(el) {
459
- if (!SSR) {
460
- this.columnResizeObserver.unobserve(el);
461
- }
462
- },
463
- /**
464
- * Allow classes options to be strings or functions
465
- */
466
- resolveClasses(passed, args = null) {
467
- if (typeof passed === "undefined") return;
468
- if (typeof passed === "function") {
469
- return passed(args);
470
- }
471
- return passed;
472
- },
473
- /**
474
- * Handles horizontal scroll
475
- * - Shifts the first column as the user scrolls
476
- */
477
- syncScrollLeft() {
478
- const left = this.$refs.display.scrollLeft;
479
- this.$refs.header.$el.style.transform = `translateX(-${ left }px)`;
480
- },
481
- /**
482
- * Checks and sets state if the table is overflow horizontally
483
- */
484
- checkOverflowX() {
485
- this.overflownX = this.$refs.display.scrollWidth > this.$refs.display.clientWidth;
486
- },
487
- /**
488
- * Checks whether if the tables scroll position is at the start or end and updates state
489
- */
490
- checkScrollability() {
491
- if (!this.overflownX) return;
492
- const element = this.$refs.display;
493
- this.canScrollLeft = element.scrollLeft > 0;
494
- this.canScrollRight = element.clientWidth + element.scrollLeft < element.scrollWidth;
495
- },
496
- /**
497
- * Creates column array for internal use
498
- * - Avoid mutating user's prop
499
- * - Current columns being used in the display
500
- * - This internal copy has internal properties/structural info (like ID)
501
- * - This is the copy of the users columns to avoid mutating their object
502
- * - Can be used in the future for adding/removing or enabling/disabling
503
- */
504
- createColumns() {
505
- const newId = this.idCreator("c");
506
- const columns = cloneDeep(this.columns);
507
- const prep = (column, parent) => {
508
- column.id = newId();
509
- column.parent = parent;
510
- column.width = "auto";
511
- column.boxWidth = null;
512
- column.sortApplied = false;
513
- column.sortAscending = false;
514
- column.sortFocused = false;
515
- let headers = [];
516
- // Add the column's headers for output to attribute
517
- if (parent) {
518
- if (parent.headers && parent.headers.length) {
519
- headers = [ ...parent.headers ];
520
- } else {
521
- headers.push(parent.id);
522
- }
523
- }
524
- headers.push(column.id);
525
- column.headers = headers;
526
- // Call the function on this column's children
527
- if (column.columns) {
528
- column.columns.forEach(c => prep(c, column));
529
- // Make sure column has a required properties
530
- } else if (!column.key && !column.value && !column.slot) {
531
- console.warn("UluTableSticky: Missing 'key', 'value' or 'slot' in column configuration for", column);
532
- }
533
- };
534
- columns.forEach(c => prep(c, null));
535
- return columns;
536
- },
537
- /**
538
- * Conversion of the columns (which are nested hierarchy) to a flat list of columns
539
- * sorted by the way they need to be displayed in rows
540
- * - Used for nested headers
541
- * - Transform nested data into row arrays
542
- */
543
- createHeaderRows(currentColumns) {
544
- // Create empty row array, each array will hold it's columns
545
- const newId = this.idCreator("hr");
546
- const count = currentColumns.reduce(this.maxColumnChildren, 1);
547
- const height = "auto";
548
- const rows = new Array(count).fill(null).map(() => ({
549
- height,
550
- boxHeight: null,
551
- columns: [],
552
- id: newId()
553
- }));
554
- /**
555
- * Function that adds columns to the rows array's based
556
- * on their depth, called recursively.
557
- */
558
- function setInRows(depth, column) {
559
- const columns = column.columns;
560
- // Go to inward to the deepest child
561
- if (columns) columns.forEach(c => setInRows(1 + depth, c));
562
- // Now that the deepest children have been calculated and pushed we have
563
- // all the information we need to determine the parent's colspan by reducing
564
- // the parents children's colspans and children would include their children
565
- column.rowspan = columns ? 1 : count - depth;
566
- column.colspan = columns ? columns.reduce((a, c) => a + c.colspan, 0) : 1;
567
- rows[depth].columns.push(column);
568
- }
569
- currentColumns.forEach(c => setInRows(0, c));
570
- return rows;
571
- },
572
- /**
573
- * Creates row array for internal use
574
- * - Avoid mutating user's prop
575
- */
576
- createRows(forFooter) {
577
- const newId = this.idCreator(forFooter ? "fr" : "br");
578
- const rows = forFooter ? this.footerRows : this.rows;
579
- return rows ? rows.map(row => ({
580
- height: null,
581
- boxHeight: null,
582
- data: row,
583
- id: newId()
584
- })) : [];
585
- },
586
- onResize() {
587
- if (SSR) return;
588
- const newWindowWidth = getWindowWidth();
589
- if (windowWidth === newWindowWidth) return;
590
- windowWidth = newWindowWidth;
591
-
592
- // Called when the resize event is first fired (before change)
593
- if (!this.resizing) {
594
- this.resizing = true;
595
- this.removeTableSizes();
272
+ /**
273
+ * Enables the visibility of the scroll controls
274
+ * - Scroll controls shift the tables x-axis when the table has overflow-x
275
+ * - Can be templated manually using slot named "controlsButtons", slot needs to create layout and call methods
276
+ * + scope = { scrollLeft, scrollRight, canScrollLeft, canScrollRight }
277
+ * - Scroll controls are transformed with the header (move down as the user scrolls)
278
+ */
279
+ scrollControls: Boolean,
280
+ /**
281
+ * Scrollable context DOM Element, if the sticky element is within another
282
+ * scrolling parent use this to change the scroll activation handler to use a custom
283
+ * scrollable parent element
284
+ */
285
+ scrollContext: {
286
+ default: () => import.meta.env.SSR ? null : window
287
+ },
288
+ /**
289
+ * Amount to scroll when user uses scroll controls (pixels)
290
+ */
291
+ scrollControlAmount: {
292
+ type: Number,
293
+ default: 100
294
+ }
295
+ });
296
+
297
+ const emit = defineEmits(["column-sort"]);
298
+
299
+ const header = ref(null);
300
+ const display = ref(null);
301
+ const table = ref(null);
302
+
303
+ const idCreator = (type) => {
304
+ let id = 0;
305
+ return () => `${ props.idPrefix }-${ type }-${ ++id }`;
306
+ };
307
+
308
+ /**
309
+ * Creates column array for internal use
310
+ * - Avoid mutating user's prop
311
+ * - Current columns being used in the display
312
+ * - This internal copy has internal properties/structural info (like ID)
313
+ * - This is the copy of the users columns to avoid mutating their object
314
+ * - Can be used in the future for adding/removing or enabling/disabling
315
+ */
316
+ const createColumns = () => {
317
+ const newId = idCreator("c");
318
+ const columns = cloneDeep(props.columns);
319
+ const prep = (column, parent) => {
320
+ column.id = newId();
321
+ column.parent = parent;
322
+ column.width = "auto";
323
+ column.boxWidth = null;
324
+ column.sortApplied = false;
325
+ column.sortAscending = false;
326
+ column.sortFocused = false;
327
+ let headers = [];
328
+ // Add the column's headers for output to attribute
329
+ if (parent) {
330
+ if (parent.headers && parent.headers.length) {
331
+ headers = [ ...parent.headers ];
596
332
  } else {
597
- this.resizing = false;
598
- this.setTableSizes();
599
- this.checkOverflowX();
600
- this.syncScrollLeft();
601
- }
602
- },
603
- /**
604
- * Method to update the table (sizes, etc) when data has changed
605
- */
606
- refresh() {
607
- if (SSR) return;
608
- this.removeTableSizes();
609
- this.$nextTick(() => {
610
- this.setTableSizes();
611
- this.checkOverflowX();
612
- this.checkScrollability();
613
- this.syncScrollLeft();
614
- });
615
- },
616
- onScrollX() {
617
- this.checkScrollability();
618
- this.syncScrollLeft();
619
- },
620
- idCreator(type) {
621
- let id = 0;
622
- return () => `${ this.idPrefix }-${ type }-${ ++id }`;
623
- },
624
- /**
625
- * Recursive function used as a reducer to return the deepest nested columns
626
- */
627
- maxColumnChildren(d, c) {
628
- const m = c.columns ? c.columns.reduce(this.maxColumnChildren) + 1 : 1;
629
- return d > m ? d : m;
630
- },
631
- /**
632
- * Method to attach handlers needed after creation
633
- */
634
- attachHandlers() {
635
- // this.handlerScrollX = this.throttleScroll(this.onScrollX); // Note: Non-reactive property
636
- this.handlerScrollX = this.onScrollX; // Note: Non-reactive property
637
- this.$refs.display.addEventListener("scroll", this.handlerScrollX );
638
- this.scrollContext.addEventListener("touchmove", this.handlerScrollY);
639
- window.addEventListener("resize", this.resizeHandler);
640
- },
641
- removeHandlers() {
642
- this.$refs.display.removeEventListener("scroll", this.handlerScrollX);
643
- this.scrollContext.removeEventListener("scroll", this.handlerScrollY);
644
- this.scrollContext.addEventListener("touchmove", this.handlerScrollY);
645
- window.removeEventListener("resize", this.resizeHandler);
646
- },
647
- removeTableSizes() {
648
- this.sizesPainted = false;
649
- this.sizesCalculated = false;
650
- const setRowHeight = row => {
651
- row.boxHeight = null;
652
- row.height = "auto";
653
- };
654
- this.tableWidth = "auto";
655
- this.headerRows.forEach(row => {
656
- setRowHeight(row);
657
- row.columns.forEach(column => {
658
- column.boxWidth = null;
659
- column.width = "auto";
660
- });
661
- });
662
- if (this.firstColumnSticky) {
663
- this.currentRows.forEach(row => setRowHeight(row));
664
- this.currentFooterRows.forEach(row => setRowHeight(row));
333
+ headers.push(parent.id);
665
334
  }
666
- },
667
- scrollLeft() {
668
- const element = this.$refs.display;
669
- const scrollLeft = element.scrollLeft;
670
- const amount = this.scrollControlAmount;
671
- const toScroll = scrollLeft - amount;
672
-
673
- element.scroll({
674
- left: toScroll < 0 ? 0 : toScroll,
675
- behavior: "smooth"
676
- });
677
- },
678
- scrollRight() {
679
- const element = this.$refs.display;
680
- const scrollWidth = element.scrollWidth;
681
- const scrollLeft = element.scrollLeft;
682
- const amount = this.scrollControlAmount;
683
- const toScroll = scrollLeft + amount;
684
- // If amount would be greater than scrollable area
685
- // scroll to end
686
- // element.scrollLeft = element.scrollWidth;
687
- element.scroll({
688
- left: toScroll > scrollWidth ? element.scrollWidth : toScroll,
689
- behavior: "smooth"
690
- });
691
- },
692
- /**
693
- * Cleanup function for when component is not in use
694
- */
695
- setTableSizes() {
696
- if (SSR) return;
697
- // Set the table and it's cloned header to the exact same width
698
- const size = (element, key) => Math.ceil(element.getBoundingClientRect()[key]);
699
- this.tableWidth = `${ size(this.$refs.table.$el, "width") }px`;
700
- const getElement = object => document.getElementById(object.id);
701
-
702
- const setRowHeight = row => {
703
- const element = getElement(row);
704
- // Ensure element still exists, sometimes (only seen in storybook the element
705
- // is removed before the unmounted/beforeUnmounted hook), this prevents the error
706
- // for missing element. #mounted-no-element
707
- if (element) {
708
- row.boxHeight = size(element, "height");
709
- row.height = `${ row.boxHeight }px`;
710
- }
711
- };
712
- // Set the tables header <tr> and <th> to their rendered sizes
713
- // By measuring each and updating it's column object data
714
- // reactively updating all the cloned versions
715
- this.headerRows.forEach(row => {
716
- setRowHeight(row);
717
- row.columns.forEach(column => {
718
- const element = getElement(column);
719
- // See #mounted-no-element
720
- if (element) {
721
- column.boxWidth = size(element, "width");
722
- column.width = `${ column.boxWidth }px`;
723
- }
724
- });
725
- });
726
- // If first column sticky the plugin needs to set
727
- // each row's height so the cloned column matches
728
- if (this.firstColumnSticky) {
729
- this.currentRows.forEach(row => setRowHeight(row));
730
- this.currentFooterRows.forEach(row => setRowHeight(row));
731
- }
732
- this.$nextTick(() => {
733
- this.sizesCalculated = true;
734
- runAfterFramePaint(() => {
735
- this.sizesPainted = true;
736
- });
737
- });
738
- },
739
- tableReady() {
740
- this.setTableSizes();
741
- },
742
- /**
743
- * Creates a new throttled scroll handler
744
- */
745
- // throttleScroll(handler) {
746
- // let id = null;
747
- // // Old Fired after frame
748
- // return (event) => {
749
- // if (id) window.cancelAnimationFrame(id);
750
- // id = window.requestAnimationFrame(() => handler(event));
751
- // };
752
- // },
753
- },
754
- mounted() {
755
- if (!SSR) {
756
- this.attachHandlers();
757
- this.checkOverflowX();
758
- this.checkScrollability();
759
335
  }
760
- },
761
- beforeUnmount() {
762
- if (!SSR) {
763
- this.removeHandlers();
336
+ headers.push(column.id);
337
+ column.headers = headers;
338
+ // Call the function on this column's children
339
+ if (column.columns) {
340
+ column.columns.forEach(c => prep(c, column));
341
+ // Make sure column has a required properties
342
+ } else if (!column.key && !column.value && !column.slot) {
343
+ console.warn("UluTableSticky: Missing 'key', 'value' or 'slot' in column configuration for", column);
764
344
  }
765
- },
766
- unmounted() {
767
- if (!SSR) {
768
- // Allow resizer to be GC
769
- this.columnResizeObserver.disconnect();
770
- this.columnResizeObserver = null;
345
+ };
346
+ columns.forEach(c => prep(c, null));
347
+ return columns;
348
+ };
349
+
350
+ /**
351
+ * Recursive function used as a reducer to return the deepest nested columns
352
+ */
353
+ const maxColumnChildren = (d, c) => {
354
+ const m = c.columns ? c.columns.reduce(maxColumnChildren, 1) + 1 : 1;
355
+ return d > m ? d : m;
356
+ };
357
+
358
+ /**
359
+ * Conversion of the columns (which are nested hierarchy) to a flat list of columns
360
+ * sorted by the way they need to be displayed in rows
361
+ * - Used for nested headers
362
+ * - Transform nested data into row arrays
363
+ */
364
+ const createHeaderRows = (currentColumnsData) => {
365
+ // Create empty row array, each array will hold it's columns
366
+ const newId = idCreator("hr");
367
+ const count = currentColumnsData.reduce(maxColumnChildren, 1);
368
+ const height = "auto";
369
+ const rows = new Array(count).fill(null).map(() => ({
370
+ height,
371
+ boxHeight: null,
372
+ columns: [],
373
+ id: newId()
374
+ }));
375
+
376
+ /**
377
+ * Function that adds columns to the rows array's based
378
+ * on their depth, called recursively.
379
+ */
380
+ function setInRows(depth, column) {
381
+ const columns = column.columns;
382
+ // Go to inward to the deepest child
383
+ if (columns) columns.forEach(c => setInRows(1 + depth, c));
384
+ // Now that the deepest children have been calculated and pushed we have
385
+ // all the information we need to determine the parent's colspan by reducing
386
+ // the parents children's colspans and children would include their children
387
+ column.rowspan = columns ? 1 : count - depth;
388
+ column.colspan = columns ? columns.reduce((a, c) => a + c.colspan, 0) : 1;
389
+ rows[depth].columns.push(column);
390
+ }
391
+ currentColumnsData.forEach(c => setInRows(0, c));
392
+ return rows;
393
+ };
394
+
395
+ /**
396
+ * Creates row array for internal use
397
+ * - Avoid mutating user's prop
398
+ */
399
+ const createRows = (forFooter) => {
400
+ const newId = idCreator(forFooter ? "fr" : "br");
401
+ const currentRows = forFooter ? props.footerRows : props.rows;
402
+ return currentRows ? currentRows.map(row => ({
403
+ height: null,
404
+ boxHeight: null,
405
+ data: row,
406
+ id: newId()
407
+ })) : [];
408
+ };
409
+
410
+ const currentColumns = ref(createColumns());
411
+ const currentRows = ref(createRows());
412
+ const currentFooterRows = ref(createRows(true));
413
+ const headerRows = ref(createHeaderRows(currentColumns.value));
414
+ const sizesCalculated = ref(false);
415
+ const tableWidth = ref("auto");
416
+ const resizing = ref(false);
417
+ const overflownX = ref(false);
418
+ const canScrollLeft = ref(false);
419
+ const canScrollRight = ref(false);
420
+ const sizesPainted = ref(false);
421
+
422
+ let columnResizeObserver = null;
423
+ if (!SSR) {
424
+ columnResizeObserver = new ResizeObserver(() => onColumnResize());
425
+ }
426
+
427
+ const controlsShown = computed(() => props.scrollControls && overflownX.value);
428
+ const headerVisibleX = computed(() => sizesCalculated.value && overflownX.value);
429
+ const headerOpacityX = computed(() => headerVisibleX.value ? "1" : "0");
430
+
431
+ /**
432
+ * Used to output the body rows. This is an array of only the deepest child columns
433
+ * parent column information can be accessed by reference
434
+ */
435
+ const rowColumns = computed(() => {
436
+ const columns = currentColumns.value;
437
+ const rc = [];
438
+ const add = c => {
439
+ if (c.columns) c.columns.forEach(add);
440
+ else rc.push(c);
441
+ };
442
+ // Create array of actual
443
+ columns.forEach(add);
444
+
445
+ // Iterate over all columns checking for rowHeader
446
+ // - If a column has row header create an id function passed current row's index
447
+ // - Store callbacks in an array to call on each rows cells
448
+ let rowHeaders = [];
449
+ rc.forEach((c, columnIndex) => {
450
+ // Creating copy of array here so it doesn't include it's own ID and also
451
+ // so there can be headers of headers going from left to right only
452
+ const thisRowsHeader = rowHeaders.slice();
453
+ c.getRowHeaders = rowIndex => thisRowsHeader.map(cb => cb(rowIndex)).join(" ");
454
+ // Now we add this columns row header function
455
+ // Which will be included in all columns after this iteration
456
+ if (c.rowHeader) {
457
+ c.getRowHeaderId = rowIndex => `${ props.idPrefix }-rh-${ rowIndex }-${ columnIndex }`;
458
+ rowHeaders.push(c.getRowHeaderId);
459
+ }
460
+ });
461
+ return rc;
462
+ });
463
+
464
+ const headerHeight = computed(() => {
465
+ // Offset height would be the combination of all the rows height's
466
+ return headerRows.value.reduce((a, r) => a + r.boxHeight, 0);
467
+ });
468
+
469
+ /**
470
+ * Reduce the array of column header rows to the first row, first column
471
+ */
472
+ const headerRowsFirst = computed(() => {
473
+ const firstRow = headerRows.value[0];
474
+ const firstColumn = Object.assign({}, firstRow.columns[0], { rowspan: 1, colspan: 1 });
475
+ const columns = [ firstColumn ];
476
+ return [{
477
+ ...firstRow,
478
+ columns,
479
+ boxHeight: headerHeight.value,
480
+ height: `${ headerHeight.value }px`
481
+ }];
482
+ });
483
+
484
+ /**
485
+ * Reduce the rowColumn array to only the first column
486
+ */
487
+ const rowColumnsFirst = computed(() => {
488
+ return [ rowColumns.value[0] ];
489
+ });
490
+
491
+ const firstColumnSize = computed(() => {
492
+ const height = headerRowsFirst.value[0].height;
493
+ const width = headerRows.value[0].columns[0].width;
494
+ return { width, height };
495
+ });
496
+
497
+ const resetSorts = (except) => {
498
+ const resetSort = cols => {
499
+ cols.forEach(col => {
500
+ if (except.key !== col.key) {
501
+ col.sortApplied = false;
502
+ col.sortAscending = false;
503
+ }
504
+ if (col.columns) {
505
+ resetSort(col.columns);
506
+ }
507
+ });
508
+ };
509
+ resetSort(currentColumns.value);
510
+ };
511
+
512
+ const applySort = (column) => {
513
+ resetSorts(column);
514
+ if (column.sortApplied) {
515
+ column.sortAscending = !column.sortAscending;
516
+ } else {
517
+ column.sortApplied = true;
518
+ }
519
+ emit("column-sort", column);
520
+ };
521
+
522
+ const onColumnResize = () => {
523
+ if (sizesPainted.value) {
524
+ refresh();
525
+ }
526
+ };
527
+
528
+ const headerAdded = (el) => {
529
+ if (!SSR && columnResizeObserver) {
530
+ columnResizeObserver.observe(el);
531
+ }
532
+ };
533
+
534
+ const headerRemoved = (el) => {
535
+ if (!SSR && columnResizeObserver) {
536
+ columnResizeObserver.unobserve(el);
537
+ }
538
+ };
539
+
540
+ /**
541
+ * Allow classes options to be strings or functions
542
+ */
543
+ const resolveClasses = (passed, args = null) => {
544
+ if (typeof passed === "undefined") return;
545
+ if (typeof passed === "function") {
546
+ return passed(args);
547
+ }
548
+ return passed;
549
+ };
550
+
551
+ /**
552
+ * Handles horizontal scroll
553
+ * - Shifts the first column as the user scrolls
554
+ */
555
+ const syncScrollLeft = () => {
556
+ if (display.value && header.value) {
557
+ const left = display.value.scrollLeft;
558
+ header.value.$el.style.transform = `translateX(-${ left }px)`;
559
+ }
560
+ };
561
+
562
+ /**
563
+ * Checks and sets state if the table is overflow horizontally
564
+ */
565
+ const checkOverflowX = () => {
566
+ if (display.value) {
567
+ overflownX.value = display.value.scrollWidth > display.value.clientWidth;
568
+ }
569
+ };
570
+
571
+ /**
572
+ * Checks whether if the tables scroll position is at the start or end and updates state
573
+ */
574
+ const checkScrollability = () => {
575
+ if (!overflownX.value || !display.value) return;
576
+ const element = display.value;
577
+ canScrollLeft.value = element.scrollLeft > 0;
578
+ canScrollRight.value = element.clientWidth + element.scrollLeft < element.scrollWidth;
579
+ };
580
+
581
+ const onResize = () => {
582
+ if (SSR) return;
583
+ const newWindowWidth = getWindowWidth();
584
+ if (windowWidth === newWindowWidth) return;
585
+ windowWidth = newWindowWidth;
586
+
587
+ // Called when the resize event is first fired (before change)
588
+ if (!resizing.value) {
589
+ resizing.value = true;
590
+ removeTableSizes();
591
+ } else {
592
+ resizing.value = false;
593
+ setTableSizes();
594
+ checkOverflowX();
595
+ syncScrollLeft();
596
+ }
597
+ };
598
+
599
+ const resizeHandler = debounce(onResize, 500, true);
600
+
601
+ /**
602
+ * Method to update the table (sizes, etc) when data has changed
603
+ */
604
+ const refresh = () => {
605
+ if (SSR) return;
606
+ removeTableSizes();
607
+ nextTick(() => {
608
+ setTableSizes();
609
+ checkOverflowX();
610
+ checkScrollability();
611
+ syncScrollLeft();
612
+ });
613
+ };
614
+
615
+ const onScrollX = () => {
616
+ checkScrollability();
617
+ syncScrollLeft();
618
+ };
619
+
620
+ const handlerScrollX = onScrollX; // Note: Non-reactive property
621
+ const handlerScrollY = () => {}; // Used to match the old event listener mapping if touchmove was registered globally
622
+
623
+ /**
624
+ * Method to attach handlers needed after creation
625
+ */
626
+ const attachHandlers = () => {
627
+ // this.handlerScrollX = this.throttleScroll(this.onScrollX); // Note: Non-reactive property
628
+ if (display.value) {
629
+ display.value.addEventListener("scroll", handlerScrollX);
630
+ }
631
+ if (props.scrollContext) {
632
+ props.scrollContext.addEventListener("touchmove", handlerScrollY);
633
+ }
634
+ window.addEventListener("resize", resizeHandler);
635
+ };
636
+
637
+ const removeHandlers = () => {
638
+ if (display.value) {
639
+ display.value.removeEventListener("scroll", handlerScrollX);
640
+ }
641
+ if (props.scrollContext) {
642
+ props.scrollContext.removeEventListener("scroll", handlerScrollY);
643
+ props.scrollContext.removeEventListener("touchmove", handlerScrollY);
644
+ }
645
+ window.removeEventListener("resize", resizeHandler);
646
+ };
647
+
648
+ const removeTableSizes = () => {
649
+ sizesPainted.value = false;
650
+ sizesCalculated.value = false;
651
+ const setRowHeight = row => {
652
+ row.boxHeight = null;
653
+ row.height = "auto";
654
+ };
655
+ tableWidth.value = "auto";
656
+ headerRows.value.forEach(row => {
657
+ setRowHeight(row);
658
+ row.columns.forEach(column => {
659
+ column.boxWidth = null;
660
+ column.width = "auto";
661
+ });
662
+ });
663
+ if (props.firstColumnSticky) {
664
+ currentRows.value.forEach(row => setRowHeight(row));
665
+ currentFooterRows.value.forEach(row => setRowHeight(row));
666
+ }
667
+ };
668
+
669
+ const scrollLeft = () => {
670
+ const element = display.value;
671
+ if (!element) return;
672
+ const sLeft = element.scrollLeft;
673
+ const amount = props.scrollControlAmount;
674
+ const toScroll = sLeft - amount;
675
+
676
+ element.scroll({
677
+ left: toScroll < 0 ? 0 : toScroll,
678
+ behavior: "smooth"
679
+ });
680
+ };
681
+
682
+ const scrollRight = () => {
683
+ const element = display.value;
684
+ if (!element) return;
685
+ const scrollWidth = element.scrollWidth;
686
+ const sLeft = element.scrollLeft;
687
+ const amount = props.scrollControlAmount;
688
+ const toScroll = sLeft + amount;
689
+ // If amount would be greater than scrollable area
690
+ // scroll to end
691
+ // element.scrollLeft = element.scrollWidth;
692
+ element.scroll({
693
+ left: toScroll > scrollWidth ? scrollWidth : toScroll,
694
+ behavior: "smooth"
695
+ });
696
+ };
697
+
698
+ /**
699
+ * Cleanup function for when component is not in use
700
+ */
701
+ const setTableSizes = () => {
702
+ if (SSR) return;
703
+ // Set the table and it's cloned header to the exact same width
704
+ const size = (element, key) => Math.ceil(element.getBoundingClientRect()[key]);
705
+ if (table.value && table.value.$el) {
706
+ tableWidth.value = `${ size(table.value.$el, "width") }px`;
707
+ }
708
+ const getElement = object => document.getElementById(object.id);
709
+
710
+ const setRowHeight = row => {
711
+ const element = getElement(row);
712
+ // Ensure element still exists, sometimes (only seen in storybook the element
713
+ // is removed before the unmounted/beforeUnmounted hook), this prevents the error
714
+ // for missing element. #mounted-no-element
715
+ if (element) {
716
+ row.boxHeight = size(element, "height");
717
+ row.height = `${ row.boxHeight }px`;
771
718
  }
719
+ };
720
+
721
+ // Set the tables header <tr> and <th> to their rendered sizes
722
+ // By measuring each and updating it's column object data
723
+ // reactively updating all the cloned versions
724
+ headerRows.value.forEach(row => {
725
+ setRowHeight(row);
726
+ row.columns.forEach(column => {
727
+ const element = getElement(column);
728
+ // See #mounted-no-element
729
+ if (element) {
730
+ column.boxWidth = size(element, "width");
731
+ column.width = `${ column.boxWidth }px`;
732
+ }
733
+ });
734
+ });
735
+
736
+ // If first column sticky the plugin needs to set
737
+ // each row's height so the cloned column matches
738
+ if (props.firstColumnSticky) {
739
+ currentRows.value.forEach(row => setRowHeight(row));
740
+ currentFooterRows.value.forEach(row => setRowHeight(row));
772
741
  }
742
+ nextTick(() => {
743
+ sizesCalculated.value = true;
744
+ runAfterFramePaint(() => {
745
+ sizesPainted.value = true;
746
+ });
747
+ });
773
748
  };
749
+
750
+ const tableReady = () => {
751
+ setTableSizes();
752
+ };
753
+
754
+ /**
755
+ * Creates a new throttled scroll handler
756
+ */
757
+ // const throttleScroll = (handler) => {
758
+ // let id = null;
759
+ // // Old Fired after frame
760
+ // return (event) => {
761
+ // if (id) window.cancelAnimationFrame(id);
762
+ // id = window.requestAnimationFrame(() => handler(event));
763
+ // };
764
+ // };
765
+
766
+ watch(() => props.columns, () => {
767
+ currentColumns.value = createColumns();
768
+ headerRows.value = createHeaderRows(currentColumns.value);
769
+ refresh();
770
+ }, { deep: true });
771
+
772
+ watch(() => props.rows, () => {
773
+ currentRows.value = createRows();
774
+ refresh();
775
+ }, { deep: true });
776
+
777
+ watch(() => props.footerRows, () => {
778
+ currentFooterRows.value = createRows(true);
779
+ refresh();
780
+ }, { deep: true });
781
+
782
+ onMounted(() => {
783
+ if (!SSR) {
784
+ attachHandlers();
785
+ checkOverflowX();
786
+ checkScrollability();
787
+ }
788
+ });
789
+
790
+ onBeforeUnmount(() => {
791
+ if (!SSR) {
792
+ removeHandlers();
793
+ if (columnResizeObserver) {
794
+ // Allow resizer to be Garbage Collected
795
+ columnResizeObserver.disconnect();
796
+ columnResizeObserver = null;
797
+ }
798
+ }
799
+ });
774
800
  </script>
775
801
 
776
802
  <!--