@dosgato/dialog 1.1.0 → 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.
@@ -3,6 +3,8 @@ import { resize, PopupMenu } from '@txstate-mws/svelte-components';
3
3
  import { derivedStore, Store } from '@txstate-mws/svelte-store';
4
4
  import { afterUpdate, beforeUpdate, onDestroy, onMount, setContext, tick } from 'svelte';
5
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';
6
8
  import { isNotBlank } from 'txstate-utils';
7
9
  import LoadIcon from './LoadIcon.svelte';
8
10
  import TreeNode from './TreeNode.svelte';
@@ -14,7 +16,13 @@ export let filter = '';
14
16
  export let nodeClass = undefined;
15
17
  export let singleSelect = undefined;
16
18
  export let enableResize = false;
17
- export let minColumnSize = 100;
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;
18
26
  /**
19
27
  * this `itemType` prop is here for typescript only
20
28
  *
@@ -37,45 +45,47 @@ setContext(TREE_STORE_CONTEXT, store);
37
45
  const { filteredRootItems, headerOverride } = store;
38
46
  let checkboxelement;
39
47
  const headerelements = [];
48
+ let showMoreButton;
40
49
  const treeWidth = new Store({});
41
- let numShownColumns = headers.length;
42
- let numNeverHidden = headers.filter(h => h.neverHide).length;
43
50
  // Need to keep track of which headers are shown or hidden and which is selected
44
51
  let shownHeaders = headers;
45
52
  let hiddenHeaders = [];
46
53
  let selectedHeader = undefined;
54
+ let selectedHeaderValue;
47
55
  function updateShownHeaders() {
48
- numShownColumns = Math.min(headers.length, Math.floor(($treeWidth.clientWidth ?? 1024) / minColumnSize));
49
- if (numShownColumns < headers.length) {
56
+ if (typeof responsiveHeaders === 'function') {
50
57
  shownHeaders = [];
51
58
  hiddenHeaders = [];
52
- let available = (selectedHeader ? numShownColumns - 1 : numShownColumns) - numNeverHidden;
53
- if (selectedHeader?.neverHide)
54
- available++;
55
- for (let i = 0; i < headers.length; i++) {
56
- if (available > 0 || headers[i].neverHide || headers[i].id === selectedHeader?.id) {
57
- shownHeaders.push(headers[i]);
58
- if (headers[i].id !== selectedHeader?.id && !headers[i].neverHide)
59
- available--;
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);
60
66
  }
61
67
  else {
62
- hiddenHeaders.push(headers[i]);
68
+ hiddenHeaders.push(h);
63
69
  }
64
70
  }
65
- }
66
- else {
67
- shownHeaders = headers;
68
- hiddenHeaders = [];
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
+ }
69
77
  }
70
78
  }
71
79
  function calcHeaderSizes() {
72
80
  updateShownHeaders();
73
81
  const headerSizes = [];
74
82
  let totalFixed = checkboxelement?.offsetWidth ?? 0;
83
+ if (hiddenHeaders.length)
84
+ totalFixed += (showMoreButton?.offsetWidth ?? 0);
75
85
  for (let i = 0; i < shownHeaders.length; i++) {
76
86
  const header = shownHeaders[i];
77
87
  if (header.fixed || $headerOverride[header.id]) {
78
- headerSizes[i] = $headerOverride[header.id] ?? (header.fixed ? (hiddenHeaders.length && i === shownHeaders.length - 1 ? `calc(${header.fixed} + 1em)` : header.fixed) : undefined);
88
+ headerSizes[i] = $headerOverride[header.id] ?? header.fixed;
79
89
  totalFixed += headerelements[i]?.offsetWidth ?? 0;
80
90
  }
81
91
  }
@@ -188,32 +198,34 @@ $: myRootItems = $store?.rootItems?.filter(r => myRootItemIds.has(r.id)) ?? [];
188
198
  async function selectHeader(selected) {
189
199
  selectedHeader = headers.find(h => h.id === selected.value);
190
200
  updateShownHeaders();
191
- headerSizes.set([]);
192
201
  store.resetHeaderOverride();
202
+ const headersizes = shownHeaders.map(h => h.fixed ? h.fixed : '');
203
+ headerSizes.set(headersizes);
193
204
  await tick();
194
205
  headerSizes.set(calcHeaderSizes());
195
206
  }
196
207
  </script>
197
208
 
198
209
  <svelte:window on:mouseup={headerDragEnd} />
199
- <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}>
200
- <div class="checkbox" bind:this={checkboxelement}>&nbsp;</div>
201
- {#each shownHeaders as header, i (header.label)}
202
- {@const hasDropdown = hiddenHeaders.length && i === shownHeaders.length - 1}
203
- <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
204
- <div bind:this={headerelements[i]} id={header.id} class="tree-header-cell {header.id}" class:column-dropdown={hasDropdown} style:width={$headerOverride[header.id] ?? $headerSizes?.[i]} style:padding-left={i === 0 ? '1.4em' : undefined} tabindex={hasDropdown ? 0 : undefined}>{header.label}{#if i === 0 && $store.loading}
205
- <LoadIcon />{/if}{#if i === 0 && isNotBlank(search)}&nbsp;(searching: {search}){/if}
206
- {#if hasDropdown}
207
- <Icon icon={dotsIcon} inline hiddenLabel="Show more columns"/>
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}
208
224
  {/if}
209
- </div>
210
- <!-- svelte-ignore a11y-no-static-element-interactions -->
211
- {#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}
212
- {/each}
213
- </div>
214
- {#if hiddenHeaders.length > 0}
215
- <PopupMenu items={hiddenHeaders.map(h => ({ value: h.id, label: h.label }))} buttonelement={headerelements[shownHeaders.length-1]} on:change={e => {selectHeader(e.detail)}}/>
225
+ </PopupMenu>
226
+ </div>
216
227
  {/if}
228
+ </div>
217
229
  {#if mounted && myRootItems?.length}
218
230
  <!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
219
231
  <ul bind:this={store.treeElement} role="tree" class:resizing={!!dragtargetid} on:mousemove={dragtargetid ? headerDrag : undefined} on:touchmove={dragtargetid ? headerDrag : undefined} on:mouseup={headerDragEnd} on:touchend={headerDragEnd} on:keyup={onKeyUp}>
@@ -269,11 +281,6 @@ async function selectHeader(selected) {
269
281
  height: 100%;
270
282
  background-color: var(--tree-head-text, white);
271
283
  }
272
- :global(.column-dropdown) {
273
- display: flex;
274
- gap: 0.5em;
275
- justify-content: space-between;
276
- }
277
284
  :global([data-eq~="650px"]) .tree-header {
278
285
  font-size: 0.8em;
279
286
  }
@@ -290,5 +297,29 @@ async function selectHeader(selected) {
290
297
  :global([data-eq~="650px"]) ul {
291
298
  font-size: 0.8em;
292
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
+ }
293
324
 
294
325
  </style>
@@ -10,7 +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
- minColumnSize?: number | 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;
14
19
  /**
15
20
  * this `itemType` prop is here for typescript only
16
21
  *
@@ -51,7 +51,6 @@ export interface TreeHeader<T extends TreeItemFromDB> {
51
51
  render?: (item: TypedTreeItem<T>) => string;
52
52
  component?: SvelteComponent;
53
53
  class?: (item: TypedTreeItem<T>) => string | string[];
54
- neverHide?: boolean;
55
54
  }
56
55
  export declare class TreeStore<T extends TreeItemFromDB> extends ActiveStore<ITreeStore<T>> {
57
56
  #private;
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.1.0",
4
+ "version": "1.1.1",
5
5
  "scripts": {
6
6
  "prepublishOnly": "svelte-package",
7
7
  "dev": "vite dev --force",