@dosgato/dialog 1.3.4 → 1.3.5

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,6 @@
1
1
  <script lang="ts">
2
2
  import arrowClockwiseFill from '@iconify-icons/ph/arrow-clockwise-fill'
3
- import deleteOutline from '@iconify-icons/mdi/delete-outline'
3
+ import deleteOutline from '@iconify-icons/mdi/delete'
4
4
  import xCircle from '@iconify-icons/ph/x-circle'
5
5
  import { FORM_CONTEXT, FORM_INHERITED_PATH, type FormStore } from '@txstate-mws/svelte-forms'
6
6
  import { getContext } from 'svelte'
@@ -9,6 +9,7 @@
9
9
  import type { AnyItem, Client } from './chooser/ChooserAPI'
10
10
  import Details from './chooser/Details.svelte'
11
11
  import Thumbnail from './chooser/Thumbnail.svelte'
12
+ import AssetTabs from './chooser/AssetTabs.svelte'
12
13
  import { getDescribedBy, FieldStandard, InlineMessage, Icon } from './'
13
14
 
14
15
  export let id: string | undefined = undefined
@@ -28,6 +29,8 @@
28
29
  export let extradescid: string | undefined = undefined
29
30
  export let helptext: string | undefined = undefined
30
31
  export let selectedAsset: AnyItem | RawURL | BrokenURL | undefined = undefined
32
+ export let altTextPath : string | undefined = undefined
33
+ export let altTextRequired: boolean = false
31
34
 
32
35
  // TODO: add a mime type acceptance prop, maybe a regex or function, to prevent users from
33
36
  // choosing unacceptable mime types
@@ -41,6 +44,7 @@
41
44
  let urlEntryInput: HTMLInputElement | undefined
42
45
 
43
46
  const descid = randomid()
47
+ const assetTabsId = randomid()
44
48
  let modalshown = false
45
49
  async function show () {
46
50
  if (selectedAsset && selectedAsset.type !== 'raw' && selectedAsset.type !== 'broken') store.setPreview(selectedAsset)
@@ -51,7 +55,15 @@
51
55
  }
52
56
  function onChange (setVal: any) {
53
57
  return (e) => {
54
- selectedAsset = e.detail
58
+ selectedAsset = e.detail.preview
59
+ if (
60
+ altTextPath &&
61
+ e.detail.copyAltText &&
62
+ selectedAsset?.type === 'asset' &&
63
+ selectedAsset?.image?.altText
64
+ ) {
65
+ formStore.setField(altTextPath, selectedAsset.image.altText).catch(console.error)
66
+ }
55
67
  setVal(selectedAsset?.id)
56
68
  hide()
57
69
  }
@@ -159,25 +171,66 @@
159
171
  }
160
172
  }
161
173
  $: void updateSelected($value)
174
+ function onClickRemove (setVal: any) {
175
+ return (e) => {
176
+ selectedAsset = undefined
177
+ setVal(undefined)
178
+ if (altTextPath) {
179
+ formStore.setField(altTextPath, undefined).catch(console.error)
180
+ }
181
+ }
182
+ }
162
183
  </script>
163
184
 
164
185
  <FieldStandard bind:id {path} {descid} {label} {defaultValue} {conditional} {required} {related} {helptext} let:value let:messagesid let:helptextid let:valid let:invalid let:id let:onBlur let:setVal>
