@dosgato/dialog 0.0.60 → 0.0.62

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.
@@ -48,7 +48,7 @@ $: setNeedsShowHelp(helpelement);
48
48
  {#if helptext}
49
49
  <!-- svelte-ignore a11y-click-events-have-key-events -->
50
50
  <div use:resize={{ debounce: 10 }} on:resize={setNeedsShowHelp} id={helptextid} class="dialog-field-help" class:needsShowHelp class:expanded={showhelp} on:click={() => { if (needsShowHelp) showhelp = !showhelp }}>
51
- <span bind:this={helpelement}>{helptext}</span>
51
+ <span bind:this={helpelement}>{@html helptext}</span>
52
52
  {#if needsShowHelp}
53
53
  <button type="button" class="dialog-field-help-expand">Show {#if showhelp}Less{:else}More{/if}<ScreenReaderOnly>, ignore this, the help text it controls will be read to you as input description</ScreenReaderOnly></button>
54
54
  {/if}
@@ -48,7 +48,7 @@ $: describedby = [title ? labelid : undefined, descid].filter(isNotBlank).join('
48
48
  <slot></slot>
49
49
  </div>
50
50
  <footer class="actions">
51
- <slot name="buttons" {nextTitle} {prevTitle} hasRequired={hasRequired && !ignoreTabs} onPrev={onPrev} onNext={onNext}>
51
+ <slot name="buttons" {nextTitle} {prevTitle} hasRequired={hasRequired && !ignoreTabs} onPrev={onPrev} onNext={onNext} {describedby}>
52
52
  {#if prevTitle && !ignoreTabs}
53
53
  <Button class="prev" disabled={!prevTitle} on:click={onPrev}><Icon icon={arrowLeftLight} inline /> Previous<ScreenReaderOnly> Tab ({prevTitle})</ScreenReaderOnly></Button>
54
54
  {/if}
@@ -39,6 +39,7 @@ declare const __propDef: {
39
39
  hasRequired: boolean;
40
40
  onPrev: (() => void) | undefined;
41
41
  onNext: (() => void) | undefined;
42
+ describedby: string;
42
43
  };
43
44
  };
44
45
  };
@@ -1,10 +1,9 @@
1
1
  <script>import applicationOutline from '@iconify-icons/mdi/application-outline';
2
2
  import folderIcon from '@iconify-icons/ph/folder';
3
3
  import folderNotchOpen from '@iconify-icons/ph/folder-notch-open';
4
- import { derivedStore } from '@txstate-mws/svelte-store';
5
4
  import { createEventDispatcher, getContext, onMount, setContext } from 'svelte';
6
- import { isNotBlank } from 'txstate-utils';
7
- import { Dialog, iconForMime, Tabs, Tree, TreeStore } from '..';
5
+ import { isNotBlank, randomid, sleep } from 'txstate-utils';
6
+ import { Button, Dialog, iconForMime, Tabs, Tree, UploadUI } from '..';
8
7
  import { CHOOSER_API_CONTEXT } from './ChooserAPI';
9
8
  import { CHOOSER_STORE_CONTEXT, ChooserStore } from './ChooserStore';
10
9
  import Details from './Details.svelte';
@@ -22,21 +21,17 @@ export let activeSources = undefined;
22
21
  export let passthruFilters = undefined;
23
22
  export let filter = undefined;
24
23
  export let store = new ChooserStore(chooserClient);
24
+ store.filter = filter;
25
25
  setContext(CHOOSER_STORE_CONTEXT, store);
26
26
  const dispatch = createEventDispatcher();
27
+ let showuploader = false;
27
28
  let tabStore;
28
- const { sources, source, preview } = store;
29
+ const { sources, source, preview, treeStore, selected } = store;
29
30
  $: currentName = tabStore?.currentName();
30
- async function fetchChildren(item) {
31
- if (item?.type === 'asset' || !$source)
32
- return [];
33
- const children = await chooserClient.getChildren($source.name, item?.path ?? '/', filter);
34
- return children.map(c => ({ ...c, children: undefined }));
31
+ $: if ($currentName) {
32
+ store.setSource($currentName);
33
+ treeStore.refresh().catch(console.error);
35
34
  }
36
- const treeStore = new TreeStore(fetchChildren);
37
- const selected = derivedStore(treeStore, 'selectedItems.0');
38
- $: store.setSource($currentName);
39
- $: selectPreview($preview, $source);
40
35
  $: store.setPreview($selected);
41
36
  function onChoose() {
42
37
  dispatch('change', $store.preview);
@@ -44,28 +39,27 @@ function onChoose() {
44
39
  function onDeselect() {
45
40
  store.setPreview(undefined);
46
41
  }
42
+ function onUploadComplete() {
43
+ treeStore.openAndRefresh($selected).catch(console.error);
44
+ showuploader = false;
45
+ }
47
46
  async function openRecursive(pathSplit, depth) {
48
47
  let curr = $treeStore.rootItems?.find(itm => itm.name === pathSplit[0]);
49
48
  for (let i = 0; i < depth; i++) {
50
49
  curr = curr?.children?.find(c => c.name === pathSplit[i + 1]);
51
50
  }
52
- if (!curr)
53
- throw new Error('tried to open a path that does not exist');
51
+ if (!curr) {
52
+ console.warn('tried to preload a path', '/' + pathSplit.join('/'), 'that does not exist ');
53
+ return;
54
+ }
54
55
  await treeStore.open(curr, false);
55
56
  if (depth + 1 >= pathSplit.length)
56
57
  return curr;
57
58
  return await openRecursive(pathSplit, depth + 1);
58
59
  }
59
- async function selectPreview(..._) {
60
+ async function selectPreview(preloadPath) {
60
61
  if (!$store.initialized)
61
62
  return;
62
- await treeStore.refresh(undefined);
63
- const preloadSource = $preview?.source ?? initialSource;
64
- if ($source?.name !== preloadSource)
65
- return;
66
- const preloadPath = $store.preview?.path ?? initialPath;
67
- initialSource = undefined;
68
- initialPath = undefined;
69
63
  if (preloadPath) {
70
64
  const currentSelection = await openRecursive(preloadPath.split('/').filter(isNotBlank), 0);
71
65
  treeStore.trigger();
@@ -77,18 +71,20 @@ async function selectPreview(..._) {
77
71
  }
78
72
  }
79
73
  onMount(async () => {
80
- await store.init({ images, pages, assets, folders, activeSources, initialSource, initialPath, passthruFilters, filter });
81
- if ($source?.name)
82
- tabStore?.activateName($source.name);
83
- await selectPreview();
74
+ const preloadSource = $preview?.source ?? $selected?.source ?? $source?.name ?? initialSource;
75
+ const preloadPath = $preview?.path ?? $selected?.path ?? initialPath;
76
+ await store.init({ images, pages, assets, folders, activeSources, initialSource: preloadSource, initialPath: preloadPath, passthruFilters, filter });
77
+ await treeStore.refresh();
78
+ await selectPreview(preloadPath);
84
79
  });
80
+ const previewId = randomid();
85
81
  </script>
86
82
 
87
- <Dialog size="xl" ignoreTabs title={label} on:escape continueText="Choose" disabled={!$store.preview && required} cancelText="Cancel" on:continue={onChoose}>
83
+ <Dialog size="xl" ignoreTabs title={label} on:escape continueText="Choose" disabled={!$preview && required} cancelText="Cancel">
88
84
  <section class="dialog-chooser-window">
89
85
  <header class="dialog-chooser-controls">
90
86
  {#if $sources.length > 1}
91
- <Tabs bind:store={tabStore} tabs={$sources.map(s => ({ name: s.name, title: s.label ?? s.name }))} />
87
+ <Tabs bind:store={tabStore} tabs={$sources.map(s => ({ name: s.name, title: s.label ?? s.name }))} active={$preview?.source ?? $selected?.source ?? $source?.name ?? initialSource} />
92
88
  {/if}
93
89
  </header>
94
90
  <section class="dialog-chooser-chooser">
@@ -98,13 +94,23 @@ onMount(async () => {
98
94
  ]} singleSelect store={treeStore} on:deselect={onDeselect} on:choose={onChoose} />
99
95
  {/if}
100
96
  </section>
101
- <section class="dialog-chooser-preview" tabindex="-1">
97
+ <section id={previewId} class="dialog-chooser-preview" tabindex="-1">
102
98
  {#if $preview}
103
99
  <Thumbnail item={$preview} larger />
104
100
  <Details item={$preview} />
105
101
  {/if}
106
102
  </section>
107
103
  </section>
104
+ <svelte:fragment slot="buttons" let:describedby>
105
+ {#if chooserClient.upload && $source?.type === 'asset'}
106
+ <Button class="upload" disabled={$selected?.type !== 'folder' || !(chooserClient.mayUpload?.($selected) ?? true)} on:click={() => { showuploader = true }}>Upload</Button>
107
+ {/if}
108
+ <Button cancel {describedby} on:click={() => dispatch('escape')}>Cancel</Button>
109
+ <Button class="primary" disabled={!$preview && required} describedby={previewId} on:click={onChoose}>Choose</Button>
110
+ </svelte:fragment>
111
+ {#if showuploader && $selected?.type === 'folder' && chooserClient.upload}
112
+ <UploadUI title="Upload to {$selected.path}" folder={$selected} uploader={chooserClient.upload.bind(chooserClient)} on:escape={() => { showuploader = false }} on:saved={onUploadComplete}/>
113
+ {/if}
108
114
  </Dialog>
109
115
 
110
116
  <style>
@@ -136,9 +142,12 @@ onMount(async () => {
136
142
  padding: 1em;
137
143
  overflow-y: auto;
138
144
  }
139
- header {
145
+ .dialog-chooser-controls {
140
146
  position: relative;
141
147
  width: 100%;
142
148
  }
149
+ :global(footer.actions .upload) {
150
+ margin-right: auto;
151
+ }
143
152
 
144
153
  </style>
@@ -25,7 +25,20 @@ export interface Client<F = any> {
25
25
  * undo values it generates.
26
26
  */
27
27
  valueToUrl?: (value: string) => string | undefined;
28
- upload: (source: string, path: string, files: FileList) => Promise<void>;
28
+ /**
29
+ * must accept a standard FileList object and upload the files to the service
30
+ * should throw an error if the source/path does not accept uploads the UI is
31
+ * responsible for refreshing the folder list after success
32
+ * should call the provided progress function as often as possible so that
33
+ * dosgato-dialog can keep the user informed
34
+ */
35
+ upload?: (folder: Folder, files: File[], progress: (ratio: number) => void) => Promise<(Asset | Folder)[] | undefined>;
36
+ /**
37
+ * return whether the user is allowed to upload to the currently selected folder
38
+ * if this function is not provided it is assumed the answer is always yes
39
+ * may optionally return an array of accepted mime types for the folder
40
+ */
41
+ mayUpload?: (folder: Folder) => boolean | string[];
29
42
  }
30
43
  export interface Source {
31
44
  type: ChooserType;
@@ -1,4 +1,4 @@
1
- import type { TypedTreeItem } from '../tree/treestore.js';
1
+ import { TreeStore, type TypedTreeItem } from '../tree/treestore.js';
2
2
  import { Store } from '@txstate-mws/svelte-store';
3
3
  import type { AnyItem, Asset, Client, ChooserType, Folder, Page, Source } from './ChooserAPI.js';
4
4
  export interface UISource extends Source {
@@ -47,9 +47,13 @@ export declare class ChooserStore<F = any> extends Store<IAssetStore> {
47
47
  options: InternalStoreOptions<F>;
48
48
  constructor(client: Client);
49
49
  setOptions(options: ChooserStoreOptions<F>): void;
50
+ filter: undefined | ((item: AnyItem) => boolean | Promise<boolean>);
51
+ fetchChildren(item?: TypedTreeItem<Page | Folder | Asset>): Promise<AnyItem[]>;
52
+ treeStore: TreeStore<Asset | Folder | Page>;
50
53
  sources: import("@txstate-mws/svelte-store").DerivedStore<UISource[], IAssetStore>;
51
54
  source: import("@txstate-mws/svelte-store").DerivedStore<UISource | undefined, IAssetStore>;
52
55
  preview: import("@txstate-mws/svelte-store").DerivedStore<AnyItem | AnyUIItem | undefined, IAssetStore>;
56
+ selected: import("@txstate-mws/svelte-store").DerivedStore<TypedTreeItem<Asset | Folder | Page> | undefined, any>;
53
57
  getSource(state?: IAssetStore): UISource | undefined;
54
58
  getSourceIndex(name: string, state?: IAssetStore, type?: ChooserType): number;
55
59
  init(options: ChooserStoreOptions<F>): Promise<void>;
@@ -1,6 +1,8 @@
1
+ import { TreeStore } from '../tree/treestore.js';
1
2
  import { Store, derivedStore } from '@txstate-mws/svelte-store';
2
3
  import { tick } from 'svelte';
3
4
  import { findIndex } from 'txstate-utils';
5
+ import { TabStore } from '../TabStore.js';
4
6
  export const CHOOSER_STORE_CONTEXT = {};
5
7
  const nofilter = (x) => true;
6
8
  export function combinePath(path, name) {
@@ -32,9 +34,18 @@ export class ChooserStore extends Store {
32
34
  filter
33
35
  };
34
36
  }
37
+ filter = undefined;
38
+ async fetchChildren(item) {
39
+ const $source = this.getSource();
40
+ if (item?.type === 'asset' || !$source)
41
+ return [];
42
+ return await this.client.getChildren($source.name, item?.path ?? '/', this.filter);
43
+ }
44
+ treeStore = new TreeStore(this.fetchChildren.bind(this));
35
45
  sources = derivedStore(this, v => [...(v.sources?.page ?? []), ...(v.sources?.asset ?? [])].filter(s => this.options.activeSources ? this.options.activeSources.has(s.name) : true));
36
46
  source = derivedStore(this, v => this.getSource(v));
37
47
  preview = derivedStore(this, 'preview');
48
+ selected = derivedStore(this.treeStore, 'selectedItems.0');
38
49
  getSource(state = this.value) {
39
50
  return state.sources?.[state.activetype]?.[state.activesource];
40
51
  }
@@ -53,7 +64,7 @@ export class ChooserStore extends Store {
53
64
  const sources = { page: pageSources.filter(s => !this.options.activeSources || this.options.activeSources.has(s.name)) ?? [], asset: assetSources.filter(s => !this.options.activeSources || this.options.activeSources.has(s.name)) ?? [] };
54
65
  return { ...v, sources };
55
66
  });
56
- this.setSource(this.value.preview?.source ?? this.options.initialSource, true);
67
+ this.setSource(this.options.initialSource, true);
57
68
  await tick();
58
69
  this.update(v => ({ ...v, initialized: true }));
59
70
  }
@@ -0,0 +1,160 @@
1
+ <script>import trashLight from '@iconify-icons/ph/trash-light';
2
+ import { roundTo, unique } from 'txstate-utils';
3
+ import { createEventDispatcher } from 'svelte';
4
+ import { Dialog, FileIcon, Icon } from '..';
5
+ export let title;
6
+ export let folder;
7
+ export let maxFiles = 200;
8
+ export let escapable = true;
9
+ export let mimeWhitelist = [];
10
+ export let mimeBlacklist = [];
11
+ export let uploader;
12
+ $: whitelist = new Set(mimeWhitelist);
13
+ $: blacklist = new Set(mimeBlacklist);
14
+ const dispatch = createEventDispatcher();
15
+ let dragover = 0;
16
+ let uploadList = [];
17
+ let uploadLocked = false;
18
+ let uploadProgress = 0;
19
+ let uploadError;
20
+ function onUploadEnter(e) {
21
+ if (e.dataTransfer?.items.length)
22
+ dragover++;
23
+ }
24
+ function onUploadLeave(e) {
25
+ if (e.dataTransfer?.items.length)
26
+ dragover--;
27
+ }
28
+ function onUploadDrop(e) {
29
+ e.preventDefault();
30
+ dragover = 0;
31
+ if (!uploadLocked && e.dataTransfer?.items?.length) {
32
+ uploadList = unique(uploadList.concat(Array.from(e.dataTransfer.files)), 'name').slice(-1 * maxFiles);
33
+ }
34
+ }
35
+ function onUploadChange(e) {
36
+ const files = e.currentTarget.files;
37
+ if (files?.length)
38
+ uploadList = unique(uploadList.concat(Array.from(files)), 'name').slice(-1 * maxFiles);
39
+ e.currentTarget.value = '';
40
+ }
41
+ async function onUploadSubmit() {
42
+ if (uploadLocked)
43
+ return;
44
+ uploadLocked = true;
45
+ try {
46
+ const data = new FormData();
47
+ for (let i = 0; i < uploadList.length; i++) {
48
+ data.append('file' + i, uploadList[i]);
49
+ }
50
+ // TODO: accept new list of children from uploader function and feed it
51
+ // to the tree instead of refreshing the tree
52
+ await uploader(folder, uploadList, ratio => { uploadProgress = ratio; });
53
+ uploadList = [];
54
+ uploadError = undefined;
55
+ dispatch('saved');
56
+ }
57
+ catch (e) {
58
+ uploadError = e.message;
59
+ }
60
+ finally {
61
+ uploadLocked = false;
62
+ }
63
+ }
64
+ function onUploadEscape() {
65
+ if (!uploadLocked && escapable) {
66
+ uploadList = [];
67
+ uploadError = undefined;
68
+ dispatch('escape');
69
+ }
70
+ }
71
+ function onDeleteFile(file) {
72
+ return () => {
73
+ uploadList = uploadList.filter(f => f !== file);
74
+ };
75
+ }
76
+ </script>
77
+
78
+ <Dialog {title} disabled={!uploadList.length} cancelText="Cancel" continueText="Upload" on:escape={onUploadEscape} on:continue={onUploadSubmit}>
79
+ {#if uploadLocked}
80
+ <progress value={uploadProgress} aria-label="Assets Uploading">{roundTo(100 * uploadProgress)}%</progress>
81
+ {:else}
82
+ {#if uploadError}<div class="error">{uploadError}</div>{/if}
83
+ <form method="POST" enctype="multipart/form-data"
84
+ on:submit|preventDefault|stopPropagation={onUploadSubmit}
85
+ class="uploader" class:dragover={dragover > 0}
86
+ on:dragenter={onUploadEnter} on:dragleave={onUploadLeave}
87
+ on:dragover|preventDefault={() => {}} on:drop={onUploadDrop}
88
+ >
89
+ <input type="file" id="uploader_input" multiple on:change={onUploadChange}>
90
+ <label for="uploader_input">Choose or drag files</label>
91
+ <ul>
92
+ {#each uploadList as file}
93
+ <li>
94
+ <FileIcon width="1.5em" mime={file.type} inline />{file.name}<button type="button" on:click={onDeleteFile(file)}><Icon icon={trashLight} width="1.5em" hiddenLabel="Remove File" inline /></button>
95
+ {#if (whitelist.size && !whitelist.has(file.type)) || (blacklist.size && blacklist.has(file.type))}
96
+ <div class="error">File type not allowed</div>
97
+ {/if}
98
+ </li>
99
+ {/each}
100
+ </ul>
101
+ </form>
102
+ {/if}
103
+ </Dialog>
104
+
105
+ <style>
106
+ .uploader {
107
+ border: 2px dashed #666666;
108
+ border-radius: 0.5em;
109
+ text-align: center;
110
+ padding: 1em;
111
+ min-height: 10em;
112
+ }
113
+ .uploader.dragover {
114
+ border-color: #333333;
115
+ }
116
+ .uploader ul {
117
+ padding: 0;
118
+ margin: 0;
119
+ list-style: none;
120
+ text-align: left;
121
+ }
122
+ progress {
123
+ width: 80%;
124
+ margin-left: 10%;
125
+ }
126
+ input[type="file"] {
127
+ opacity: 0;
128
+ }
129
+ label {
130
+ display: inline-block;
131
+ width: 50%;
132
+ padding: 1em;
133
+ background: #ccc;
134
+ cursor: pointer;
135
+ border-radius: 5px;
136
+ border: 1px solid #ccc;
137
+ margin-bottom: 1em;
138
+ }
139
+ input:focus + label {
140
+ outline: 2px solid blue;
141
+ }
142
+ .error {
143
+ color: red;
144
+ }
145
+ button {
146
+ border: 0;
147
+ background: none;
148
+ padding: 0.5em;
149
+ cursor: pointer;
150
+ }
151
+ li {
152
+ display: flex;
153
+ flex-wrap: wrap;
154
+ align-items: center;
155
+ }
156
+ li div {
157
+ width: 100%;
158
+ }
159
+
160
+ </style>
@@ -0,0 +1,26 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ import { type Client, type Folder } from '..';
3
+ declare const __propDef: {
4
+ props: {
5
+ title: string;
6
+ folder: Folder;
7
+ maxFiles?: number | undefined;
8
+ escapable?: boolean | undefined;
9
+ mimeWhitelist?: string[] | undefined;
10
+ mimeBlacklist?: string[] | undefined;
11
+ uploader: NonNullable<Client['upload']>;
12
+ };
13
+ events: {
14
+ saved: CustomEvent<any>;
15
+ escape: CustomEvent<any>;
16
+ } & {
17
+ [evt: string]: CustomEvent<any>;
18
+ };
19
+ slots: {};
20
+ };
21
+ export type UploadUiProps = typeof __propDef.props;
22
+ export type UploadUiEvents = typeof __propDef.events;
23
+ export type UploadUiSlots = typeof __propDef.slots;
24
+ export default class UploadUi extends SvelteComponentTyped<UploadUiProps, UploadUiEvents, UploadUiSlots> {
25
+ }
26
+ export {};
@@ -1,3 +1,4 @@
1
1
  export * from './ChooserAPI.js';
2
2
  export * from './ChooserStore.js';
3
3
  export { default as Chooser } from './Chooser.svelte';
4
+ export { default as UploadUI } from './UploadUI.svelte';
@@ -1,3 +1,4 @@
1
1
  export * from './ChooserAPI.js';
2
2
  export * from './ChooserStore.js';
3
3
  export { default as Chooser } from './Chooser.svelte';
4
+ export { default as UploadUI } from './UploadUI.svelte';
@@ -146,6 +146,7 @@ afterUpdate(() => {
146
146
  }
147
147
  }
148
148
  });
149
+ $: myRootItems = $store && $filteredRootItems;
149
150
  </script>
150
151
 
151
152
  <svelte:window on:mouseup={headerDragEnd} />
@@ -157,20 +158,20 @@ afterUpdate(() => {
157
158
  {#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}
158
159
  {/each}
159
160
  </div>
160
- {#if mounted && $filteredRootItems?.length}
161
+ {#if mounted && myRootItems?.length}
161
162
  <!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
162
163
  <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}>
163
- {#each $filteredRootItems as item, i (item.id)}
164
+ {#each myRootItems as item, i (item.id)}
164
165
  <TreeNode
165
166
  {item}
166
167
  {headers}
167
168
  {headerSizes}
168
169
  {nodeClass}
169
170
  posinset={i + 1}
170
- setsize={$filteredRootItems.length}
171
+ setsize={myRootItems.length}
171
172
  level={item.level}
172
- prev={$filteredRootItems[i - 1]}
173
- next={$filteredRootItems[i + 1]}
173
+ prev={myRootItems[i - 1]}
174
+ next={myRootItems[i + 1]}
174
175
  on:choose
175
176
  on:deselect
176
177
  />
@@ -7,7 +7,7 @@ $: headerComponent = header.component;
7
7
  </script>
8
8
 
9
9
  {#if header.icon}
10
- <span class="icon"><Icon {icon} inline width="1.3em" /></span>
10
+ <span class="icon"><Icon {icon} inline width="1.5em" /></span>
11
11
  {/if}
12
12
  {#if header.component}
13
13
  <svelte:component this={headerComponent} {item} {header} />
@@ -127,8 +127,6 @@ export class TreeStore extends ActiveStore {
127
127
  }
128
128
  else {
129
129
  this.value.itemsById = {};
130
- for (const child of children)
131
- child.level = 1;
132
130
  this.value.rootItems = children;
133
131
  }
134
132
  this.addLookup(children);
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.60",
4
+ "version": "0.0.62",
5
5
  "scripts": {
6
6
  "prepublishOnly": "svelte-package",
7
7
  "dev": "vite dev --force",