@ulu/frontend-vue 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/dist/breakpoints-ClT9bfZm.js +211 -0
  4. package/dist/frontend-vue.css +1 -0
  5. package/dist/frontend-vue.js +82 -0
  6. package/dist/frontend-vue.umd.cjs +561 -0
  7. package/dist/index-P5Rwl_Dl.js +7263 -0
  8. package/dist/index.es-HlG3u0J5.js +3134 -0
  9. package/lib/_index.scss +14 -0
  10. package/lib/components/_index.scss +6 -0
  11. package/lib/components/collapsible/UluAccordion.vue +82 -0
  12. package/lib/components/collapsible/UluCollapsibleRegion.vue +278 -0
  13. package/lib/components/collapsible/UluDropdown.vue +42 -0
  14. package/lib/components/collapsible/UluModal.vue +384 -0
  15. package/lib/components/collapsible/UluOverflowPopover.vue +52 -0
  16. package/lib/components/collapsible/UluTab.vue +9 -0
  17. package/lib/components/collapsible/UluTabGroup.vue +31 -0
  18. package/lib/components/collapsible/UluTabList.vue +9 -0
  19. package/lib/components/collapsible/UluTabPanel.vue +9 -0
  20. package/lib/components/collapsible/UluTabPanels.vue +9 -0
  21. package/lib/components/elements/UluAlert.vue +81 -0
  22. package/lib/components/elements/UluBadge.vue +58 -0
  23. package/lib/components/elements/UluBadgeStack.vue +27 -0
  24. package/lib/components/elements/UluButton.vue +161 -0
  25. package/lib/components/elements/UluCallout.vue +30 -0
  26. package/lib/components/elements/UluCard.vue +241 -0
  27. package/lib/components/elements/UluDefinitionList.vue +40 -0
  28. package/lib/components/elements/UluExternalLink.vue +47 -0
  29. package/lib/components/elements/UluIcon.vue +108 -0
  30. package/lib/components/elements/UluList.vue +87 -0
  31. package/lib/components/elements/UluMain.vue +5 -0
  32. package/lib/components/elements/UluSpokeSpinner.vue +25 -0
  33. package/lib/components/elements/UluTag.vue +53 -0
  34. package/lib/components/forms/UluCheckboxMenu.vue +36 -0
  35. package/lib/components/forms/UluFileDisplay.vue +39 -0
  36. package/lib/components/forms/UluFormDropzone.vue +62 -0
  37. package/lib/components/forms/UluFormFile.vue +47 -0
  38. package/lib/components/forms/UluFormMessage.vue +20 -0
  39. package/lib/components/forms/UluFormSelect.vue +37 -0
  40. package/lib/components/forms/UluFormText.vue +32 -0
  41. package/lib/components/forms/UluSearchForm.vue +31 -0
  42. package/lib/components/index.js +54 -0
  43. package/lib/components/layout/UluAdaptiveLayout.vue +11 -0
  44. package/lib/components/layout/UluDataGrid.vue +41 -0
  45. package/lib/components/layout/UluTitleRail.vue +56 -0
  46. package/lib/components/layout/UluWhenBreakpoint.vue +86 -0
  47. package/lib/components/navigation/UluBreadcrumb.vue +72 -0
  48. package/lib/components/navigation/UluMenu.vue +105 -0
  49. package/lib/components/navigation/UluMenuStack.vue +49 -0
  50. package/lib/components/navigation/UluNavStrip.vue +48 -0
  51. package/lib/components/navigation/UluSkipLink.vue +5 -0
  52. package/lib/components/systems/facets/UluFacets.vue +380 -0
  53. package/lib/components/systems/facets/UluFacetsList.vue +39 -0
  54. package/lib/components/systems/facets/UluFacetsSearch.vue +67 -0
  55. package/lib/components/systems/facets/_facets.scss +64 -0
  56. package/lib/components/systems/index.js +17 -0
  57. package/lib/components/systems/scroll-anchors/UluScrollAnchors.vue +152 -0
  58. package/lib/components/systems/scroll-anchors/UluScrollAnchorsNav.vue +37 -0
  59. package/lib/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue +124 -0
  60. package/lib/components/systems/scroll-anchors/UluScrollAnchorsSection.vue +63 -0
  61. package/lib/components/systems/scroll-anchors/symbols.js +6 -0
  62. package/lib/components/systems/skeleton/UluShowSkeleton.vue +13 -0
  63. package/lib/components/systems/skeleton/UluSkeletonContent.vue +60 -0
  64. package/lib/components/systems/skeleton/UluSkeletonMedia.vue +11 -0
  65. package/lib/components/systems/skeleton/UluSkeletonTextInline.vue +9 -0
  66. package/lib/components/systems/slider/UluImageSlideShow.vue +75 -0
  67. package/lib/components/systems/slider/UluSlideShow.vue +331 -0
  68. package/lib/components/systems/slider/UluSlideShowSlide.vue +25 -0
  69. package/lib/components/systems/table-sticky/UluTableSticky.vue +793 -0
  70. package/lib/components/systems/table-sticky/UluTableStickyRows.vue +73 -0
  71. package/lib/components/systems/table-sticky/UluTableStickyTable.vue +237 -0
  72. package/lib/components/systems/table-sticky/_table-sticky.scss +185 -0
  73. package/lib/components/utils/UluCondText.vue +28 -0
  74. package/lib/components/utils/UluEmpty.vue +3 -0
  75. package/lib/components/utils/UluEmptyView.vue +3 -0
  76. package/lib/components/utils/UluPlaceholderImage.vue +53 -0
  77. package/lib/components/utils/UluPlaceholderText.vue +25 -0
  78. package/lib/components/utils/UluRouteAnnouncer.vue +83 -0
  79. package/lib/components/visualizations/UluAnimateNumber.vue +32 -0
  80. package/lib/components/visualizations/UluProgressBar.vue +94 -0
  81. package/lib/components/visualizations/UluProgressDonut.vue +97 -0
  82. package/lib/composables/index.js +10 -0
  83. package/lib/composables/useBreakpointManager.js +68 -0
  84. package/lib/composables/useIcon.js +62 -0
  85. package/lib/composables/useModifiers.js +93 -0
  86. package/lib/composables/useWindowResize.js +64 -0
  87. package/lib/index.js +10 -0
  88. package/lib/plugins/_index.scss +7 -0
  89. package/lib/plugins/breakpoints/index.js +47 -0
  90. package/lib/plugins/index.js +11 -0
  91. package/lib/plugins/modals/UluModalsDisplay.vue +59 -0
  92. package/lib/plugins/modals/api.js +76 -0
  93. package/lib/plugins/modals/index.js +60 -0
  94. package/lib/plugins/modals/useModals.js +9 -0
  95. package/lib/plugins/popovers/UluPopover.vue +189 -0
  96. package/lib/plugins/popovers/UluTooltipDisplay.vue +15 -0
  97. package/lib/plugins/popovers/UluTooltipPopover.vue +83 -0
  98. package/lib/plugins/popovers/defaults.js +108 -0
  99. package/lib/plugins/popovers/directive.js +95 -0
  100. package/lib/plugins/popovers/index.js +18 -0
  101. package/lib/plugins/popovers/manager.js +54 -0
  102. package/lib/plugins/popovers/useFollow.js +80 -0
  103. package/lib/plugins/popovers/utils.js +5 -0
  104. package/lib/plugins/toast/UluToast.vue +87 -0
  105. package/lib/plugins/toast/UluToastDisplay.vue +35 -0
  106. package/lib/plugins/toast/_toast.scss +198 -0
  107. package/lib/plugins/toast/defaults.js +30 -0
  108. package/lib/plugins/toast/index.js +17 -0
  109. package/lib/plugins/toast/store.js +71 -0
  110. package/lib/plugins/toast/useToast.js +18 -0
  111. package/lib/settings.js +119 -0
  112. package/lib/utils/dom.js +14 -0
  113. package/lib/utils/placeholder.js +6 -0
  114. package/lib/utils/vue-router.js +219 -0
  115. package/package.json +75 -0
