@dosgato/dialog 0.0.52 → 0.0.53

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,17 +1,27 @@
1
+ <!-- @component
2
+ The purpose of `<Container>` is to provide common rendering for helptext, screen reader support,
3
+ and `Feedback` messages for its slotted components under a common `<div>` useful for form fields.
4
+ -->
1
5
  <script>import { eq, resize, ScreenReaderOnly } from '@txstate-mws/svelte-components';
2
6
  import { randomid } from 'txstate-utils';
3
7
  import { getContext } from 'svelte';
4
8
  import { DG_DIALOG_FIELD_MULTIPLE } from './FieldMultiple.svelte';
5
9
  import InlineMessages from './InlineMessages.svelte';
6
10
  import { getDescribedBy } from './';
11
+ /** A label for the Container `<div>`. */
12
+ export let label;
13
+ export let messages;
14
+ /** If the input that's being built has an id pass it here so the label can point at it. */
7
15
  export let id = undefined;
16
+ /** If `descid` is defined then this assumes you've made an outside label referenced to by descid `<div id={descid}`.
17
+ Useful for things like checkboxes and radio buttons that have their own individual labels. */
8
18
  export let descid = undefined;
9
- export let label;
10
19
  export let helptext = undefined;
11
- export let messages;
20
+ /** Syntactic sugar that toggles a '*' to be appended to label. */
12
21
  export let required = false;
13
22
  export let related = 0;
14
23
  export let conditional = undefined;
24
+ /** The `id` of `<div>` messages are rendered in. */
15
25
  let messagesid;
16
26
  const dgMultipleContext = getContext(DG_DIALOG_FIELD_MULTIPLE);
17
27
  const helptextid = randomid();
@@ -2,12 +2,13 @@ import { SvelteComponentTyped } from "svelte";
2
2
  import type { Feedback } from '@txstate-mws/svelte-forms';
