@dosgato/dialog 1.0.8 → 1.1.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.
@@ -1,6 +1,10 @@
1
- <script>import { resize } from '@txstate-mws/svelte-components';
1
+ <script>import { Icon } from '..';
2
+ import { resize, PopupMenu } from '@txstate-mws/svelte-components';
2
3
  import { derivedStore, Store } from '@txstate-mws/svelte-store';
3
- import { afterUpdate, beforeUpdate, onDestroy, onMount, setContext } from 'svelte';
4
+ import { afterUpdate, beforeUpdate, onDestroy, onMount, setContext, tick } from 'svelte';
5
+ import dotsIcon from '@iconify-icons/ph/dots-three-outline-vertical-fill';
6
+ import circleIcon from '@iconify-icons/ph/circle';
7
+ import radioSelectedIcon from '@iconify-icons/ph/radio-button-fill';
4
8
  import { isNotBlank } from 'txstate-utils';
5
9
  import LoadIcon from './LoadIcon.svelte';
6
10
  import TreeNode from './TreeNode.svelte';
@@ -12,6 +16,13 @@ export let filter = '';
12
16
  export let nodeClass = undefined;
13
17
  export let singleSelect = undefined;
14
18
  export let enableResize = false;
19
+ /**
20
+ * Takes the width of the tree, in pixels, and returns an array of TreeHeader IDs that should be
21
+ * displayed at that screen width. Any headers whose ID is not returned will be added to a dropdown, which allows
22
+ * the user to view them. The last ID returned by this function is the header that is replaced if the user chooses
23
+ * to view a different header. The headers will always appear in the order in which they are defined in the headers prop.
24
+ */
25
+ export let responsiveHeaders = undefined;
15
26
  /**
16
27
  * this `itemType` prop is here for typescript only
17
28
  *
@@ -34,22 +45,55 @@ setContext(TREE_STORE_CONTEXT, store);
34
45
  const { filteredRootItems, headerOverride } = store;
35
46
  let checkboxelement;
36
47
  const headerelements = [];
48
+ let showMoreButton;
37
49
  const treeWidth = new Store({});
50
+ // Need to keep track of which headers are shown or hidden and which is selected
51
+ let shownHeaders = headers;
52
+ let hiddenHeaders = [];
53
+ let selectedHeader = undefined;
54
+ let selectedHeaderValue;
55
+ function updateShownHeaders() {
56
+ if (typeof responsiveHeaders === 'function') {
57
+ shownHeaders = [];
58
+ hiddenHeaders = [];
59
+ const shown = responsiveHeaders($treeWidth.clientWidth ?? 1024);
60
+ if (selectedHeader && !shown.includes(selectedHeader.id)) {
61
+ shown[shown.length - 1] = selectedHeader.id;
62
+ }
63
+ for (const h of headers) {
64
+ if (shown.includes(h.id)) {
65
+ shownHeaders.push(h);
66
+ }
67
+ else {
68
+ hiddenHeaders.push(h);
69
+ }
70
+ }
71
+ selectedHeaderValue = shown[shown.length - 1];
72
+ if (hiddenHeaders.length) {
73
+ const hideable = headers.find(h => h.id === selectedHeaderValue);
74
+ if (hideable)
75
+ hiddenHeaders.push(hideable);
76
+ }
77
+ }
78
+ }
38
79
  function calcHeaderSizes() {
80
+ updateShownHeaders();
39
81
  const headerSizes = [];
40
82
  let totalFixed = checkboxelement?.offsetWidth ?? 0;
41
- for (let i = 0; i < headers.length; i++) {
42
- const header = headers[i];
83
+ if (hiddenHeaders.length)
84
+ totalFixed += (showMoreButton?.offsetWidth ?? 0);
85
+ for (let i = 0; i < shownHeaders.length; i++) {
86
+ const header = shownHeaders[i];
43
87
  if (header.fixed || $headerOverride[header.id]) {
44
88
  headerSizes[i] = $headerOverride[header.id] ?? header.fixed;
45
89
  totalFixed += headerelements[i]?.offsetWidth ?? 0;
46
90
  }
47
91
  }
48
92
  const remainingWidth = ($treeWidth.clientWidth ?? 1024) - totalFixed;
49
- const growHeaders = headers.filter((h, i) => !h.fixed && !$headerOverride[h.id] && headerelements[i]?.offsetWidth);
93
+ const growHeaders = shownHeaders.filter((h, i) => !h.fixed && !$headerOverride[h.id]);
50
94
  const totalGrowShares = growHeaders.reduce((sum, h) => sum + (h.grow ?? 1), 0);
51
- for (let i = 0; i < headers.length; i++) {
52
- const header = headers[i];
95
+ for (let i = 0; i < shownHeaders.length; i++) {
96
+ const header = shownHeaders[i];
53
97
  if (!header.fixed && !$headerOverride[header.id] && headerelements[i]?.offsetWidth) {
54
98
  headerSizes[i] = `${remainingWidth * (header.grow ?? 1) / totalGrowShares}px`;
55
99
  }
@@ -151,17 +195,36 @@ afterUpdate(() => {
151
195
  });
152
196
  $: myRootItemIds = $store && $filteredRootItems;
153
197
  $: myRootItems = $store?.rootItems?.filter(r => myRootItemIds.has(r.id)) ?? [];
198
+ async function selectHeader(selected) {
199
+ selectedHeader = headers.find(h => h.id === selected.value);
200
+ updateShownHeaders();
201
+ store.resetHeaderOverride();
202
+ const headersizes = shownHeaders.map(h => h.fixed ? h.fixed : '');
203
+ headerSizes.set(headersizes);
204
+ await tick();
205
+ headerSizes.set(calcHeaderSizes());
206
+ }
154
207
  </script>
155
208
 
156
209
  <svelte:window on:mouseup={headerDragEnd} />
157
-
158
- <div class="tree-header" class:resizing={!!dragtargetid} use:resize={{ store: treeWidth }} aria-hidden="true" on:mouseup={headerDragEnd} on:touchend={headerDragEnd} on:mousemove={dragtargetid ? headerDrag : undefined} on:touchmove={dragtargetid ? headerDrag : undefined}>
159
- <div class="checkbox" bind:this={checkboxelement}>&nbsp;</div>
160
- {#each headers as header, i (header.label)}
161
- <div bind:this={headerelements[i]} id={header.id} class="tree-header-cell {header.id}" style:width={$headerOverride[header.id] ?? $headerSizes?.[i]} style:padding-left={i === 0 ? '1.4em' : undefined}>{header.label}{#if i === 0 && $store.loading}<LoadIcon />{/if}{#if i === 0 && isNotBlank(search)}&nbsp;(searching: {search}){/if}</div>
162
- <!-- svelte-ignore a11y-no-static-element-interactions -->
163
- {#if enableResize && i !== headers.length - 1}<div class="tree-separator {header.id}" on:mousedown={headerDragStart(header, i)} on:touchstart={headerDragStart(header, i)} on:dblclick={headerDragReset}>&nbsp;</div>{/if}
164
- {/each}
210
+ <div class="tree-header" class:resizing={!!dragtargetid} use:resize={{ store: treeWidth }} on:mouseup={headerDragEnd} on:touchend={headerDragEnd} on:mousemove={dragtargetid ? headerDrag : undefined} on:touchmove={dragtargetid ? headerDrag : undefined}>
211
+ <div class="checkbox" bind:this={checkboxelement} aria-hidden="true">&nbsp;</div>
212
+ {#each shownHeaders as header, i (header.label)}
213
+ <div bind:this={headerelements[i]} id={header.id} class="tree-header-cell {header.id}" aria-hidden="true" style:width={$headerOverride[header.id] ?? $headerSizes?.[i]} style:padding-left={i === 0 ? '1.4em' : undefined}>{header.label}{#if i === 0 && $store.loading}<LoadIcon />{/if}{#if i === 0 && isNotBlank(search)}&nbsp;(searching: {search}){/if}</div>
214
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
215
+ {#if enableResize && i !== shownHeaders.length - 1}<div class="tree-separator {header.id}" on:mousedown={headerDragStart(header, i)} on:touchstart={headerDragStart(header, i)} on:dblclick={headerDragReset}>&nbsp;</div>{/if}
216
+ {/each}
217
+ {#if hiddenHeaders.length}
218
+ <div class="button-wrapper">
219
+ <button bind:this={showMoreButton} type='button'><Icon icon={dotsIcon} hiddenLabel="View other columns"/></button>
220
+ <PopupMenu bind:value={selectedHeaderValue} items={hiddenHeaders.map(h => ({ value: h.id, label: h.label }))} buttonelement={showMoreButton} on:change={e => {selectHeader(e.detail)}} let:item menuContainerClass="hideable-container" menuClass="hideable-headers" menuItemSelectedClass="selected-header">
221
+ {#if item.hasOwnProperty('value')}
222
+ <Icon icon={'value' in item && item.value === selectedHeaderValue ? radioSelectedIcon : circleIcon} inline/>
223
+ {item.label}
224
+ {/if}
225
+ </PopupMenu>
226
+ </div>
227
+ {/if}
165
228
  </div>
166
229
  {#if mounted && myRootItems?.length}
167
230
  <!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
@@ -169,7 +232,7 @@ $: myRootItems = $store?.rootItems?.filter(r => myRootItemIds.has(r.id)) ?? [];
169
232
  {#each myRootItems as item, i (item.id)}
170
233
  <TreeNode
171
234
  {item}
172
- {headers}
235
+ headers={shownHeaders}
173
236
  {headerSizes}
174
237
  {nodeClass}
175
238
  posinset={i + 1}
@@ -234,5 +297,29 @@ $: myRootItems = $store?.rootItems?.filter(r => myRootItemIds.has(r.id)) ?? [];
234
297
  :global([data-eq~="650px"]) ul {
235
298
  font-size: 0.8em;
236
299
  }
300
+ .button-wrapper {
301
+ display: flex;
302
+ justify-content: flex-end;
303
+ flex-grow: 2;
304
+ }
305
+ .button-wrapper button {
306
+ background: transparent;
307
+ color: var(--tree-head-text, white);
308
+ border: 0;
309
+ }
310
+ :global(.hideable-container .hideable-headers) {
311
+ margin: 0;
312
+ padding: 0.4em;
313
+ background: white;
314
+ border: 1px solid slategray;
315
+ border-radius: 3px;
316
+ min-width: 10em;
317
+ max-height: 20em;
318
+ overflow-y: auto;
319
+ }
320
+ :global(div.hideable-container ul.hideable-headers li[role="option"]) {
321
+ padding-left: 0;
322
+ color: black;
323
+ }
237
324
 
238
325
  </style>
@@ -10,6 +10,12 @@ declare class __sveltets_Render<T extends TreeItemFromDB> {
10
10
  nodeClass?: ((itm: T) => string) | undefined;
11
11
  singleSelect?: boolean | undefined;
12
12
  enableResize?: boolean | undefined;
13
+ /**
14
+ * Takes the width of the tree, in pixels, and returns an array of TreeHeader IDs that should be
15
+ * displayed at that screen width. Any headers whose ID is not returned will be added to a dropdown, which allows
16
+ * the user to view them. The last ID returned by this function is the header that is replaced if the user chooses
17
+ * to view a different header. The headers will always appear in the order in which they are defined in the headers prop.
18
+ */ responsiveHeaders?: ((treeWidth: number) => string[]) | undefined;
13
19
  /**
14
20
  * this `itemType` prop is here for typescript only
15
21
  *
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dosgato/dialog",
3
3
  "description": "A component library for building forms that edit a JSON document.",
4
- "version": "1.0.8",
4
+ "version": "1.1.1",
5
5
  "scripts": {
6
6
  "prepublishOnly": "svelte-package",
7
7
  "dev": "vite dev --force",