@@ -0,0 +1,793 @@
1
+ <!--
2
+ Version: 2.0.6
3
+
4
+ Updates:
5
+ - 2.0.6 Moved to ulu library and refactored, styles moved to frontend
6
+ - 2.0.5 Updated for SSR in vue
7
+ - 2.0.4 Add sorting, Removed all component options (in table values/titles)
8
+ - Added ResizeObserver incase cells change width (inner component changes, interactive, etc)
9
+ - Only on table actual header for now (performance)
10
+ - Added slotHeader for templating columns
11
+ - Added deep watchers for columns and rows props, so we can update is they are altered
12
+ - Added the ability to use a custom component inside any table cell column.component and column.componentHeader
13
+ - Added ability to pass classes object for adding classes to certain inner elements (table, controls, controlButton)
14
+ - Add ability to pass "controlsComponent" to use custom component for controls
15
+ - Add resolveClasses so that all classes can be functions (useful for things like rows that don't have configuration like column cells)
16
+ Todo:
17
+ - Should probably use global counters for id creation so they are renewed with new id's every time the table re-renders. Not sure
18
+ how this would behave with a screen reader but either way the id's have changed (they won't line up to preexisting id's, guess that
19
+ could be done but would be complicated), I think using an id that never exists will make sure all the connections works ie attr. headers
20
+ - In the future the API for the data should be clearer (ie. row.data is sometimes row to the user), feels weird
21
+ Warning:
22
+ - This version has the vis
23
+ -->
24
+ <template>
25
+ <div class="table-sticky" :class="{
26
+ 'table-sticky--overflown-x' : overflownX,
27
+ 'table-sticky--can-scroll-right' : canScrollRight,
28
+ 'table-sticky--can-scroll-left' : canScrollLeft
29
+ }">
30
+ <div class="table-sticky__sticky-wrap table-sticky__sticky-wrap--header">
31
+ <div class="table-sticky__header-wrap">
32
+ <UluTableStickyTable
33
+ ref="header"
34
+ class="table-sticky__table table-sticky__table--header"
35
+ :classes="classes"
36
+ :caption="caption"
37
+ :resolveClasses="resolveClasses"
38
+ :getColumnTitle="getColumnTitle"
39
+ :idPrefix="idPrefix"
40
+ :headerRows="headerRows"
41
+ :style="{
42
+ opacity: sizesCalculated ? '1' : '0',
43
+ pointerEvents: sizesCalculated ? 'auto' : 'none',
44
+ width: tableWidth
45
+ }"
46
+ @columnSorted="applySort"
47
+ >
48
+ <template v-for="(_, name) in $slots" v-slot:[name]="slotData">
49
+ <slot :name="name" v-bind="slotData" />
50
+ </template>
51
+ </UluTableStickyTable>
52
+ </div>
53
+ </div>
54
+ <div class="table-sticky__sticky-wrap table-sticky__sticky-wrap--first-column-header">
55
+ <UluTableStickyTable
56
+ v-if="firstColumnSticky"
57
+ ref="firstColumnHeader"
58
+ class="table-sticky__table table-sticky__table--first-column-header"
59
+ :classes="classes"
60
+ :caption="caption"
61
+ :resolveClasses="resolveClasses"
62
+ :getColumnTitle="getColumnTitle"
63
+ :idPrefix="idPrefix"
64
+ :headerRows="headerRowsFirst"
65
+ :style="{
66
+ opacity: headerOpacityX,
67
+ pointerEvents: headerVisibleX ? 'auto' : 'none'
68
+ }"
69
+ @columnSorted="applySort"
70
+ >
71
+ <template v-for="(_, name) in $slots" v-slot:[name]="slotData">
72
+ <slot :name="name" v-bind="slotData" />
73
+ </template>
74
+ </UluTableStickyTable>
75
+ </div>
76
+ <div class="table-sticky__sticky-wrap table-sticky__sticky-wrap--controls">
77
+ <div
78
+ class="table-sticky__controls"
79
+ :class="resolveClasses(classes.controls)"
80
+ ref="controls"
81
+ v-show="controlsShown"
82
+ >
83
+ <slot
84
+ v-if="$slots.controls"
85
+ name="controls"
86
+ :scrollLeft="scrollLeft"
87
+ :scrollRight="scrollRight"
88
+ :canScrollLeft="canScrollLeft"
89
+ :canScrollRight="canScrollRight"
90
+ />
91
+ <component
92
+ v-else-if="controlsComponent"
93
+ :is="controlsComponent"
94
+ :scrollLeft="scrollLeft"
95
+ :scrollRight="scrollRight"
96
+ :canScrollLeft="canScrollLeft"
97
+ :canScrollRight="canScrollRight"
98
+ />
99
+ <div v-else class="table-sticky__controls-inner">
100
+ <button
101
+ class="table-sticky__control table-sticky__control--left"
102
+ aria-label="Scroll Left"
103
+ @click="scrollLeft"
104
+ :class="resolveClasses(classes.controlButton)"
105
+ :disabled="!canScrollLeft"
106
+ >
107
+ <slot name="controlLeft">
108
+ &larr;
109
+ </slot>
110
+ </button>
111
+ <button
112
+ class="table-sticky__control table-sticky__control--right"
113
+ aria-label="Scroll Right"
114
+ @click="scrollRight"
115
+ :class="resolveClasses(classes.controlButton)"
116
+ :disabled="!canScrollRight"
117
+ >
118
+ <slot name="controlRight">
119
+ &rarr;
120
+ </slot>
121
+ </button>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ <div ref="display" class="table-sticky__display">
126
+ <UluTableStickyTable
127
+ ref="table"
128
+ class="table-sticky__table table-sticky__table--actual"
129
+ :classes="classes"
130
+ :resolveClasses="resolveClasses"
131
+ isActual
132
+ :headerRows="headerRows"
133
+ :rows="currentRows"
134
+ :footerRows="currentFooterRows"
135
+ :rowColumns="rowColumns"
136
+ :caption="caption"
137
+ :idPrefix="idPrefix"
138
+ :getRowValue="getRowValue"
139
+ :getColumnTitle="getColumnTitle"
140
+ @vue:mounted="tableReady"
141
+ @actualHeaderRemoved="headerRemoved"
142
+ @actualHeaderAdded="headerAdded"
143
+ @columnSorted="applySort"
144
+ >
145
+ <template v-for="(_, name) in $slots" v-slot:[name]="slotData">
146
+ <slot :name="name" v-bind="slotData" />
147
+ </template>
148
+ </UluTableStickyTable>
149
+ <!-- Scroll Controls (optionally allow user templating via slot passed methods) -->
150
+ </div>
151
+ <UluTableStickyTable
152
+ v-if="firstColumnSticky"
153
+ ref="firstColumn"
154
+ class="table-sticky__table table-sticky__table--first-column"
155
+ :classes="classes"
156
+ :resolveClasses="resolveClasses"
157
+ :caption="caption"
158
+ :headerRows="headerRowsFirst"
159
+ :columnWidth="firstColumnSize.width"
160
+ :rows="currentRows"
161
+ :footerRows="currentFooterRows"
162
+ :rowColumns="rowColumnsFirst"
163
+ :idPrefix="idPrefix"
164
+ :getRowValue="getRowValue"
165
+ :getColumnTitle="getColumnTitle"
166
+ :style="{
167
+ opacity: headerOpacityX,
168
+ pointerEvents: headerVisibleX ? 'auto' : 'none'
169
+ }"
170
+ @columnSorted="applySort"
171
+ >
172
+ <template v-for="(_, name) in $slots" v-slot:[name]="slotData">
173
+ <slot :name="name" v-bind="slotData" />
174
+ </template>
175
+ </UluTableStickyTable>
176
+ </div>
177
+ </template>
178
+
179
+ <script>
180
+ import UluTableStickyTable from "./UluTableStickyTable.vue";
181
+ import { debounce } from "@ulu/utils/performance.js";
182
+ import { runAfterFramePaint } from "@ulu/utils/browser/performance.js";
183
+ import cloneDeep from "lodash/cloneDeep";
184
+
185
+ const arrayOfObjects = a => a.every(o => typeof o === "object");
186
+ const required = true;
187
+ const SSR = import.meta.env.SSR;
188
+ const getWindowWidth = () => SSR ? 0 : window.innerWidth;
189
+ // Used to make sure resize events on ios (when address bar collapses)
190
+ // don't trigger recalculations
191
+ let windowWidth = getWindowWidth();
192
+
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
202
+ },
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
+ }
308
+ },
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
+ };
327
+ },
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
+ }
351
+ },
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
+ }
423
+ },
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("columnSort", 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();
596
+ } 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));
665
+ }
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
+ }
760
+ },
761
+ beforeUnmount() {
762
+ if (!SSR) {
763
+ this.removeHandlers();
764
+ }
765
+ },
766
+ unmounted() {
767
+ if (!SSR) {
768
+ // Allow resizer to be GC
769
+ this.columnResizeObserver.disconnect();
770
+ this.columnResizeObserver = null;
771
+ }
772
+ }
773
+ };
774
+ </script>
775
+
776
+ <!--
777
+ Issues to Avoid:
778
+ - If we allow the user to pass components in column config, it will cause errors and performance issues when watching the column config for 'deep' changes
779
+ - Thoughts:
780
+ - 1. remove component option
781
+ - This would be fine and simplify the component, the user can already use slots to pass their own component. They could always
782
+ set up their own wrapper component for the UluTableSticky with the slot templates routed to the preferred components
783
+ - 2. Allow component option but only strings and have another property that is a lookup of components (shallow, markRaw)
784
+ - I think this is the best approach, as it is an advanced option
785
+ - 3. Remove the deep watch and force the user to have to provide a new array when changing column config (and probably row)
786
+ - Don't like this because, I think the table may redraw completely, but then again we are cloning the columns, so the whole
787
+ table is redrawn anyways
788
+ - We should probably avoid having components in objects anyways
789
+ - Decided this is not nessassary with slots the user can setup their own components
790
+ - Removed b/c bad practice to have components in reactive data (that's watched)
791
+ - Future this could be prop instead of in column.config
792
+ - Or this can be refactored to just use the props directly and keep the data for the plugin seperate but related
793
+ -->