3
3
  declare const __propDef: {
4
4
  props: {
5
- id?: string | undefined;
6
- descid?: string | undefined;
7
- label: string;
8
- helptext?: string | undefined;
5
+ /** A label for the Container `<div>`. */ label: string;
9
6
  messages: Feedback[];
10
- required?: boolean | undefined;
7
+ /** If the input that's being built has an id pass it here so the label can point at it. */ id?: string | undefined;
8
+ /** If `descid` is defined then this assumes you've made an outside label referenced to by descid `<div id={descid}`.
9
+ Useful for things like checkboxes and radio buttons that have their own individual labels. */ descid?: string | undefined;
10
+ helptext?: string | undefined;
11
+ /** Syntactic sugar that toggles a '*' to be appended to label. */ required?: boolean | undefined;
11
12
  related?: number | true | undefined;
12
13
  conditional?: boolean | undefined;
13
14
  };
@@ -24,6 +25,10 @@ declare const __propDef: {
24
25
  export type ContainerProps = typeof __propDef.props;
25
26
  export type ContainerEvents = typeof __propDef.events;
26
27
  export type ContainerSlots = typeof __propDef.slots;
28
+ /**
29
+ * The purpose of `<Container>` is to provide common rendering for helptext, screen reader support,
30
+ * and `Feedback` messages for its slotted components under a common `<div>` useful for form fields.
31
+ */
27
32
  export default class Container extends SvelteComponentTyped<ContainerProps, ContainerEvents, ContainerSlots> {
28
33
  }
29
34
  export {};
@@ -1,3 +1,9 @@
1
+ <!-- @component
2
+ A Field component for dynamically rendering checkbox choices accross the available width of a `<div>`. If there are more
3
+ choices than the available width of the `<div>` will support then it will create multiple rows to render within using
4
+ flex. Ordering is top down by default but can be order horizontally by toggling `leftToRight`.
5
+ The value of the field will be an array corresponding to the values of the checkboxes that are checked.
6
+ -->
1
7
  <script>import { getContext } from 'svelte';
2
8
  import { Field, FORM_CONTEXT } from '@txstate-mws/svelte-forms';
3
9
  import { derivedStore } from '@txstate-mws/svelte-store';
@@ -26,6 +26,12 @@ declare const __propDef: {
26
26
  export type FieldChoicesProps = typeof __propDef.props;
27
27
  export type FieldChoicesEvents = typeof __propDef.events;
28
28
  export type FieldChoicesSlots = typeof __propDef.slots;
29
+ /**
30
+ * A Field component for dynamically rendering checkbox choices accross the available width of a `<div>`. If there are more
31
+ * choices than the available width of the `<div>` will support then it will create multiple rows to render within using
32
+ * flex. Ordering is top down by default but can be order horizontally by toggling `leftToRight`.
33
+ * The value of the field will be an array corresponding to the values of the checkboxes that are checked.
34
+ */
29
35
  export default class FieldChoices extends SvelteComponentTyped<FieldChoicesProps, FieldChoicesEvents, FieldChoicesSlots> {
30
36
  }
31
37
  export {};
@@ -1,32 +1,45 @@
1
+ <!-- @component
2
+ The purpose of FieldMultiselect is to provide a Field with text input having a popup menu
3
+ that displays, or completes, choice selections based on what's been typed in the text input.
4
+ Selected choices will be added to a list of selected items, in a pill format, that provides
5
+ a means for tracking and removing existing selections. The choices listed in the popup are
6
+ controlled by the parent component via the `getOptions` function that will be used as a
7
+ debounced callback on the contents of the text input.
8
+ -->
1
9
  <script>import { MultiSelect } from '@txstate-mws/svelte-components';
2
10
  import { Store } from '@txstate-mws/svelte-store';
3
11
  import { onMount } from 'svelte';
4
12
  import { isNotBlank } from 'txstate-utils';
5
13
  import FieldStandard from './FieldStandard.svelte';
6
14
  import { getDescribedBy } from './helpers';
7
- export let id = undefined;
8
15
  export let path;
16
+ /** Function to pass to the component that tells it how to use the text
17
+ in the text input to determine what `PopupMenuItem[]` should be displayed
18
+ in the `<PopupMenu>`. Items already 'selected' from the popup menu will be
19
+ tracked and automatically filtered from the popup if returned as one of the
20
+ `PopupMenuItem[]` by `getOptions`. */
21
+ export let getOptions;
22
+ export let id = undefined;
9
23
  export let label = '';
24
+ /** Text to display in the text input when it's empty. */
10
25
  export let placeholder = '';
11
26
  export let disabled = false;
12
27
  export let defaultValue = [];
13
28
  export let conditional = undefined;
14
29
  export let required = false;
30
+ /** Max number of selections to be allowed before disabling the input - 0 for unlimited. */
15
31
  export let maxSelections = 0;
16
- export let getOptions;
32
+ export let lookupByValue = async (val) => { const options = await getOptions(val); return options.find((opt) => opt.value === val); };
17
33
  export let related = 0;
18
34
  export let extradescid = undefined;
19
35
  export let helptext = undefined;
20
- // each time we run getOptions we will save the value -> label mappings
21
- // that it finds, so that we can display labels on pills
36
+ // Each time we run getOptions we will save the value -> label mappings
37
+ // that it finds, so that we can display labels on pills.
22
38
  const valueToLabel = {};
23
39
  async function wrapGetOptions(search) {
24
40
  const opts = await getOptions(search);
25
- // if no options are returned with the search term, we can end up with an infinite loop
26
- // the first time reactToValue calls wrapGetOptions
27
- // if (opts.length === 0) {
28
- // opts = await getOptions('')
29
- // }
41
+ /** If no options are returned with the search term, we can end up with an infinite loop the first time reactToValue calls wrapGetOptions */
42
+ // if (opts.length === 0) opts = await getOptions('')
30
43
  for (const opt of opts) {
31
44
  valueToLabel[opt.value] = opt.label || opt.value;
32
45
  }
@@ -41,14 +54,17 @@ onMount(async () => {
41
54
  await reactToValue(defaultValue);
42
55
  hasInit = true;
43
56
  });
44
- // if we get a value from the form that we haven't seen in the popup menu
45
- // yet, we won't have a label for it
46
- // this function runs getOptions on any selected values for which the label
47
- // is currently unknown
57
+ // If we get a value from the form that we haven't seen in the popup menu
58
+ // yet, we won't have a label for it.
59
+ // This function runs getOptions on any selected values for which the label
60
+ // is currently unknown.
48
61
  async function reactToValue(value) {
49
62
  await Promise.all(value.map(async (v) => {
50
- if (!valueToLabel[v])
51
- await wrapGetOptions(v);
63
+ if (!valueToLabel[v]) {
64
+ const item = await lookupByValue(v);
65
+ if (item)
66
+ valueToLabel[item.value] = item.label ?? item.value;
67
+ }
52
68
  }));
53
69
  selectedStore.set(value.map(v => ({ value: v, label: valueToLabel[v] })).filter(v => isNotBlank(v.label)));
54
70
  }
@@ -2,16 +2,21 @@ import { SvelteComponentTyped } from "svelte";
2
2
  import type { PopupMenuItem } from '@txstate-mws/svelte-components';
3
3
  declare const __propDef: {
4
4
  props: {
5
- id?: string | undefined;
6
5
  path: string;
6
+ /** Function to pass to the component that tells it how to use the text
7
+ in the text input to determine what `PopupMenuItem[]` should be displayed
8
+ in the `<PopupMenu>`. Items already 'selected' from the popup menu will be
9
+ tracked and automatically filtered from the popup if returned as one of the
10
+ `PopupMenuItem[]` by `getOptions`. */ getOptions: (search: string) => Promise<PopupMenuItem[]>;
11
+ id?: string | undefined;
7
12
  label?: string | undefined;
8
- placeholder?: string | undefined;
13
+ /** Text to display in the text input when it's empty. */ placeholder?: string | undefined;
9
14
  disabled?: boolean | undefined;
10
15
  defaultValue?: string[] | undefined;
11
16
  conditional?: boolean | undefined;
12
17
  required?: boolean | undefined;
13
- maxSelections?: number | undefined;
14
- getOptions: (search: string) => Promise<PopupMenuItem[]>;
18
+ /** Max number of selections to be allowed before disabling the input - 0 for unlimited. */ maxSelections?: number | undefined;
19
+ lookupByValue?: ((val: string) => Promise<PopupMenuItem | undefined>) | undefined;
15
20
  related?: number | true | undefined;
16
21
  extradescid?: string | undefined;
17
22
  helptext?: string | undefined;
@@ -24,6 +29,14 @@ declare const __propDef: {
24
29
  export type FieldMultiselectProps = typeof __propDef.props;
25
30
  export type FieldMultiselectEvents = typeof __propDef.events;
26
31
  export type FieldMultiselectSlots = typeof __propDef.slots;
32
+ /**
33
+ * The purpose of FieldMultiselect is to provide a Field with text input having a popup menu
34
+ * that displays, or completes, choice selections based on what's been typed in the text input.
35
+ * Selected choices will be added to a list of selected items, in a pill format, that provides
36
+ * a means for tracking and removing existing selections. The choices listed in the popup are
37
+ * controlled by the parent component via the `getOptions` function that will be used as a
38
+ * debounced callback on the contents of the text input.
39
+ */
27
40
  export default class FieldMultiselect extends SvelteComponentTyped<FieldMultiselectProps, FieldMultiselectEvents, FieldMultiselectSlots> {
28
41
  }
29
42
  export {};
@@ -1,10 +1,22 @@
1
+ <!-- @component
2
+ The purpose of `<FieldStandard>` is to provide a standard [`<Field>`](https://github.com/txstate-etc/svelte-forms#field)
3
+ component that not only handles the association of a state/value with the JSON payload associated with a
4
+ [`FormStore`](https://github.com/txstate-etc/svelte-forms/blob/main/src/lib/FormStore.ts) but also handles
5
+ common rendering for helptext, aria descriptions, and `Feedback` messages for its slotted components
6
+ by encapsulating them in a [`<Container>`](https://github.com/txstate-etc/dosgato-dialog/blob/main/src/lib/Container.svelte)
7
+ setup to work in conjuction with the parent `<Field>` tag.
8
+ -->
1
9
  <script>import { Field } from '@txstate-mws/svelte-forms';
2
10
  import { randomid } from 'txstate-utils';
3
11
  import Container from './Container.svelte';
12
+ /** If the input that's being built has an id pass it here so the label can point at it. */
4
13
  export let id = randomid();
14
+ /** If `descid` is defined then this assumes you've made an outside label referenced to by descid `<div id={descid}`.
15
+ Useful for things like checkboxes and radio buttons that have their own individual labels. */
5
16
  export let descid = undefined;
6
17
  export let path;
7
18
  export let defaultValue = undefined;
19
+ /** A label for the Container `<div>`. */
8
20
  export let label;
9
21
  export let notNull = false;
10
22
  export let number = false;
@@ -13,6 +25,7 @@ export let datetime = false;
13
25
  export let boolean = false;
14
26
  export let serialize = undefined;
15
27
  export let deserialize = undefined;
28
+ /** If you need to do some processing just before submit or validate define that processing here. */
16
29
  export let finalize = undefined;
17
30
  export let conditional = undefined;
18
31
  export let required = false;
@@ -1,11 +1,12 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
2
  declare const __propDef: {
3
3
  props: {
4
- id?: string | undefined;
5
- descid?: string | undefined;
4
+ /** If the input that's being built has an id pass it here so the label can point at it. */ id?: string | undefined;
5
+ /** If `descid` is defined then this assumes you've made an outside label referenced to by descid `<div id={descid}`.
6
+ Useful for things like checkboxes and radio buttons that have their own individual labels. */ descid?: string | undefined;
6
7
  path: string;
7
8
  defaultValue?: any;
8
- label: string;
9
+ /** A label for the Container `<div>`. */ label: string;
9
10
  notNull?: boolean | undefined;
10
11
  number?: boolean | undefined;
11
12
  date?: boolean | undefined;
@@ -13,7 +14,7 @@ declare const __propDef: {
13
14
  boolean?: boolean | undefined;
14
15
  serialize?: ((value: any) => string) | undefined;
15
16
  deserialize?: ((value: string) => any) | undefined;
16
- finalize?: ((value: any) => any) | undefined;
17
+ /** If you need to do some processing just before submit or validate define that processing here. */ finalize?: ((value: any) => any) | undefined;
17
18
  conditional?: boolean | undefined;
18
19
  required?: boolean | undefined;
19
20
  related?: number | true | undefined;
@@ -42,6 +43,14 @@ declare const __propDef: {
42
43
  export type FieldStandardProps = typeof __propDef.props;
43
44
  export type FieldStandardEvents = typeof __propDef.events;
44
45
  export type FieldStandardSlots = typeof __propDef.slots;
46
+ /**
47
+ * The purpose of `<FieldStandard>` is to provide a standard [`<Field>`](https://github.com/txstate-etc/svelte-forms#field)
48
+ * component that not only handles the association of a state/value with the JSON payload associated with a
49
+ * [`FormStore`](https://github.com/txstate-etc/svelte-forms/blob/main/src/lib/FormStore.ts) but also handles
50
+ * common rendering for helptext, aria descriptions, and `Feedback` messages for its slotted components
51
+ * by encapsulating them in a [`<Container>`](https://github.com/txstate-etc/dosgato-dialog/blob/main/src/lib/Container.svelte)
52
+ * setup to work in conjuction with the parent `<Field>` tag.
53
+ */
45
54
  export default class FieldStandard extends SvelteComponentTyped<FieldStandardProps, FieldStandardEvents, FieldStandardSlots> {
46
55
  }
47
56
  export {};
package/dist/Icon.svelte CHANGED
@@ -1,6 +1,12 @@
1
+ <!-- @component
2
+ This is a wrapper component around iconify's [OfflineIcon](https://docs.iconify.design/icon-components/svelte/offline.html)
3
+ implementation of icons that adds a hidden label that can be read by screen readers. Useful for situations where aria-label
4
+ isn't supported, to provide in kind icon support, while still making use of aria attributes regardless of support.
5
+ -->
1
6
  <script>import Icon from '@iconify/svelte/dist/OfflineIcon.svelte';
2
7
  import { ScreenReaderOnly } from '@txstate-mws/svelte-components';
3
8
  export let icon;
9
+ /** Label used in a `<ScreenReaderOnly>`. */
4
10
  export let hiddenLabel = undefined;
5
11
  export let inline = false;
6
12
  export let width = '1em';
@@ -3,7 +3,7 @@ import type { IconifyIcon } from '@iconify/svelte';
3
3
  declare const __propDef: {
4
4
  props: {
5
5
  icon: IconifyIcon | undefined;
6
- hiddenLabel?: string | undefined;
6
+ /** Label used in a `<ScreenReaderOnly>`. */ hiddenLabel?: string | undefined;
7
7
  inline?: boolean | undefined;
8
8
  width?: string | number | undefined;
9
9
  height?: string | number | undefined;
@@ -17,6 +17,11 @@ declare const __propDef: {
17
17
  export type IconProps = typeof __propDef.props;
18
18
  export type IconEvents = typeof __propDef.events;
19
19
  export type IconSlots = typeof __propDef.slots;
20
+ /**
21
+ * This is a wrapper component around iconify's [OfflineIcon](https://docs.iconify.design/icon-components/svelte/offline.html)
22
+ * implementation of icons that adds a hidden label that can be read by screen readers. Useful for situations where aria-label
23
+ * isn't supported, to provide in kind icon support, while still making use of aria attributes regardless of support.
24
+ */
20
25
  export default class Icon extends SvelteComponentTyped<IconProps, IconEvents, IconSlots> {
21
26
  }
22
27
  export {};
@@ -1,3 +1,6 @@
1
+ <!-- @component
2
+ The purpose of this component is to provide common `Feedback` message styling with icons that support screen readers.
3
+ -->
1
4
  <script>import alertCircleOutline from '@iconify-icons/mdi/alert-circle-outline.js';
2
5
  import checkCircleOutline from '@iconify-icons/mdi/check-circle-outline.js';
3
6
  import informationOutline from '@iconify-icons/mdi/information-outline.js';
@@ -11,6 +14,16 @@ const icons = {
11
14
  system: closeOctagonOutline
12
15
  };
13
16
  $: icon = icons[message.type] ?? alertCircleOutline;
17
+ // Would we like to add something like the following for non-Error message types being used in aria descriptions?
18
+ /*
19
+ const ariaLables = {
20
+ error: 'Error',
21
+ warning: 'Warning',
22
+ success: 'Success',
23
+ system: 'System'
24
+ }
25
+ $: hiddenLabel = ariaLables[message.type] ?? 'Error'
26
+ */
14
27
  </script>
15
28
 
16
29
  <div class={message.type}><Icon width='1.5em' {icon} hiddenLabel='Error' /><span>{message.message}</span></div>
@@ -12,6 +12,7 @@ declare const __propDef: {
12
12
  export type InlineMessageProps = typeof __propDef.props;
13
13
  export type InlineMessageEvents = typeof __propDef.events;
14
14
  export type InlineMessageSlots = typeof __propDef.slots;
15
+ /** The purpose of this component is to provide common `Feedback` message styling with icons that support screen readers. */
15
16
  export default class InlineMessage extends SvelteComponentTyped<InlineMessageProps, InlineMessageEvents, InlineMessageSlots> {
16
17
  }
17
18
  export {};
@@ -1,7 +1,10 @@
1
+ <!-- @component
2
+ This renders the `Feedback` messages bound to it if there are any to display.
3
+ -->
1
4
  <script>import { randomid } from 'txstate-utils';
2
5
  import InlineMessage from './InlineMessage.svelte';
3
- export let id = randomid();
4
6
  export let messages;
7
+ export let id = randomid();
5
8
  const savedid = id;
6
9
  $: id = messages.length ? savedid : undefined;
7
10
  </script>
@@ -2,8 +2,8 @@ import { SvelteComponentTyped } from "svelte";
2
2
  import type { Feedback } from '@txstate-mws/svelte-forms';
3
3
  declare const __propDef: {
4
4
  props: {
5
- id?: string | undefined;
6
5
  messages: Feedback[];
6
+ id?: string | undefined;
7
7
  };
8
8
  events: {
9
9
  [evt: string]: CustomEvent<any>;
@@ -13,6 +13,7 @@ declare const __propDef: {
13
13
  export type InlineMessagesProps = typeof __propDef.props;
14
14
  export type InlineMessagesEvents = typeof __propDef.events;
15
15
  export type InlineMessagesSlots = typeof __propDef.slots;
16
+ /** This renders the `Feedback` messages bound to it if there are any to display. */
16
17
  export default class InlineMessages extends SvelteComponentTyped<InlineMessagesProps, InlineMessagesEvents, InlineMessagesSlots> {
17
18
  }
18
19
  export {};
package/dist/Tabs.svelte CHANGED
@@ -9,6 +9,7 @@ export let tabs;
9
9
  export let active = undefined;
10
10
  export let store = new TabStore(tabs, active);
11
11
  export let disableDialogControl = false;
12
+ export let accordionOnMobile = true;
12
13
  $: store.update(v => ({ ...v, tabs }));
13
14
  const activeStore = new Store({});
14
15
  const tabelements = [];
@@ -29,7 +30,7 @@ const accordion = store.accordion();
29
30
  $: cols = Math.min(Math.floor(($store.clientWidth ?? 1024) / 90), $store.tabs.length);
30
31
  $: scalefactor = Math.min(roundTo(($store.clientWidth ?? 1024) / (cols * 130), 4), 1);
31
32
  $: wrapping = cols !== $store.tabs.length;
32
- $: dialogContext.hasTabs = !$accordion;
33
+ $: dialogContext.hasTabs = !$accordion || !accordionOnMobile;
33
34
  function onClick(idx) {
34
35
  return () => store.activate(idx);
35
36
  }
@@ -86,7 +87,7 @@ onMount(reactToCurrent);
86
87
  </script>
87
88
 
88
89
  {#if $store.tabs.length > 1}
89
- {#if !$accordion}
90
+ {#if !$accordion || !accordionOnMobile}
90
91
  <!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
91
92
  <ul use:resize={{ store }} class="tabs-buttons" role="tablist">
92
93
  {#each $store.tabs as tab, idx (tab.name)}
@@ -6,6 +6,7 @@ declare const __propDef: {
6
6
  active?: string | undefined;
7
7
  store?: TabStore | undefined;
8
8
  disableDialogControl?: boolean | undefined;
9
+ accordionOnMobile?: boolean | undefined;
9
10
  };
10
11
  events: {
11
12
  [evt: string]: CustomEvent<any>;
@@ -9,7 +9,7 @@ export let larger = false;
9
9
  <div class="dialog-chooser-thumbnail">
10
10
  {#if item.type === 'asset'}
11
11
  {#if item.image}
12
- <img src={(larger ? item.image.thumbnailUrl : item.image.previewUrl) ?? item.image.thumbnailUrl ?? item.url} alt="" />
12
+ <img src={(larger ? (item.image.previewUrl ?? item.image.thumbnailUrl) : (item.image.thumbnailUrl ?? item.image.previewUrl)) ?? item.url} alt="" />
13
13
  {:else}
14
14
  <FileIcon mime={item.mime} width='5em' />
15
15
  {/if}
@@ -108,7 +108,7 @@ let focusWithin = false;
108
108
  </script>
109
109
 
110
110
  <svelte:window on:mousemove={onMouseMove} on:mouseup={onMouseUp} on:touchend={onMouseUp} on:touchcancel={onMouseUp} />
111
- <FieldStandard bind:id {label} {path} {required} {conditional} {helptext} {descid} let:value let:setVal let:helptextid>
111
+ <FieldStandard bind:id {label} {path} {required} conditional={conditional && isNotBlank(imageSrc)} {helptext} {descid} let:value let:setVal let:helptextid>
112
112
  {@const _ = init(value, setVal)}
113
113
  {#if isNotBlank(imageSrc)}
114
114
  <div on:focusin={() => { focusWithin = true }} on:focusout={() => { focusWithin = false }}>
@@ -3,7 +3,7 @@ declare const __propDef: {
3
3
  props: {
4
4
  id?: string | undefined;
5
5
  path: string;
6
- imageSrc: string;
6
+ imageSrc: string | undefined;
7
7
  selectionAspectRatio?: number | undefined;
8
8
  minSelection?: number | undefined;
9
9
  label?: string | undefined;
@@ -1,10 +1,12 @@
1
1
  <script>import { resize } from '@txstate-mws/svelte-components';
2
2
  import { derivedStore, Store } from '@txstate-mws/svelte-store';
3
- import { afterUpdate, beforeUpdate, onDestroy, onMount, setContext, tick } from 'svelte';
3
+ import { afterUpdate, beforeUpdate, onDestroy, onMount, setContext } from 'svelte';
4
+ import { toArray } from 'txstate-utils';
4
5
  import LoadIcon from './LoadIcon.svelte';
5
6
  import TreeNode from './TreeNode.svelte';
6
7
  import { getHashId, TreeStore, TREE_STORE_CONTEXT } from './treestore';
7
8
  export let headers;
9
+ export let searchable = undefined;
8
10
  export let nodeClass = undefined;
9
11
  export let singleSelect = undefined;
10
12
  export let enableResize = false;
@@ -53,6 +55,28 @@ $: store.singleSelect = singleSelect;
53
55
  function onDragEnd() {
54
56
  store.update(v => ({ ...v, dragging: false }));
55
57
  }
58
+ let search = '';
59
+ let searchTimer = 0;
60
+ $: searchableFn = searchable == null ? undefined : (typeof searchable === 'function' ? (itm) => toArray(searchable(itm)) : (itm) => [itm[searchable]]);
61
+ function onKeyUp(e) {
62
+ if (!searchableFn)
63
+ return;
64
+ if (e.key.length === 1) {
65
+ search += e.key;
66
+ const searchItems = $store.focused?.parent ? $store.focused.parent.children : $store.rootItems;
67
+ const newFocus = searchItems?.find(itm => searchableFn(itm).some(str => str.toLocaleLowerCase().startsWith(search.toLocaleLowerCase())));
68
+ if (newFocus)
69
+ store.focus(newFocus);
70
+ clearTimeout(searchTimer);
71
+ searchTimer = setTimeout(() => { search = ''; }, 4000);
72
+ }
73
+ else if (e.key === 'Backspace') {
74
+ search = search.substring(0, search.length - 1);
75
+ }
76
+ else if (e.key === 'Escape') {
77
+ search = '';
78
+ }
79
+ }
56
80
  let dragheaderid;
57
81
  let dragheaderidx;
58
82
  let dragtarget;
@@ -126,13 +150,13 @@ afterUpdate(() => {
126
150
  <div class="tree-header" class:resizing={!!dragheaderid} use:resize={{ store: treeWidth }} aria-hidden="true" on:mouseup={headerDragEnd} on:touchend={headerDragEnd} on:mousemove={dragheaderid ? headerDrag : undefined} on:touchmove={dragheaderid ? headerDrag : undefined}>
127
151
  <div class="checkbox" bind:this={checkboxelement}>&nbsp;</div>
128
152
  {#each headers as header, i (header.label)}
129
- <div bind:this={headerelements[i]} id={header.id} class="tree-header-cell {header.id}" style:width={$headerOverride[header.id] ?? $headerSizes?.[i]}>{header.label}{#if i === 0 && $store.loading}<LoadIcon />{/if}</div>
153
+ <div bind:this={headerelements[i]} id={header.id} class="tree-header-cell {header.id}" style:width={$headerOverride[header.id] ?? $headerSizes?.[i]}>{header.label}{#if i === 0 && $store.loading}<LoadIcon />{/if}{#if i === 0 && search}&nbsp;(searching: {search}){/if}</div>
130
154
  {#if enableResize && i !== headers.length - 1}<div class="tree-separator" on:mousedown={headerDragStart(header, i)} on:touchstart={headerDragStart(header, i)} on:dblclick={headerDragReset}>&nbsp;</div>{/if}
131
155
  {/each}
132
156
  </div>
133
157
  {#if mounted && $rootItems?.length}
134
158
  <!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
135
- <ul bind:this={store.treeElement} role="tree" class:resizing={!!dragheaderid} on:mousemove={dragheaderid ? headerDrag : undefined} on:touchmove={dragheaderid ? headerDrag : undefined} on:mouseup={headerDragEnd} on:touchend={headerDragEnd}>
159
+ <ul bind:this={store.treeElement} role="tree" class:resizing={!!dragheaderid} on:mousemove={dragheaderid ? headerDrag : undefined} on:touchmove={dragheaderid ? headerDrag : undefined} on:mouseup={headerDragEnd} on:touchend={headerDragEnd} on:keyup={onKeyUp}>
136
160
  {#each $rootItems as item, i (item.id)}
137
161
  <TreeNode
138
162
  {item}
@@ -4,6 +4,7 @@ import type { DragEligibleFn, CopyHandlerFn, DropEffectFn, FetchChildrenFn, Move
4
4
  declare class __sveltets_Render<T extends TreeItemFromDB> {
5
5
  props(): {
6
6
  headers: TreeHeader<T>[];
7
+ searchable?: keyof T | ((item: T) => string | string[]) | undefined;
7
8
  nodeClass?: ((itm: T) => string) | undefined;
8
9
  singleSelect?: boolean | undefined;
9
10
  enableResize?: boolean | undefined;
@@ -209,6 +209,7 @@ export class TreeStore extends ActiveStore {
209
209
  this.trigger();
210
210
  const selectedItems = Array.from(droppedItems.values());
211
211
  const commonparent = this.findCommonParent([...selectedItems, item]);
212
+ this.focus(item, false);
212
213
  try {
213
214
  const result = dropEffect === 'move'
214
215
  ? await this.moveHandler?.(selectedItems, item, above)
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": "0.0.52",
4
+ "version": "0.0.53",
5
5
  "scripts": {
6
6
  "prepublishOnly": "svelte-package",
7
7
  "dev": "vite dev --force",