165
186
  {#if selectedAsset?.id}
166
- <div class="dialog-chooser-container" class:urlEntry>
167
- <Thumbnail item={selectedAsset} />
168
- <div class="dialog-chooser-right">
169
- <Details item={selectedAsset} singleLine />
170
- {#if !urlEntry}
171
- <button type="button" on:click={show} aria-describedby={getDescribedBy([descid, messagesid, helptextid, extradescid])}>
172
- <Icon icon={arrowClockwiseFill} inline /> Replace
173
- </button>
174
- <button type="button" on:click={() => { selectedAsset = undefined; setVal(undefined) }} aria-describedby={getDescribedBy([descid, messagesid, helptextid, extradescid])}>
175
- <Icon icon={deleteOutline} inline /> Remove
176
- </button>
177
- {/if}
178
- </div>
179
- </div>
187
+ {#if selectedAsset?.type === 'asset'}
188
+ <div class="asset-chooser-container">
189
+ <div class="preview-container">
190
+ <Thumbnail item={selectedAsset} />
191
+ {#if !urlEntry}
192
+ <div class="actions">
193
+ <button type="button" on:click={show} aria-describedby={getDescribedBy([descid, messagesid, helptextid, extradescid])}>
194
+ <span>Replace</span> <Icon icon={arrowClockwiseFill} inline />
195
+ </button>
196
+ <button type="button" on:click={onClickRemove(setVal)} aria-describedby={getDescribedBy([descid, messagesid, helptextid, extradescid])}>
197
+ <span>Remove</span> <Icon icon={deleteOutline} inline />
198
+ </button>
199
+ </div>
200
+ {/if}
201
+ </div>
202
+ <div class="detail-container">
203
+ <div class="file-name" aria-live="polite">
204
+ <span class="chooser-label">Name:</span>
205
+ <span class="chooser-data">{selectedAsset.name}</span>
206
+ </div>
207
+ <div class="tabs-container">
208
+ <AssetTabs id={assetTabsId} {selectedAsset} showMetadata={$$slots.metadata != null} {altTextPath} {altTextRequired}>
209
+ <slot name="metadata" slot="metadata" selectedAsset={selectedAsset} />
210
+ </AssetTabs>
211
+ </div>
212
+ </div>
213
+ </div>
214
+ {:else}
215
+ <div class="dialog-chooser-container" class:urlEntry>
216
+ <Thumbnail item={selectedAsset} />
217
+ <div class="dialog-chooser-right">
218
+ <Details item={selectedAsset} singleLine />
219
+ {#if !urlEntry}
220
+ <div class="actions">
221
+ <button type="button" on:click={show} aria-describedby={getDescribedBy([descid, messagesid, helptextid, extradescid])}>
222
+ <Icon icon={arrowClockwiseFill} inline /> Replace
223
+ </button>
224
+ <button type="button" on:click={() => { selectedAsset = undefined; setVal(undefined) }} aria-describedby={getDescribedBy([descid, messagesid, helptextid, extradescid])}>
225
+ <Icon icon={deleteOutline} inline /> Remove
226
+ </button>
227
+ </div>
228
+ {/if}
229
+ </div>
230
+ </div>
231
+ {/if}
180
232
  {/if}
233
+
181
234
  {#if urlEntry || !selectedAsset?.id}
182
235
  <div class="dialog-chooser-entry">
183
236
  {#if urlEntry}
@@ -193,7 +246,7 @@
193
246
  <InlineMessage message={{ message: 'Entry does not match an internal resource and is not a valid URL. Nothing will be saved.', type: 'warning' }} />
194
247
  {/if}
195
248
  {#if modalshown}
196
- <Chooser {store} {label} {pages} {assets} {images} {initialSource} {initialPath} {folders} {required} on:change={onChange(setVal)} on:escape={hide} />
249
+ <Chooser {store} {label} {pages} {assets} {images} {initialSource} {initialPath} {folders} {required} showAltTextOption={!!altTextPath} on:change={onChange(setVal)} on:escape={hide} />
197
250
  {/if}
198
251
  </FieldStandard>
199
252
 
@@ -225,16 +278,24 @@
225
278
  :global([data-eq~="400px"]) .dialog-chooser-right {
226
279
  max-width: unset;
227
280
  }
228
- .dialog-chooser-right button {
229
- margin-top: 0.5em;
281
+ .dialog-chooser-right .actions {
282
+ display: flex;
283
+ }
284
+ .dialog-chooser-right button, .asset-chooser-container .actions button {
230
285
  border: 0;
231
- font-weight: bold;
232
286
  border-radius: 0.2em;
233
287
  cursor: pointer;
234
288
  padding: 0.4em;
235
289
  background: none;
290
+ display: flex;
291
+ align-items: center;
292
+ gap: 2px;
293
+ }
294
+ .asset-chooser-container .actions button span {
295
+ font-weight: bold;
296
+ font-size: 0.75em;
236
297
  }
237
- .dialog-chooser-right button:hover {
298
+ .dialog-chooser-right button:hover, .asset-chooser-container .actions button:hover {
238
299
  background-color: #cccccc;
239
300
  }
240
301
  .dialog-chooser-entry {
@@ -281,4 +342,64 @@
281
342
  :global([data-eq~="400px"] .dialog-chooser-container .dialog-chooser-thumbnail img) {
282
343
  object-position: left;
283
344
  }
345
+ .asset-chooser-container {
346
+ display: flex;
347
+ align-items: flex-start;
348
+ gap: 1em;
349
+ padding: 1em;
350
+ }
351
+
352
+ .asset-chooser-container .preview-container {
353
+ width: 20%;
354
+ }
355
+
356
+ .asset-chooser-container .preview-container :global(.dialog-chooser-thumbnail) {
357
+ justify-content: center;
358
+ display: flex;
359
+ }
360
+
361
+ .asset-chooser-container .preview-container .actions {
362
+ display: flex;
363
+ justify-content: space-between;
364
+ flex-wrap: wrap;
365
+ }
366
+
367
+ .asset-chooser-container .detail-container {
368
+ flex: 1;
369
+ border: 1px solid var(--dg-dialog-header-bg, #ddd);
370
+ border-radius: 4px;
371
+ }
372
+
373
+ .asset-chooser-container .detail-container .file-name {
374
+ background-color: var(--dg-dialog-header-bg, #ddd);
375
+ padding: 0.75em;
376
+ word-break: break-word;
377
+ }
378
+
379
+ .asset-chooser-container .detail-container .chooser-label {
380
+ font-weight: bold;
381
+ margin-right: 0.5em;
382
+ }
383
+ .asset-chooser-container .detail-container .tabs-container {
384
+ position: relative;
385
+ }
386
+
387
+ @media (max-width: 800px) {
388
+ .asset-chooser-container {
389
+ flex-direction: column;
390
+ }
391
+ .asset-chooser-container .preview-container {
392
+ width: 100%;
393
+ }
394
+ .asset-chooser-container .preview-container :global(.dialog-chooser-thumbnail img) {
395
+ max-height: 300px;
396
+ }
397
+ .asset-chooser-container .preview-container .actions {
398
+ justify-content: center;
399
+ gap: 2em;
400
+ }
401
+ .asset-chooser-container .detail-container {
402
+ width: 100%;
403
+ }
404
+ }
284
405
  </style>
@@ -20,11 +20,18 @@ declare const __propDef: {
20
20
  extradescid?: string | undefined;
21
21
  helptext?: string | undefined;
22
22
  selectedAsset?: AnyItem | RawURL | BrokenURL | undefined;
23
+ altTextPath?: string | undefined;
24
+ altTextRequired?: boolean;
23
25
  };
24
26
  events: {
25
27
  [evt: string]: CustomEvent<any>;
26
28
  };
27
- slots: {};
29
+ slots: {
30
+ metadata: {
31
+ slot: string;
32
+ selectedAsset: AnyItem | RawURL | BrokenURL | undefined;
33
+ };
34
+ };
28
35
  };
29
36
  export type FieldChooserLinkProps = typeof __propDef.props;
30
37
  export type FieldChooserLinkEvents = typeof __propDef.events;
@@ -0,0 +1,201 @@
1
+ <script lang="ts">
2
+ import { bytesToHuman } from 'txstate-utils'
3
+ import type { Asset } from './ChooserAPI'
4
+ import { Dialog, FieldTextArea } from '..'
5
+ import Button from '../Button.svelte'
6
+ import Icon from '../Icon.svelte'
7
+ import outIcon from '@iconify-icons/ph/arrow-square-out-bold'
8
+ import warningIcon from '@iconify-icons/ph/warning'
9
+ import { getContext } from 'svelte'
10
+ import type { Client } from './ChooserAPI'
11
+ import { CHOOSER_API_CONTEXT } from './ChooserAPI'
12
+
13
+ export let id: string
14
+ export let selectedAsset: Asset
15
+ export let showMetadata: boolean = false
16
+ export let altTextRequired: boolean = false
17
+ // if they say it's required but don't provide a path, default to 'altText'
18
+ export let altTextPath: string | undefined = altTextRequired ? 'altText' : undefined
19
+ const chooserClient = getContext<Client>(CHOOSER_API_CONTEXT)
20
+
21
+ let tabListEl: HTMLUListElement
22
+ let manageAssetsModalOpen = false
23
+
24
+ $: isImage = selectedAsset?.image !== undefined
25
+
26
+ $: tabs = [
27
+ ...(isImage && altTextPath ? [{ id: `${id}-alttext`, label: 'Alternate Text' }] : []),
28
+ { id: `${id}-details`, label: 'Asset Details' },
29
+ ...(showMetadata ? [{ id: `${id}-metadata`, label: 'Metadata' }] : [])
30
+ ]
31
+ $: activeTab = tabs ? tabs[0].id : 0
32
+ let focusedTabIdx = 0
33
+
34
+ function selectTab(tabId: string) {
35
+ activeTab = tabId
36
+ focusedTabIdx = tabs.findIndex(t => t.id === tabId)
37
+ // ensure the focused tab is visible
38
+ const tabEls = tabListEl.querySelectorAll(`[role="tab"][data-tab-asset]`)
39
+ tabEls[focusedTabIdx]?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center'})
40
+ }
41
+
42
+ function focusTab(idx: number) {
43
+ focusedTabIdx = idx
44
+ activeTab = tabs[idx].id
45
+ const tabEls = tabListEl.querySelectorAll(`[role="tab"][data-tab-asset]`)
46
+ if (tabEls[idx]) (tabEls[idx] as HTMLElement).focus()
47
+ tabEls[idx]?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center'})
48
+ }
49
+
50
+ function onTabKeydown(e: KeyboardEvent, idx: number) {
51
+ if (e.key === 'ArrowRight') {
52
+ e.preventDefault()
53
+ focusTab((idx + 1) % tabs.length)
54
+ } else if (e.key === 'ArrowLeft') {
55
+ e.preventDefault()
56
+ focusTab((idx - 1 + tabs.length) % tabs.length)
57
+ } else if (e.key === 'Enter' || e.key === ' ') {
58
+ e.preventDefault()
59
+ selectTab(tabs[idx].id)
60
+ }
61
+ }
62
+
63
+ async function navigateToManageAsset() {
64
+ manageAssetsModalOpen = false
65
+ const url = chooserClient.idToEditingUrl ? await chooserClient.idToEditingUrl(selectedAsset.id) : undefined
66
+ if (url) {
67
+ window.location.assign(url)
68
+ }
69
+ }
70
+ </script>
71
+
72
+ <ul bind:this={tabListEl} class="tabs" role="tablist">
73
+ {#each tabs as tab, idx}
74
+ <li
75
+ id="{tab.id}-tab"
76
+ class="tab {activeTab === tab.id ? 'active' : ''}"
77
+ role="tab"
78
+ aria-selected={activeTab === tab.id}
79
+ aria-controls={tab.id}
80
+ tabindex={focusedTabIdx === idx ? 0 : -1}
81
+ data-tab-asset
82
+ on:click={() => selectTab(tab.id)}
83
+ on:keydown={(e) => onTabKeydown(e, idx)}
84
+ on:focus={() => { focusedTabIdx = idx }}
85
+ >
86
+ <span>{tab.label}</span>
87
+ </li>
88
+ {/each}
89
+ </ul>
90
+
91
+ {#if activeTab === `${id}-alttext`}
92
+ <div id={`${id}-alttext`} class="tab-content" role="tabpanel" aria-labelledby="{id}-alttext-tab">
93
+ {#if altTextPath}<FieldTextArea required={altTextRequired} path={altTextPath} label="Alt. Text" helptext="Describes the asset for visually-impaired users and search engines."/>{/if}
94
+ </div>
95
+ {/if}
96
+ {#if activeTab === `${id}-details`}
97
+ <div id={`${id}-details`} class="tab-content" role="tabpanel" aria-labelledby="{id}-details-tab">
98
+ <div class="details">
99
+ <div class="path">
100
+ <span class="chooser-label">Assets Path:</span>
101
+ <span class="chooser-data">{selectedAsset.path}</span>
102
+ </div>
103
+ {#if selectedAsset.image}
104
+ <div>
105
+ <span class="chooser-label">Dimensions:</span>
106
+ <span class="chooser-data">{selectedAsset.image.width}x{selectedAsset.image.height}</span>
107
+ </div>
108
+ {/if}
109
+ <div>
110
+ <span class="chooser-label">File Type:</span>
111
+ <span class="chooser-data">{selectedAsset.mime}</span>
112
+ </div>
113
+ <div>
114
+ <span class="chooser-label">File Size:</span>
115
+ <span class="chooser-data">{bytesToHuman(selectedAsset.bytes)}</span>
116
+ </div>
117
+ {#if chooserClient.idToEditingUrl}
118
+ <Button on:click={() => { manageAssetsModalOpen = true }}>
119
+ <span class="manage-in-assets">
120
+ Manage in Assets
121
+ <Icon icon={outIcon} />
122
+ </span>
123
+ </Button>
124
+ {/if}
125
+ </div>
126
+ </div>
127
+ {/if}
128
+ {#if activeTab === `${id}-metadata`}
129
+ <div id={`${id}-metadata`} class="tab-content" role="tabpanel" aria-labelledby="{id}-metadata-tab">
130
+ <slot name="metadata" let:selectedAsset={selectedAsset} />
131
+ </div>
132
+ {/if}
133
+ {#if manageAssetsModalOpen}
134
+ <Dialog
135
+ title="Leave Dialog?"
136
+ size="small"
137
+ on:escape={() => { manageAssetsModalOpen = false }}
138
+ on:continue={navigateToManageAsset}
139
+ continueText="Manage Asset"
140
+ cancelText="Go Back"
141
+ icon={warningIcon}>
142
+ <p>Any changes made in the previous window may not be saved. Do you wish to continue?</p>
143
+ </Dialog>
144
+ {/if}
145
+
146
+ <style>
147
+ .tabs {
148
+ list-style: none;
149
+ display: flex;
150
+ gap: 10px;
151
+ margin: 0;
152
+ padding-inline: 0.375em;
153
+ background-color: var(--dg-dialog-header-bg, #ddd);
154
+ max-width: 100%;
155
+ overflow-x: auto;
156
+ scrollbar-width: none;
157
+ }
158
+ .tabs .tab {
159
+ background-color: #ebebeb;
160
+ color: #696969;
161
+ font-weight: 600;
162
+ padding: 0.5em 0.75em 0.125em;
163
+ border: 1px solid #ccced1;
164
+ border-top-left-radius: 4px;
165
+ border-top-right-radius: 4px;
166
+ }
167
+ .tabs .tab span{
168
+ white-space: nowrap;
169
+ }
170
+ .tabs .tab[aria-selected="true"] {
171
+ background-color: #f7f7f7;
172
+ color: #000;
173
+ border-bottom-color: #f7f7f7;
174
+ }
175
+ .tab-content {
176
+ padding: 1em 1em 1em 1.125em; /* left padding to align with tab text, which is 0.75em + 0.375em */
177
+ }
178
+ .tab-content .details {
179
+ display: flex;
180
+ flex-wrap: wrap;
181
+ column-gap: 1.5em;
182
+ row-gap: 0.5em;
183
+ }
184
+ .tab-content .details > div {
185
+ display: flex;
186
+ flex-direction: column;
187
+ }
188
+ .tab-content .path {
189
+ flex-basis: 100%;
190
+ }
191
+ .tab-content .chooser-label {
192
+ font-weight: 600;
193
+ margin-right: 0.5em;
194
+ }
195
+ .manage-in-assets {
196
+ display: flex;
197
+ align-items: center;
198
+ justify-content: center;
199
+ gap: 0.4em;
200
+ }
201
+ </style>
@@ -0,0 +1,23 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ import type { Asset } from './ChooserAPI';
3
+ declare const __propDef: {
4
+ props: {
5
+ id: string;
6
+ selectedAsset: Asset;
7
+ showMetadata?: boolean;
8
+ altTextRequired?: boolean;
9
+ altTextPath?: string | undefined;
10
+ };
11
+ events: {
12
+ [evt: string]: CustomEvent<any>;
13
+ };
14
+ slots: {
15
+ metadata: {};
16
+ };
17
+ };
18
+ export type AssetTabsProps = typeof __propDef.props;
19
+ export type AssetTabsEvents = typeof __propDef.events;
20
+ export type AssetTabsSlots = typeof __propDef.slots;
21
+ export default class AssetTabs extends SvelteComponentTyped<AssetTabsProps, AssetTabsEvents, AssetTabsSlots> {
22
+ }
23
+ export {};
@@ -23,6 +23,7 @@
23
23
  export let passthruFilters: F | undefined = undefined
24
24
  export let filter: undefined | ((item: AnyItem) => boolean | Promise<boolean>) = undefined
25
25
  export let store = new ChooserStore<F>(chooserClient)
26
+ export let showAltTextOption = false
26
27
  store.filter = filter
27
28
 
28
29
  setContext(CHOOSER_STORE_CONTEXT, store)
@@ -40,7 +41,7 @@
40
41
  $: store.setPreview($selected)
41
42
 
42
43
  function onChoose () {
43
- dispatch('change', $store.preview)
44
+ dispatch('change', { preview: $store.preview, copyAltText: altTextCheckbox?.checked ?? false } )
44
45
  }
45
46
 
46
47
  function onDeselect () {
@@ -85,6 +86,8 @@
85
86
  }
86
87
  }
87
88
  }
89
+
90
+ let altTextCheckbox: HTMLInputElement
88
91
  </script>
89
92
 
90
93
  <Dialog size="xl" ignoreTabs title={label} on:escape continueText="Choose" disabled={!$preview && required} cancelText="Cancel">
@@ -102,6 +105,14 @@
102
105
  {/if}
103
106
  </section>
104
107
  <ChooserPreview {thumbnailExpanded} {previewId} {store} on:thumbnailsizechange={() => { thumbnailExpanded = !thumbnailExpanded }}/>
108
+ {#if showAltTextOption && $preview && $preview.type === 'asset' && $preview.image}
109
+ <section class="alt-text-options">
110
+ <label>
111
+ <input bind:this={altTextCheckbox} type="checkbox" />
112
+ <span>Copy/paste alt. text (if available)</span>
113
+ </label>
114
+ </section>
115
+ {/if}
105
116
  </section>
106
117
  <svelte:fragment slot="buttons" let:describedby>
107
118
  {#if chooserClient.upload && $source?.type === 'asset'}
@@ -137,7 +148,7 @@
137
148
  position: relative;
138
149
  width: 75%;
139
150
  min-width: calc(100% - 21em);
140
- height: calc(100% - 4em);
151
+ height: calc(100% - 4.5em);
141
152
  background-color: white;
142
153
  overflow: auto;
143
154
  }
@@ -145,6 +156,10 @@
145
156
  position: relative;
146
157
  width: 100%;
147
158
  }
159
+ .alt-text-options {
160
+ width: 100%;
161
+ padding-block: 1em;
162
+ }
148
163
  :global(footer.actions .upload) {
149
164
  margin-right: auto;
150
165
  }
@@ -155,7 +170,10 @@
155
170
  .dialog-chooser-chooser {
156
171
  order: 3;
157
172
  width: 100%;
158
- height: 70%;
173
+ height: 50%;
174
+ }
175
+ .alt-text-options {
176
+ order: 4;
159
177
  }
160
178
  }
161
179
  </style>
@@ -15,6 +15,7 @@ declare class __sveltets_Render<F> {
15
15
  passthruFilters?: F | undefined;
16
16
  filter?: ((item: AnyItem) => boolean | Promise<boolean>) | undefined;
17
17
  store?: ChooserStore<F> | undefined;
18
+ showAltTextOption?: boolean;
18
19
  };
19
20
  events(): {
20
21
  escape: CustomEvent<any>;
@@ -40,6 +40,11 @@ export interface Client<F = any> {
40
40
  * may optionally return an array of accepted mime types for the folder
41
41
  */
42
42
  mayUpload?: (folder: Folder) => boolean | string[];
43
+ /**
44
+ * given an asset ID, return a URL that can be used to edit the asset in the main
45
+ * application. If undefined is returned, no "Edit" button will be shown
46
+ */
47
+ idToEditingUrl?: (id: string) => Promise<string | undefined>;
43
48
  }
44
49
  export interface Source {
45
50
  type: ChooserType;
@@ -108,6 +113,7 @@ export interface Asset extends Item {
108
113
  thumbnailUrl?: string;
109
114
  previewUrl?: string;
110
115
  tinyUrl?: string;
116
+ altText?: string;
111
117
  };
112
118
  }
113
119
  export {};
@@ -45,7 +45,7 @@
45
45
  .dialog-chooser-preview {
46
46
  width: 25%;
47
47
  max-width: 21em;
48
- height: calc(100% - 4em);
48
+ height: calc(100% - 4.5em);
49
49
  padding: 0 1em 1em 1em;
50
50
  overflow-y: auto;
51
51
  overflow-x: hidden;
@@ -25,6 +25,12 @@
25
25
  {/if}
26
26
  {#if item.type === 'asset'}
27
27
  {#if item.image}
28
+ {#if item.image.altText}
29
+ <div class="alt-text">
30
+ <dt>Alt. Text:</dt>
31
+ <dd>{item.image.altText}</dd>
32
+ </div>
33
+ {/if}
28
34
  <div>
29
35
  <dt>Dimensions:</dt>
30
36
  <dd>{item.image.width}x{item.image.height}</dd>
@@ -83,8 +89,8 @@
83
89
  .multiLine .top-row {
84
90
  width: 100%;
85
91
  }
86
- .top-row {
87
- max-width: calc(100% - 9em);
92
+ .alt-text {
93
+ width: 100%;
88
94
  }
89
95
  dl.asset .top-row {
90
96
  max-width: 30em;
@@ -32,7 +32,9 @@
32
32
  {/if}
33
33
  </div>
34
34
  {:else}
35
- <FileIcon mime={item.mime} width='5em' />
35
+ <div class="file-icon-container">
36
+ <FileIcon mime={item.mime} width='5em' />
37
+ </div>
36
38
  {/if}
37
39
  {:else if item.type === 'folder'}
38
40
  <Icon icon={folderOutline} width='5em' />
@@ -62,6 +64,15 @@
62
64
  .dialog-chooser-thumbnail > :global(*) {
63
65
  display: block;
64
66
  }
67
+ .dialog-chooser-thumbnail .file-icon-container {
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ }
72
+ .dialog-chooser-thumbnail .file-icon-container :global(svg) {
73
+ width: 100%;
74
+ height: 100%;
75
+ }
65
76
  .dialog-chooser-thumbnail button {
66
77
  position: absolute;
67
78
  left: 0;
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.3.4",
4
+ "version": "1.3.5",
5
5
  "scripts": {
6
6
  "prepublishOnly": "svelte-package",
7
7
  "dev": "vite dev --force",
@@ -32,7 +32,7 @@
32
32
  "txstate-utils": "^1.8.0"
33
33
  },
34
34
  "devDependencies": {
35
- "@sveltejs/adapter-auto": "^6.0.0",
35
+ "@sveltejs/adapter-auto": "^7.0.0",
36
36
  "@sveltejs/kit": "^2.0.1",
37
37
  "@sveltejs/package": "^2.0.1",
38
38
  "@sveltejs/vite-plugin-svelte": "^6.0.0",