@txstate-mws/svelte-components 1.6.12 → 1.6.14
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/actions/elementqueries.d.ts +12 -9
- package/dist/actions/resize.d.ts +5 -5
- package/dist/components/ButtonMenu.svelte +31 -25
- package/dist/components/Card.svelte +12 -8
- package/dist/components/CardLayout.svelte +151 -158
- package/dist/components/CollapsingTable.svelte +65 -63
- package/dist/components/ConditionalWrapper.svelte +7 -6
- package/dist/components/FocusLock.svelte +79 -85
- package/dist/components/Loading.svelte +16 -15
- package/dist/components/Lottie.svelte +37 -31
- package/dist/components/Modal.svelte +36 -33
- package/dist/components/MultiSelect.svelte +158 -165
- package/dist/components/PopupMenu.svelte +270 -271
- package/dist/components/ScreenReaderOnly.svelte +5 -4
- package/dist/stores/toasts.d.ts +2 -2
- package/dist/util/util.d.ts +2 -2
- package/package.json +6 -7
|
@@ -4,18 +4,21 @@ declare class ElementQueries {
|
|
|
4
4
|
refresh(): void;
|
|
5
5
|
fullresync(): void;
|
|
6
6
|
activate(): void;
|
|
7
|
-
subscribe
|
|
7
|
+
subscribe<S extends {
|
|
8
8
|
width: number;
|
|
9
|
-
}>): () => void;
|
|
9
|
+
}>(el: HTMLElement, store?: WritableSubject<S>): () => void;
|
|
10
10
|
}
|
|
11
11
|
export declare const elementqueries: ElementQueries;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
export declare function eq<S extends {
|
|
13
|
+
width: number;
|
|
14
|
+
} = {
|
|
15
|
+
width: number;
|
|
16
|
+
}>(el: HTMLElement, opts?: {
|
|
17
|
+
store: WritableSubject<S>;
|
|
18
|
+
}): {
|
|
19
|
+
update(opts?: {
|
|
20
|
+
store: WritableSubject<S>;
|
|
21
|
+
}): void;
|
|
19
22
|
destroy(): void;
|
|
20
23
|
};
|
|
21
24
|
export {};
|
package/dist/actions/resize.d.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { Store } from '@txstate-mws/svelte-store';
|
|
2
|
-
import type {
|
|
2
|
+
import type { WritableSubject } from '@txstate-mws/svelte-store';
|
|
3
3
|
export interface ElementSize {
|
|
4
4
|
clientWidth?: number;
|
|
5
5
|
clientHeight?: number;
|
|
6
6
|
offsetWidth?: number;
|
|
7
7
|
offsetHeight?: number;
|
|
8
8
|
}
|
|
9
|
-
interface ResizeConfig {
|
|
9
|
+
interface ResizeConfig<T extends ElementSize = ElementSize> {
|
|
10
10
|
debounce?: boolean | number;
|
|
11
|
-
store?:
|
|
11
|
+
store?: WritableSubject<T>;
|
|
12
12
|
}
|
|
13
13
|
export declare class ResizeStore extends Store<ElementSize> {
|
|
14
14
|
constructor(initialState?: ElementSize);
|
|
15
15
|
}
|
|
16
|
-
export declare function resize(el: HTMLElement, config?: ResizeConfig): {
|
|
17
|
-
update(newConfig?: ResizeConfig): void;
|
|
16
|
+
export declare function resize<T extends ElementSize = ElementSize>(el: HTMLElement, config?: ResizeConfig<T>): {
|
|
17
|
+
update(newConfig?: ResizeConfig<T>): void;
|
|
18
18
|
destroy(): void;
|
|
19
19
|
} | undefined;
|
|
20
20
|
export {};
|
|
@@ -1,28 +1,34 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export let
|
|
9
|
-
export let
|
|
10
|
-
export let
|
|
11
|
-
export let
|
|
12
|
-
export let
|
|
13
|
-
export let
|
|
14
|
-
export let
|
|
15
|
-
export let
|
|
16
|
-
export let
|
|
17
|
-
export let
|
|
18
|
-
export let
|
|
19
|
-
export let
|
|
20
|
-
export let
|
|
21
|
-
export let
|
|
22
|
-
export let
|
|
23
|
-
export let
|
|
24
|
-
export let
|
|
25
|
-
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { randomid } from 'txstate-utils'
|
|
3
|
+
import { Store } from '@txstate-mws/svelte-store'
|
|
4
|
+
import type { GlueAlignOpts, GlueAlignStore } from '../actions'
|
|
5
|
+
import type { PopupMenuItem } from '../types'
|
|
6
|
+
import PopupMenu from './PopupMenu.svelte'
|
|
7
|
+
|
|
8
|
+
export let buttonelement: HTMLButtonElement
|
|
9
|
+
export let items: PopupMenuItem[] = []
|
|
10
|
+
export let menushown = false
|
|
11
|
+
export let value: string | undefined = undefined
|
|
12
|
+
export let align: GlueAlignOpts = 'auto'
|
|
13
|
+
export let cover = false
|
|
14
|
+
export let showSelected = true
|
|
15
|
+
export let width: string | undefined = undefined
|
|
16
|
+
export let computedalign = new Store<GlueAlignStore>({ valign: 'bottom', halign: 'left' })
|
|
17
|
+
export let gap = 0
|
|
18
|
+
export let adjustparentheight = false
|
|
19
|
+
export let usePortal: HTMLElement | true | undefined = undefined
|
|
20
|
+
export let loading = false
|
|
21
|
+
export let hilited: number | undefined = undefined
|
|
22
|
+
export let menuid = randomid()
|
|
23
|
+
export let emptyText: string | undefined = undefined
|
|
24
|
+
export let menuContainerClass = ''
|
|
25
|
+
export let menuClass = ''
|
|
26
|
+
export let menuItemClass = ''
|
|
27
|
+
export let menuItemHilitedClass = ''
|
|
28
|
+
export let menuItemSelectedClass = ''
|
|
29
|
+
|
|
30
|
+
const usemenurole = true
|
|
31
|
+
|
|
26
32
|
</script>
|
|
27
33
|
|
|
28
34
|
<PopupMenu
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
<!-- @component
|
|
2
2
|
[`Card`](https://github.com/txstate-etc/svelte-components/blob/main/src/lib/components/Card.svelte)
|
|
3
3
|
-->
|
|
4
|
-
<script lang="ts">
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
<script lang="ts">
|
|
5
|
+
export let className = ''
|
|
6
|
+
import { getContext } from 'svelte'
|
|
7
|
+
import { resize, passActions, type HTMLActionEntry } from '../actions'
|
|
8
|
+
import { CARDLAYOUT } from '../types'
|
|
9
|
+
import type { CardLayoutContext, Block } from '../types'
|
|
10
|
+
|
|
11
|
+
export let use: HTMLActionEntry[] = []
|
|
12
|
+
|
|
13
|
+
const block: Block = { element: undefined }
|
|
14
|
+
const { registerBlock, gutter, recalculate } = getContext<CardLayoutContext>(CARDLAYOUT)
|
|
15
|
+
const { width, order, linebreak } = registerBlock(block)
|
|
12
16
|
</script>
|
|
13
17
|
|
|
14
18
|
<li class="cardlayout-card {className}" bind:this={block.element} use:passActions={use} use:resize on:resize={recalculate} style="margin-bottom: {$gutter}px; width: {$width}; order: {$order};">
|
|
@@ -1,184 +1,177 @@
|
|
|
1
1
|
<!-- @component
|
|
2
2
|
[`CardLayout`](https://github.com/txstate-etc/svelte-components/blob/main/src/lib/components/CardLayout.svelte)
|
|
3
3
|
-->
|
|
4
|
-
<script lang="ts">
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export let
|
|
11
|
-
export let
|
|
12
|
-
export let
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
4
|
+
<script lang="ts">
|
|
5
|
+
import { onDestroy, tick, setContext, onMount } from 'svelte'
|
|
6
|
+
import { writable } from 'svelte/store'
|
|
7
|
+
import { type ElementSize, resize, type HTMLActionEntry, passActions } from '../actions'
|
|
8
|
+
import { CARDLAYOUT } from '../types'
|
|
9
|
+
import type { Block } from '../types'
|
|
10
|
+
export let maxwidth = 500
|
|
11
|
+
export let preserveorder = false
|
|
12
|
+
export let gutter = 10
|
|
13
|
+
export let className = ''
|
|
14
|
+
export let use: HTMLActionEntry[] = []
|
|
15
|
+
const ssr = typeof navigator === 'undefined' || navigator.userAgent.includes('jsdom')
|
|
16
|
+
const blocks: Block[] = []
|
|
17
|
+
const gutterstore = writable(gutter)
|
|
18
|
+
let defaultOrder = 0
|
|
19
|
+
setContext(CARDLAYOUT, {
|
|
20
|
+
registerBlock: (block: Block) => {
|
|
21
|
+
blocks.push(block)
|
|
22
|
+
hardrecalc()
|
|
23
|
+
onDestroy(() => {
|
|
24
|
+
blocks.splice(blocks.indexOf(block), 1)
|
|
25
|
+
hardrecalc()
|
|
26
|
+
})
|
|
27
|
+
block.order = writable(defaultOrder)
|
|
28
|
+
defaultOrder += 1
|
|
29
|
+
block.linebreak = writable(false)
|
|
30
|
+
const cols = savecolumns || Math.ceil(1024 / maxwidth)
|
|
31
|
+
block.width = writable(`calc(${100.0 / cols}% - ${gutter * (cols - 1) / cols}px)`)
|
|
32
|
+
return block
|
|
31
33
|
},
|
|
32
34
|
recalculate: () => {
|
|
33
|
-
|
|
35
|
+
hardrecalc()
|
|
34
36
|
},
|
|
35
37
|
gutter: gutterstore
|
|
36
|
-
})
|
|
37
|
-
$: gutterstore.set(gutter)
|
|
38
|
-
let layoutelement
|
|
39
|
-
let cycle1 = 0
|
|
40
|
-
let cycle2 = 0
|
|
41
|
-
function detectcycle(w) {
|
|
38
|
+
})
|
|
39
|
+
$: gutterstore.set(gutter)
|
|
40
|
+
let layoutelement: HTMLElement
|
|
41
|
+
let cycle1 = 0
|
|
42
|
+
let cycle2 = 0
|
|
43
|
+
function detectcycle (w: number) {
|
|
42
44
|
if (cycle1 === 0 && Math.abs(w - cycle2) > 5) {
|
|
43
|
-
|
|
45
|
+
cycle1 = w
|
|
46
|
+
} else if (cycle2 === 0 && Math.abs(w - cycle1) > 5) {
|
|
47
|
+
cycle2 = w
|
|
48
|
+
} else if (w === cycle1 || w === cycle2) {
|
|
49
|
+
return true
|
|
50
|
+
} else {
|
|
51
|
+
cycle1 = 0
|
|
52
|
+
cycle2 = 0
|
|
44
53
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
let savecolumns = 0;
|
|
58
|
-
let optimal;
|
|
59
|
-
let fullheight = 0;
|
|
60
|
-
let hardrequired = false;
|
|
61
|
-
async function recalculate(realw) {
|
|
62
|
-
const columns = Math.ceil(realw / maxwidth);
|
|
63
|
-
const guttereach = gutter * (columns - 1) / columns;
|
|
64
|
-
const cycling = detectcycle(realw);
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
let savecolumns = 0
|
|
57
|
+
let optimal: Block[][]
|
|
58
|
+
let fullheight = 0
|
|
59
|
+
let hardrequired = false
|
|
60
|
+
async function recalculate (realw: number) {
|
|
61
|
+
const columns = Math.ceil(realw / maxwidth)
|
|
62
|
+
const guttereach = gutter * (columns - 1) / columns
|
|
63
|
+
const cycling = detectcycle(realw)
|
|
65
64
|
if (columns !== savecolumns) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
await tick();
|
|
65
|
+
for (const block of blocks) block.width!.set(`calc(${100.0 / columns}% - ${guttereach}px)`)
|
|
66
|
+
await tick()
|
|
69
67
|
}
|
|
70
68
|
// collect all the card heights at this new column width
|
|
71
|
-
for (const block of blocks)
|
|
72
|
-
block.height = block.element?.offsetHeight ?? 200;
|
|
69
|
+
for (const block of blocks) block.height = block.element?.offsetHeight ?? 200
|
|
73
70
|
if (columns !== savecolumns || hardrequired) { // only do real work if number of columns has changed, recalculate triggers on resize
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
colidx += 1;
|
|
98
|
-
colheight = 0;
|
|
99
|
-
}
|
|
100
|
-
arrangement[colidx] ??= [];
|
|
101
|
-
arrangement[colidx].push(block);
|
|
102
|
-
colheight += block.height + gutter;
|
|
103
|
-
}
|
|
104
|
-
if (colheight > tallestcol) {
|
|
105
|
-
tallestcol = colheight;
|
|
106
|
-
tallestcolidx = colidx;
|
|
107
|
-
}
|
|
108
|
-
if (tallestcol < shortestoverall) {
|
|
109
|
-
shortestoverall = tallestcol;
|
|
110
|
-
optimal = arrangement;
|
|
111
|
-
}
|
|
112
|
-
// if leftmost column is tallest, increasing the allowable height can never make the overall height shorter
|
|
113
|
-
if (tallestcolidx === 0)
|
|
114
|
-
break;
|
|
71
|
+
if (preserveorder) {
|
|
72
|
+
optimal = [blocks]
|
|
73
|
+
if (columns > 1) {
|
|
74
|
+
const totalheight = blocks.reduce((totalheight, block) => totalheight + block.height! + gutter, 0)
|
|
75
|
+
const tallestblock = Math.max(...blocks.map(b => b.height!))
|
|
76
|
+
const minheight = Math.max(totalheight / columns, tallestblock)
|
|
77
|
+
let shortestoverall = totalheight
|
|
78
|
+
// 2d packing problem is NP-hard so this is an O(n) heuristic
|
|
79
|
+
// optimal configuration would be if each column is `minheight` so start there
|
|
80
|
+
// then relax it in increments and see if the overall height shrinks
|
|
81
|
+
const increment = Math.max(1, Math.floor(Math.min(minheight, tallestblock) * 0.05))
|
|
82
|
+
for (let colmaxheight = minheight; colmaxheight < minheight + tallestblock + gutter; colmaxheight += increment) {
|
|
83
|
+
let colheight = 0
|
|
84
|
+
let colidx = 0
|
|
85
|
+
let tallestcol = 0
|
|
86
|
+
let tallestcolidx = 0
|
|
87
|
+
const arrangement: Block[][] = []
|
|
88
|
+
for (const block of blocks) {
|
|
89
|
+
if (colheight + block.height! > colmaxheight && colidx < columns - 1) {
|
|
90
|
+
if (colheight > tallestcol) {
|
|
91
|
+
tallestcol = colheight
|
|
92
|
+
tallestcolidx = colidx
|
|
115
93
|
}
|
|
94
|
+
colidx += 1
|
|
95
|
+
colheight = 0
|
|
96
|
+
}
|
|
97
|
+
arrangement[colidx] ??= []
|
|
98
|
+
arrangement[colidx].push(block)
|
|
99
|
+
colheight += block.height! + gutter
|
|
116
100
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const heights = [...Array(columns)].map((_) => 0); // initializes heights to an array of zeroes
|
|
121
|
-
// begin sorting blocks into columns
|
|
122
|
-
for (const block of blocks) {
|
|
123
|
-
// find the column with the smallest current height
|
|
124
|
-
const colidx = heights.reduce((acc, curr, curridx) => curr < heights[acc] ? curridx : acc, 0);
|
|
125
|
-
// record the height we are adding to the chosen column
|
|
126
|
-
heights[colidx] += block.height + gutter;
|
|
127
|
-
// move the current card to the chosen column
|
|
128
|
-
optimal[colidx] ??= [];
|
|
129
|
-
optimal[colidx].push(block);
|
|
101
|
+
if (colheight > tallestcol) {
|
|
102
|
+
tallestcol = colheight
|
|
103
|
+
tallestcolidx = colidx
|
|
130
104
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
let order = 0;
|
|
135
|
-
for (let i = 0; i < optimal.length; i++) {
|
|
136
|
-
for (let j = 0; j < optimal[i].length; j++) {
|
|
137
|
-
const block = optimal[i][j];
|
|
138
|
-
block.order.set(order);
|
|
139
|
-
order += 1;
|
|
140
|
-
block.linebreak.set(j === optimal[i].length - 1 && i < optimal.length - 1);
|
|
105
|
+
if (tallestcol < shortestoverall) {
|
|
106
|
+
shortestoverall = tallestcol
|
|
107
|
+
optimal = arrangement
|
|
141
108
|
}
|
|
109
|
+
// if leftmost column is tallest, increasing the allowable height can never make the overall height shorter
|
|
110
|
+
if (tallestcolidx === 0) break
|
|
111
|
+
}
|
|
142
112
|
}
|
|
143
|
-
|
|
144
|
-
|
|
113
|
+
} else {
|
|
114
|
+
optimal = []
|
|
115
|
+
const heights: number[] = [...Array(columns)].map((_: any) => 0) // initializes heights to an array of zeroes
|
|
116
|
+
// begin sorting blocks into columns
|
|
117
|
+
for (const block of blocks) {
|
|
118
|
+
// find the column with the smallest current height
|
|
119
|
+
const colidx = heights.reduce((acc, curr, curridx) => curr < heights[acc] ? curridx : acc, 0)
|
|
120
|
+
// record the height we are adding to the chosen column
|
|
121
|
+
heights[colidx] += block.height! + gutter
|
|
122
|
+
// move the current card to the chosen column
|
|
123
|
+
optimal[colidx] ??= []
|
|
124
|
+
optimal[colidx].push(block)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// we created the optimal arrangement, now move the block elements to
|
|
128
|
+
// reflect it
|
|
129
|
+
let order = 0
|
|
130
|
+
for (let i = 0; i < optimal.length; i++) {
|
|
131
|
+
for (let j = 0; j < optimal[i].length; j++) {
|
|
132
|
+
const block = optimal[i][j]
|
|
133
|
+
block.order!.set(order)
|
|
134
|
+
order += 1
|
|
135
|
+
block.linebreak!.set(j === optimal[i].length - 1 && i < optimal.length - 1)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
savecolumns = columns
|
|
139
|
+
hardrequired = false
|
|
145
140
|
}
|
|
146
141
|
// re-adjust container height
|
|
147
|
-
const saveheight = fullheight
|
|
148
|
-
fullheight = 0
|
|
142
|
+
const saveheight = fullheight
|
|
143
|
+
fullheight = 0
|
|
149
144
|
for (let i = 0; i < optimal.length; i++) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
145
|
+
let top = 0
|
|
146
|
+
for (const block of optimal[i]) {
|
|
147
|
+
top += block.height! + gutter
|
|
148
|
+
}
|
|
149
|
+
fullheight = Math.max(fullheight, top)
|
|
155
150
|
}
|
|
156
|
-
if (cycling && saveheight < fullheight)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
let savewidth = 0
|
|
160
|
-
let timer = 0
|
|
161
|
-
function triggerrecalc(width) {
|
|
162
|
-
if (width === savewidth && !hardrequired)
|
|
163
|
-
|
|
164
|
-
cancelAnimationFrame(timer);
|
|
151
|
+
if (cycling && saveheight < fullheight) fullheight = saveheight
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let savewidth = 0
|
|
155
|
+
let timer = 0
|
|
156
|
+
function triggerrecalc (width: number) {
|
|
157
|
+
if (width === savewidth && !hardrequired) return
|
|
158
|
+
cancelAnimationFrame(timer)
|
|
165
159
|
timer = requestAnimationFrame(() => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
})
|
|
169
|
-
}
|
|
170
|
-
function hardrecalc() {
|
|
171
|
-
if (!layoutelement)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
onMount(() => { triggerrecalc(layoutelement.clientWidth); });
|
|
160
|
+
void recalculate(width)
|
|
161
|
+
savewidth = width
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
function hardrecalc () {
|
|
165
|
+
if (!layoutelement) return
|
|
166
|
+
hardrequired = true
|
|
167
|
+
cycle1 = 0
|
|
168
|
+
cycle2 = 0
|
|
169
|
+
triggerrecalc(layoutelement.clientWidth)
|
|
170
|
+
}
|
|
171
|
+
function onResize (e: UIEvent & { currentTarget: any, detail: ElementSize }) {
|
|
172
|
+
triggerrecalc(e.detail.clientWidth ?? 0)
|
|
173
|
+
}
|
|
174
|
+
onMount(() => { triggerrecalc(layoutelement.clientWidth) })
|
|
182
175
|
</script>
|
|
183
176
|
|
|
184
177
|
<ul class="cardlayout {className}" class:ssr style:height={ssr ? undefined : `${fullheight + gutter}px`} bind:this={layoutelement} use:resize on:resize={onResize} use:passActions={use}>
|
|
@@ -5,78 +5,80 @@ This component is meant to help make tables responsive by eliminating columns (f
|
|
|
5
5
|
When there are hidden columns, the last column header becomes a dropdown button allowing the selection of another column
|
|
6
6
|
to be displayed instead.
|
|
7
7
|
-->
|
|
8
|
-
<script lang="ts">
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export let
|
|
17
|
-
export let
|
|
18
|
-
export let
|
|
19
|
-
export let
|
|
20
|
-
export let
|
|
21
|
-
export let
|
|
22
|
-
export let
|
|
23
|
-
export let
|
|
24
|
-
export let
|
|
25
|
-
export let
|
|
26
|
-
export let
|
|
27
|
-
export let
|
|
28
|
-
let
|
|
29
|
-
let
|
|
30
|
-
let
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
import { Store } from '@txstate-mws/svelte-store'
|
|
10
|
+
import { derived } from 'svelte/store'
|
|
11
|
+
import type { CollapsingTableColumn, PopupMenuItem } from '../types'
|
|
12
|
+
import { classes } from '../util'
|
|
13
|
+
import DefaultPopupMenu from './PopupMenu.svelte'
|
|
14
|
+
import ScreenReaderOnly from './ScreenReaderOnly.svelte'
|
|
15
|
+
|
|
16
|
+
export let tableClass = ''
|
|
17
|
+
export let bodyRowClass = ''
|
|
18
|
+
export let bodyCellClass = ''
|
|
19
|
+
export let headerRowClass = ''
|
|
20
|
+
export let headerCellClass = ''
|
|
21
|
+
export let menuContainerClass = ''
|
|
22
|
+
export let menuClass = ''
|
|
23
|
+
export let menuItemClass = ''
|
|
24
|
+
export let menuItemHilitedClass = ''
|
|
25
|
+
export let items: any[]
|
|
26
|
+
export let config: CollapsingTableColumn[] | undefined = undefined
|
|
27
|
+
export let defaultCellWidth = 150
|
|
28
|
+
export let PopupMenu = DefaultPopupMenu
|
|
29
|
+
export let slots: Record<string, boolean> = $$slots
|
|
30
|
+
export let stickyheader = false
|
|
31
|
+
let columns: CollapsingTableColumn[]
|
|
32
|
+
let width = 320
|
|
33
|
+
let selected: PopupMenuItem | undefined
|
|
34
|
+
let identifyingkeys: string[] = []
|
|
35
|
+
$: firstrow = items?.[0] ?? {}
|
|
36
|
+
$: columns = config ?? Object.keys(firstrow as object).map(key => ({ key }))
|
|
37
|
+
function reactToColumns (columns: CollapsingTableColumn[]) {
|
|
35
38
|
if (!columns.some(c => c.neverhide)) {
|
|
36
|
-
|
|
39
|
+
columns[0].neverhide = true
|
|
37
40
|
}
|
|
38
41
|
const sortedcolumns = columns // stable sort
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
.map((c, i) => ({ col: c, idx: i }))
|
|
43
|
+
.sort((a, b) => ((a.col.neverhide ? 0 : 1) - (b.col.neverhide ? 0 : 1)) || a.idx - b.idx)
|
|
44
|
+
.map(o => o.col)
|
|
42
45
|
if (!selected) {
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
const firstcol = sortedcolumns.find(c => !c.neverhide)
|
|
47
|
+
selected = firstcol ? { value: firstcol.key } : undefined
|
|
45
48
|
}
|
|
46
|
-
identifyingkeys = sortedcolumns.filter(c => c.neverhide).map(c => c.key)
|
|
47
|
-
return sortedcolumns
|
|
48
|
-
}
|
|
49
|
-
$: sortedcolumns = reactToColumns(columns)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
49
|
+
identifyingkeys = sortedcolumns.filter(c => c.neverhide).map(c => c.key)
|
|
50
|
+
return sortedcolumns
|
|
51
|
+
}
|
|
52
|
+
$: sortedcolumns = reactToColumns(columns)
|
|
53
|
+
|
|
54
|
+
$: itemkeys = items.map(item => item.id ?? item._id ?? identifyingkeys.map(k => item[k]).join('.'))
|
|
55
|
+
|
|
56
|
+
const state = new Store({
|
|
57
|
+
keepcolumns: [] as (CollapsingTableColumn & { widthPercent?: number })[],
|
|
58
|
+
hiddencolumns: [] as CollapsingTableColumn[],
|
|
59
|
+
dropdowncolumn: undefined as (CollapsingTableColumn & { widthPercent?: number }) | undefined
|
|
60
|
+
})
|
|
61
|
+
const menuitems = derived(state, obj => obj.hiddencolumns.map(c => ({ value: c.key, label: c.title })))
|
|
62
|
+
function react (..._: any[]) {
|
|
63
|
+
const selectedcol = sortedcolumns.find(c => c.key === selected?.value) ?? columns[1]
|
|
64
|
+
const keepcolumns: CollapsingTableColumn[] = []
|
|
65
|
+
const hiddencolumns: CollapsingTableColumn[] = []
|
|
66
|
+
let used = sortedcolumns.filter(c => c.neverhide || c === selectedcol).reduce((sum, col) => sum + (col.width ?? defaultCellWidth), 0)
|
|
62
67
|
for (const col of sortedcolumns) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
else
|
|
69
|
-
hiddencolumns.push(col);
|
|
68
|
+
if (col.neverhide || col === selectedcol || used + (col.width ?? defaultCellWidth) < width) {
|
|
69
|
+
keepcolumns.push(col)
|
|
70
|
+
if (col !== selectedcol && !col.neverhide) used += col.width ?? defaultCellWidth
|
|
71
|
+
} else hiddencolumns.push(col)
|
|
70
72
|
}
|
|
71
73
|
for (const col of keepcolumns) {
|
|
72
|
-
|
|
74
|
+
(col as any).widthPercent = 100 * (col.width ?? defaultCellWidth) / used
|
|
73
75
|
}
|
|
74
|
-
const dropdowncolumn = hiddencolumns.length ? keepcolumns[keepcolumns.length - 1] : undefined
|
|
75
|
-
state.set({ keepcolumns, hiddencolumns, dropdowncolumn })
|
|
76
|
-
}
|
|
77
|
-
$: react(width, selected)
|
|
78
|
-
const headers = []
|
|
79
|
-
$: menubuttonelement = headers[$state.keepcolumns.length - 1]
|
|
76
|
+
const dropdowncolumn = hiddencolumns.length ? keepcolumns[keepcolumns.length - 1] : undefined
|
|
77
|
+
state.set({ keepcolumns, hiddencolumns, dropdowncolumn })
|
|
78
|
+
}
|
|
79
|
+
$: react(width, selected)
|
|
80
|
+
const headers: HTMLElement[] = []
|
|
81
|
+
$: menubuttonelement = headers[$state.keepcolumns.length - 1]
|
|
80
82
|
</script>
|
|
81
83
|
|
|
82
84
|
<div bind:clientWidth={width}>
|