@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.
- package/dist/components/collapsible/UluAccordionGroup.vue.d.ts +2 -2
- package/dist/components/collapsible/UluAccordionGroup.vue.d.ts.map +1 -1
- package/dist/components/collapsible/UluAccordionGroup.vue.js +22 -19
- package/dist/components/collapsible/UluDropdown.vue.d.ts +1 -1
- package/dist/components/collapsible/UluDropdown.vue.d.ts.map +1 -1
- package/dist/components/collapsible/UluDropdown.vue.js +22 -15
- package/dist/components/collapsible/UluModal.vue.d.ts +43 -248
- package/dist/components/collapsible/UluModal.vue.d.ts.map +1 -1
- package/dist/components/collapsible/UluModal.vue.js +139 -191
- package/dist/components/collapsible/UluTabGroup.vue.d.ts +2 -0
- package/dist/components/collapsible/UluTabGroup.vue.d.ts.map +1 -1
- package/dist/components/collapsible/UluTabGroup.vue.js +23 -14
- package/dist/components/elements/UluAlert.vue.d.ts +29 -144
- package/dist/components/elements/UluAlert.vue.d.ts.map +1 -1
- package/dist/components/elements/UluAlert.vue.js +39 -50
- package/dist/components/elements/UluBadge.vue.d.ts +6 -6
- package/dist/components/elements/UluBadgeStack.vue.d.ts +1 -1
- package/dist/components/elements/UluBadgeStack.vue.d.ts.map +1 -1
- package/dist/components/elements/UluBadgeStack.vue.js +12 -9
- package/dist/components/elements/UluButton.vue.d.ts +47 -177
- package/dist/components/elements/UluButton.vue.d.ts.map +1 -1
- package/dist/components/elements/UluButton.vue.js +59 -72
- package/dist/components/elements/UluButtonVerbose.vue.d.ts +38 -123
- package/dist/components/elements/UluButtonVerbose.vue.d.ts.map +1 -1
- package/dist/components/elements/UluButtonVerbose.vue.js +52 -65
- package/dist/components/elements/UluCallout.vue.d.ts +20 -25
- package/dist/components/elements/UluCallout.vue.d.ts.map +1 -1
- package/dist/components/elements/UluCallout.vue.js +11 -16
- package/dist/components/elements/UluCaptionedFigure.vue.d.ts +25 -0
- package/dist/components/elements/UluCaptionedFigure.vue.d.ts.map +1 -0
- package/dist/components/elements/UluCaptionedFigure.vue.js +48 -0
- package/dist/components/elements/UluCard.vue.d.ts +2 -2
- package/dist/components/elements/UluDefinitionList.vue.d.ts +4 -2
- package/dist/components/elements/UluDefinitionList.vue.d.ts.map +1 -1
- package/dist/components/elements/UluDefinitionList.vue.js +32 -28
- package/dist/components/elements/UluExternalLink.vue.d.ts +2 -2
- package/dist/components/elements/UluImage.vue.d.ts +14 -0
- package/dist/components/elements/UluImage.vue.d.ts.map +1 -0
- package/dist/components/elements/UluImage.vue.js +53 -0
- package/dist/components/elements/UluList.vue.d.ts.map +1 -1
- package/dist/components/elements/UluList.vue.js +14 -13
- package/dist/components/elements/UluOverflowScroller.vue.d.ts +49 -0
- package/dist/components/elements/UluOverflowScroller.vue.d.ts.map +1 -0
- package/dist/components/elements/UluOverflowScroller.vue.js +138 -0
- package/dist/components/elements/UluScrollSlider.vue.d.ts +38 -0
- package/dist/components/elements/UluScrollSlider.vue.d.ts.map +1 -0
- package/dist/components/elements/UluScrollSlider.vue.js +146 -0
- package/dist/components/elements/UluSlider.vue.d.ts +57 -0
- package/dist/components/elements/UluSlider.vue.d.ts.map +1 -0
- package/dist/components/elements/UluSlider.vue.js +277 -0
- package/dist/components/forms/UluFormFile.vue.d.ts +2 -2
- package/dist/components/forms/UluFormRadio.vue.d.ts +4 -4
- package/dist/components/index.d.ts +6 -0
- package/dist/components/layout/UluTitleRail.vue.d.ts +29 -87
- package/dist/components/layout/UluTitleRail.vue.d.ts.map +1 -1
- package/dist/components/layout/UluTitleRail.vue.js +51 -46
- package/dist/components/navigation/UluBreadcrumb.vue.d.ts +27 -68
- package/dist/components/navigation/UluBreadcrumb.vue.d.ts.map +1 -1
- package/dist/components/navigation/UluBreadcrumb.vue.js +51 -54
- package/dist/components/navigation/UluMenu.vue.d.ts +30 -138
- package/dist/components/navigation/UluMenu.vue.d.ts.map +1 -1
- package/dist/components/navigation/UluMenu.vue.js +85 -84
- package/dist/components/navigation/UluMenuStack.vue.d.ts +12 -2
- package/dist/components/navigation/UluMenuStack.vue.d.ts.map +1 -1
- package/dist/components/navigation/UluMenuStack.vue.js +26 -18
- package/dist/components/navigation/UluNavStrip.vue.d.ts +22 -134
- package/dist/components/navigation/UluNavStrip.vue.d.ts.map +1 -1
- package/dist/components/navigation/UluNavStrip.vue.js +43 -31
- package/dist/components/systems/facets/UluFacetsSidebarLayout.vue.js +10 -10
- package/dist/components/systems/facets/useFacets.d.ts +3 -0
- package/dist/components/systems/facets/useFacets.d.ts.map +1 -1
- package/dist/components/systems/facets/useFacets.js +124 -112
- package/dist/components/systems/index.d.ts +0 -3
- package/dist/components/systems/scroll-anchors/UluScrollAnchors.vue.d.ts +2 -2
- package/dist/components/systems/table-sticky/UluTableSticky.vue.d.ts +504 -432
- package/dist/components/systems/table-sticky/UluTableSticky.vue.d.ts.map +1 -1
- package/dist/components/systems/table-sticky/UluTableSticky.vue.js +313 -456
- package/dist/components/systems/table-sticky/UluTableStickyRows.vue.d.ts +40 -31
- package/dist/components/systems/table-sticky/UluTableStickyRows.vue.d.ts.map +1 -1
- package/dist/components/systems/table-sticky/UluTableStickyRows.vue.js +43 -45
- package/dist/components/systems/table-sticky/UluTableStickyTable.vue.d.ts +60 -146
- package/dist/components/systems/table-sticky/UluTableStickyTable.vue.d.ts.map +1 -1
- package/dist/components/systems/table-sticky/UluTableStickyTable.vue.js +156 -175
- package/dist/components/utils/UluAction.vue.d.ts +36 -0
- package/dist/components/utils/UluAction.vue.d.ts.map +1 -0
- package/dist/components/utils/UluAction.vue.js +59 -0
- package/dist/components/utils/UluConditionalText.vue.d.ts +7 -26
- package/dist/components/utils/UluConditionalText.vue.d.ts.map +1 -1
- package/dist/components/utils/UluConditionalText.vue.js +12 -14
- package/dist/components/utils/UluConditionalWrapper.vue.d.ts.map +1 -1
- package/dist/components/utils/UluConditionalWrapper.vue.js +11 -9
- package/dist/components/utils/UluPlaceholderImage.vue.d.ts +12 -57
- package/dist/components/utils/UluPlaceholderImage.vue.d.ts.map +1 -1
- package/dist/components/utils/UluPlaceholderImage.vue.js +18 -26
- package/dist/components/utils/UluPlaceholderText.vue.d.ts +6 -20
- package/dist/components/utils/UluPlaceholderText.vue.js +12 -14
- package/dist/components/utils/UluRouteAnnouncer.vue.d.ts +9 -58
- package/dist/components/utils/UluRouteAnnouncer.vue.d.ts.map +1 -1
- package/dist/components/utils/UluRouteAnnouncer.vue.js +28 -28
- package/dist/components/visualizations/UluAnimateNumber.vue.d.ts +20 -14
- package/dist/components/visualizations/UluAnimateNumber.vue.d.ts.map +1 -1
- package/dist/components/visualizations/UluAnimateNumber.vue.js +18 -26
- package/dist/components/visualizations/UluProgressCircle.vue.d.ts +2 -2
- package/dist/composables/useModifiers.d.ts +20 -25
- package/dist/composables/useModifiers.d.ts.map +1 -1
- package/dist/index.js +206 -200
- package/dist/plugins/modals/UluModalsDisplay.vue.d.ts +3 -12
- package/dist/plugins/modals/UluModalsDisplay.vue.js +24 -45
- package/dist/plugins/modals/index.js +6 -6
- package/dist/plugins/toast/UluToast.vue.d.ts +24 -49
- package/dist/plugins/toast/UluToast.vue.d.ts.map +1 -1
- package/dist/plugins/toast/UluToast.vue.js +68 -77
- package/dist/plugins/toast/UluToastDisplay.vue.d.ts +1 -9
- package/dist/plugins/toast/UluToastDisplay.vue.js +27 -35
- package/dist/plugins/toast/defaults.d.ts +40 -35
- package/dist/plugins/toast/defaults.js +2 -2
- package/dist/plugins/toast/index.js +4 -4
- package/dist/plugins/toast/store.d.ts +40 -35
- package/dist/plugins/toast/store.d.ts.map +1 -1
- package/dist/utils/props.d.ts +7 -0
- package/dist/utils/props.d.ts.map +1 -0
- package/dist/utils/props.js +6 -0
- package/lib/components/collapsible/UluAccordionGroup.vue +4 -1
- package/lib/components/collapsible/UluDropdown.vue +5 -1
- package/lib/components/collapsible/UluModal.vue +278 -298
- package/lib/components/collapsible/UluTabGroup.vue +21 -6
- package/lib/components/elements/UluAlert.vue +38 -51
- package/lib/components/elements/UluBadgeStack.vue +4 -1
- package/lib/components/elements/UluButton.vue +105 -129
- package/lib/components/elements/UluButtonVerbose.vue +67 -89
- package/lib/components/elements/UluCallout.vue +15 -19
- package/lib/components/elements/UluCaptionedFigure.vue +40 -0
- package/lib/components/elements/UluDefinitionList.vue +27 -6
- package/lib/components/elements/UluImage.vue +56 -0
- package/lib/components/elements/UluList.vue +1 -0
- package/lib/components/elements/UluOverflowScroller.vue +140 -0
- package/lib/components/elements/UluScrollSlider.vue +150 -0
- package/lib/components/elements/UluSlider.vue +488 -0
- package/lib/components/index.js +10 -0
- package/lib/components/layout/UluTitleRail.vue +55 -48
- package/lib/components/navigation/UluBreadcrumb.vue +29 -34
- package/lib/components/navigation/UluMenu.vue +60 -71
- package/lib/components/navigation/UluMenuStack.vue +6 -1
- package/lib/components/navigation/UluNavStrip.vue +43 -31
- package/lib/components/systems/facets/useFacets.js +33 -17
- package/lib/components/systems/index.js +0 -4
- package/lib/components/systems/table-sticky/UluTableSticky.vue +602 -576
- package/lib/components/systems/table-sticky/UluTableStickyRows.vue +16 -27
- package/lib/components/systems/table-sticky/UluTableStickyTable.vue +95 -96
- package/lib/components/utils/UluAction.vue +81 -0
- package/lib/components/utils/UluConditionalText.vue +13 -16
- package/lib/components/utils/UluConditionalWrapper.vue +5 -1
- package/lib/components/utils/UluPlaceholderImage.vue +44 -46
- package/lib/components/utils/UluPlaceholderText.vue +10 -13
- package/lib/components/utils/UluRouteAnnouncer.vue +59 -47
- package/lib/components/visualizations/UluAnimateNumber.vue +23 -30
- package/lib/composables/useModifiers.js +21 -26
- package/lib/plugins/modals/UluModalsDisplay.vue +44 -45
- package/lib/plugins/toast/UluToast.vue +28 -34
- package/lib/plugins/toast/UluToastDisplay.vue +9 -15
- package/lib/utils/props.js +8 -0
- package/package.json +9 -5
- package/dist/components/systems/slider/UluImageSlideShow.vue.d.ts +0 -130
- package/dist/components/systems/slider/UluImageSlideShow.vue.d.ts.map +0 -1
- package/dist/components/systems/slider/UluImageSlideShow.vue.js +0 -73
- package/dist/components/systems/slider/UluSlideShow.vue.d.ts +0 -205
- package/dist/components/systems/slider/UluSlideShow.vue.d.ts.map +0 -1
- package/dist/components/systems/slider/UluSlideShow.vue.js +0 -292
- package/dist/components/systems/slider/UluSlideShowSlide.vue.d.ts +0 -17
- package/dist/components/systems/slider/UluSlideShowSlide.vue.d.ts.map +0 -1
- package/dist/components/systems/slider/UluSlideShowSlide.vue.js +0 -26
- package/lib/components/systems/slider/UluImageSlideShow.vue +0 -75
- package/lib/components/systems/slider/UluSlideShow.vue +0 -336
- 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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
|
|
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
|
-
|
|
762
|
-
|
|
763
|
-
|
|
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
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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
|
<!--
|