@dosgato/dialog 1.3.8 → 1.4.0
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.
- package/dist/FieldChooserLink.svelte +7 -5
- package/dist/FieldChooserLink.svelte.d.ts +1 -0
- package/dist/chooser/AssetTabs.svelte +2 -1
- package/dist/chooser/AssetTabs.svelte.d.ts +1 -0
- package/dist/cropper/FieldCropper.svelte +26 -35
- package/dist/cropper/cropper.js +44 -1
- package/dist/iconpicker/FieldIconPicker.svelte +2 -2
- package/dist/iconpicker/iconpicker.js +417 -355
- package/dist/imageposition/FieldImagePosition.svelte +16 -25
- package/dist/tree/TreeCell.svelte +24 -6
- package/dist/tree/treestore.d.ts +7 -7
- package/package.json +1 -1
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
export let selectedAsset: AnyItem | RawURL | BrokenURL | undefined = undefined
|
|
32
32
|
export let altTextPath : string | undefined = undefined
|
|
33
33
|
export let altTextRequired: boolean = false
|
|
34
|
+
export let altTextHelp: string = 'Describes the asset for visually-impaired users and search engines.'
|
|
34
35
|
|
|
35
36
|
// TODO: add a mime type acceptance prop, maybe a regex or function, to prevent users from
|
|
36
37
|
// choosing unacceptable mime types
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
const formStore = getContext<FormStore>(FORM_CONTEXT)
|
|
39
40
|
const inheritedPath = getContext<string>(FORM_INHERITED_PATH)
|
|
40
41
|
const finalPath = [inheritedPath, path].filter(isNotBlank).join('.')
|
|
42
|
+
const finalAltTextPath = altTextPath ? [inheritedPath, altTextPath].filter(isNotBlank).join('.') : undefined
|
|
41
43
|
const value = formStore.getField<string>(finalPath)
|
|
42
44
|
const chooserClient = getContext<Client>(CHOOSER_API_CONTEXT)
|
|
43
45
|
const store = new ChooserStore(chooserClient)
|
|
@@ -57,12 +59,12 @@
|
|
|
57
59
|
return (e) => {
|
|
58
60
|
selectedAsset = e.detail.preview
|
|
59
61
|
if (
|
|
60
|
-
|
|
62
|
+
finalAltTextPath &&
|
|
61
63
|
e.detail.copyAltText &&
|
|
62
64
|
selectedAsset?.type === 'asset' &&
|
|
63
65
|
selectedAsset?.image?.altText
|
|
64
66
|
) {
|
|
65
|
-
formStore.setField(
|
|
67
|
+
formStore.setField(finalAltTextPath, selectedAsset.image.altText).catch(console.error)
|
|
66
68
|
}
|
|
67
69
|
setVal(selectedAsset?.id)
|
|
68
70
|
hide()
|
|
@@ -175,8 +177,8 @@
|
|
|
175
177
|
return (e) => {
|
|
176
178
|
selectedAsset = undefined
|
|
177
179
|
setVal(undefined)
|
|
178
|
-
if (
|
|
179
|
-
formStore.setField(
|
|
180
|
+
if (finalAltTextPath) {
|
|
181
|
+
formStore.setField(finalAltTextPath, undefined).catch(console.error)
|
|
180
182
|
}
|
|
181
183
|
}
|
|
182
184
|
}
|
|
@@ -205,7 +207,7 @@
|
|
|
205
207
|
<span class="chooser-data">{selectedAsset.name}</span>
|
|
206
208
|
</div>
|
|
207
209
|
<div class="tabs-container">
|
|
208
|
-
<AssetTabs id={assetTabsId} {selectedAsset} showMetadata={$$slots.metadata != null} {altTextPath} {altTextRequired}>
|
|
210
|
+
<AssetTabs id={assetTabsId} {selectedAsset} showMetadata={$$slots.metadata != null} {altTextPath} {altTextRequired} {altTextHelp} >
|
|
209
211
|
<slot name="metadata" slot="metadata" {selectedAsset} />
|
|
210
212
|
</AssetTabs>
|
|
211
213
|
</div>
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
export let altTextRequired: boolean = false
|
|
17
17
|
// if they say it's required but don't provide a path, default to 'altText'
|
|
18
18
|
export let altTextPath: string | undefined = altTextRequired ? 'altText' : undefined
|
|
19
|
+
export let altTextHelp: string | undefined
|
|
19
20
|
const chooserClient = getContext<Client>(CHOOSER_API_CONTEXT)
|
|
20
21
|
|
|
21
22
|
let tabListEl: HTMLUListElement
|
|
@@ -93,7 +94,7 @@
|
|
|
93
94
|
|
|
94
95
|
{#if activeTab === `${id}-alttext`}
|
|
95
96
|
<div id={`${id}-alttext`} class="tab-content" role="tabpanel" aria-labelledby="{id}-alttext-tab">
|
|
96
|
-
{#if altTextPath}<FieldTextArea required={altTextRequired} path={altTextPath} label="Alt. Text" helptext=
|
|
97
|
+
{#if altTextPath}<FieldTextArea required={altTextRequired} path={altTextPath} label="Alt. Text" helptext={altTextHelp} />{/if}
|
|
97
98
|
</div>
|
|
98
99
|
{/if}
|
|
99
100
|
{/if}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import arrowsCircleIcon from '@iconify-icons/ph/arrows-clockwise-fill'
|
|
3
|
+
import arrowsOutIcon from '@iconify-icons/ph/arrows-out-fill'
|
|
2
4
|
import { resize, ScreenReaderOnly } from '@txstate-mws/svelte-components'
|
|
3
|
-
import {
|
|
5
|
+
import { FORM_CONTEXT, FORM_INHERITED_PATH, type FormStore } from '@txstate-mws/svelte-forms'
|
|
6
|
+
import { getContext, onMount, tick } from 'svelte'
|
|
4
7
|
import { isNotBlank, randomid } from 'txstate-utils'
|
|
5
8
|
import FieldStandard from '../FieldStandard.svelte'
|
|
6
9
|
import { CropperStore, type CropOutput } from './cropper'
|
|
7
10
|
import { Button } from '..'
|
|
8
|
-
import arrowsOutIcon from '@iconify-icons/ph/arrows-out-fill'
|
|
9
|
-
import arrowsCircleIcon from '@iconify-icons/ph/arrows-clockwise-fill'
|
|
10
11
|
|
|
11
12
|
export let id: string | undefined = undefined
|
|
12
13
|
export let path: string
|
|
@@ -18,20 +19,17 @@
|
|
|
18
19
|
export let conditional: boolean | undefined = undefined
|
|
19
20
|
export let helptext: string | undefined = undefined
|
|
20
21
|
|
|
21
|
-
const
|
|
22
|
-
const
|
|
22
|
+
const inheritedPath = getContext<string>(FORM_INHERITED_PATH)
|
|
23
|
+
const finalPath = [inheritedPath, path].filter(isNotBlank).join('.')
|
|
24
|
+
const store = getContext<FormStore>(FORM_CONTEXT)
|
|
25
|
+
const val = store.getField<CropOutput>(finalPath)
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const initialAspectRatio: number = selectionAspectRatio
|
|
27
|
-
function init (spValue, spSetVal) {
|
|
28
|
-
setVal = spSetVal
|
|
29
|
-
value = spValue
|
|
30
|
-
}
|
|
27
|
+
const cropperStore = new CropperStore({ width: 0, height: 0, minSelection, targetAspect: selectionAspectRatio })
|
|
28
|
+
const { output, outputPct, selection } = cropperStore
|
|
31
29
|
|
|
32
|
-
$:
|
|
30
|
+
$: cropperStore.setOutput($val)
|
|
33
31
|
function reactToOutput (...args: any[]) {
|
|
34
|
-
if (mounted)
|
|
32
|
+
if (mounted) store.setField(finalPath, $output)
|
|
35
33
|
}
|
|
36
34
|
$: reactToOutput($output)
|
|
37
35
|
|
|
@@ -39,7 +37,7 @@
|
|
|
39
37
|
function updateRect (..._: any) {
|
|
40
38
|
if (!container) return false
|
|
41
39
|
rect = container.getBoundingClientRect()
|
|
42
|
-
|
|
40
|
+
cropperStore.updateDimensions(rect.width, rect.height)
|
|
43
41
|
return true
|
|
44
42
|
}
|
|
45
43
|
|
|
@@ -59,7 +57,7 @@
|
|
|
59
57
|
const clientY = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY
|
|
60
58
|
if (isInside(clientX, clientY)) {
|
|
61
59
|
e.preventDefault()
|
|
62
|
-
|
|
60
|
+
cropperStore.startDrag(clientX - rect.left, clientY - rect.top)
|
|
63
61
|
}
|
|
64
62
|
}
|
|
65
63
|
|
|
@@ -68,24 +66,24 @@
|
|
|
68
66
|
if (window.TouchEvent && e instanceof TouchEvent && e.touches.length > 1) return
|
|
69
67
|
const clientX = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX
|
|
70
68
|
const clientY = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY
|
|
71
|
-
if (e instanceof MouseEvent && !e.buttons && $
|
|
72
|
-
if (isInside(clientX, clientY) || $
|
|
69
|
+
if (e instanceof MouseEvent && !e.buttons && $cropperStore.drag) cropperStore.endDrag()
|
|
70
|
+
if (isInside(clientX, clientY) || $cropperStore.drag) cropperStore.mouseMove(...relativeToRect(clientX, clientY))
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
function onMouseUp (e: MouseEvent | TouchEvent) {
|
|
76
74
|
if (!updateRect()) return
|
|
77
|
-
|
|
75
|
+
cropperStore.endDrag()
|
|
78
76
|
const clientX = e instanceof MouseEvent ? e.clientX : e.changedTouches[0].clientX
|
|
79
77
|
const clientY = e instanceof MouseEvent ? e.clientY : e.changedTouches[0].clientY
|
|
80
78
|
if (isInside(clientX, clientY)) {
|
|
81
|
-
|
|
79
|
+
cropperStore.mouseMove(...relativeToRect(clientX, clientY))
|
|
82
80
|
container?.querySelector<HTMLDivElement>('.selectionHilite')?.focus()
|
|
83
81
|
}
|
|
84
82
|
}
|
|
85
83
|
|
|
86
84
|
function onMaximize () {
|
|
87
85
|
if (!updateRect()) return
|
|
88
|
-
|
|
86
|
+
cropperStore.maximize()
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
function onKeyDown (type: 'move' | 'tl' | 'tr' | 'bl' | 'br') {
|
|
@@ -104,9 +102,9 @@
|
|
|
104
102
|
} else return
|
|
105
103
|
const step = e.shiftKey ? (e.altKey || e.metaKey ? 80 : 20) : (e.altKey || e.metaKey ? 40 : 1)
|
|
106
104
|
if (type === 'move') {
|
|
107
|
-
|
|
105
|
+
cropperStore.move(left ? -1 * step : (right ? step : 0), up ? -1 * step : (down ? step : 0))
|
|
108
106
|
} else {
|
|
109
|
-
|
|
107
|
+
cropperStore.expand(type, ((tl || bl) && right) || ((tl || tr) && down) || ((tr || br) && left) || ((bl || br) && up) ? -1 * step : step)
|
|
110
108
|
}
|
|
111
109
|
}
|
|
112
110
|
}
|
|
@@ -121,15 +119,9 @@
|
|
|
121
119
|
const movedescid = randomid()
|
|
122
120
|
let focusWithin = false
|
|
123
121
|
|
|
124
|
-
let arChanged = false
|
|
125
122
|
async function reactToAspectRatio (ar) {
|
|
126
123
|
if (!ar) return
|
|
127
|
-
|
|
128
|
-
await tick()
|
|
129
|
-
if (ar !== initialAspectRatio || arChanged) {
|
|
130
|
-
store.maximize()
|
|
131
|
-
arChanged = true
|
|
132
|
-
}
|
|
124
|
+
cropperStore.updateTargetAspect(ar)
|
|
133
125
|
}
|
|
134
126
|
$: void reactToAspectRatio(selectionAspectRatio)
|
|
135
127
|
|
|
@@ -143,7 +135,7 @@
|
|
|
143
135
|
} else {
|
|
144
136
|
if (e.target.src !== initialVal || srcChanged) {
|
|
145
137
|
await tick()
|
|
146
|
-
|
|
138
|
+
cropperStore.maximize()
|
|
147
139
|
srcChanged = true
|
|
148
140
|
}
|
|
149
141
|
}
|
|
@@ -152,18 +144,17 @@
|
|
|
152
144
|
|
|
153
145
|
<svelte:window on:mousemove={onMouseMove} on:mouseup={onMouseUp} on:touchend={onMouseUp} on:touchcancel={onMouseUp} />
|
|
154
146
|
<FieldStandard bind:id {label} {path} {required} conditional={conditional && isNotBlank(imageSrc)} {helptext} {descid} let:value let:setVal let:helptextid>
|
|
155
|
-
{@const _ = init(value, setVal)}
|
|
156
147
|
{#if isNotBlank(imageSrc)}
|
|
157
148
|
<div on:focusin={() => { focusWithin = true }} on:focusout={() => { focusWithin = false }}>
|
|
158
149
|
<div class="action-buttons">
|
|
159
150
|
<Button type="button" on:click={onMaximize} icon={arrowsOutIcon}>Center and Maximize</Button>
|
|
160
|
-
<Button type="button" on:click={() =>
|
|
151
|
+
<Button type="button" on:click={() => cropperStore.reset()} icon={arrowsCircleIcon} class="btn-clear">Clear</Button>
|
|
161
152
|
</div>
|
|
162
153
|
<div class="cropper-instructions">
|
|
163
154
|
Click and drag to select a section of your image to use.
|
|
164
155
|
</div>
|
|
165
156
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
166
|
-
<div bind:this={container} use:resize on:resize={() => updateRect()} class="crop-image-container" on:mousedown={onMouseDown} on:touchstart={onMouseDown} on:touchmove={onMouseMove} style:cursor={$
|
|
157
|
+
<div bind:this={container} use:resize on:resize={() => updateRect()} class="crop-image-container" on:mousedown={onMouseDown} on:touchstart={onMouseDown} on:touchmove={onMouseMove} style:cursor={$cropperStore.cursor}>
|
|
167
158
|
<img class="crop-image" src={imageSrc} alt="" on:load={onimageload}/>
|
|
168
159
|
{#if $selection && $outputPct}
|
|
169
160
|
<div class='crop-bg'>
|
|
@@ -182,7 +173,7 @@
|
|
|
182
173
|
<ScreenReaderOnly id={movedescid}>arrows move crop selection, hold shift and/or cmd/alt for bigger steps</ScreenReaderOnly>
|
|
183
174
|
{#if focusWithin}
|
|
184
175
|
<ScreenReaderOnly arialive="polite">top left x y coordinate is ({Math.round($selection.left)}, {Math.round($selection.top)}) bottom right x y coordinate is ({Math.round($selection.right)}, {Math.round($selection.bottom)})</ScreenReaderOnly>
|
|
185
|
-
<ScreenReaderOnly arialive="polite">crop area is {Math.round($
|
|
176
|
+
<ScreenReaderOnly arialive="polite">crop area is {Math.round($cropperStore.width)} pixels wide by {Math.round($cropperStore.height)} pixels tall</ScreenReaderOnly>
|
|
186
177
|
{/if}
|
|
187
178
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
188
179
|
<div class='selectionCorner tl'
|
package/dist/cropper/cropper.js
CHANGED
|
@@ -19,7 +19,50 @@ export class CropperStore extends Store {
|
|
|
19
19
|
this.update(v => ({ ...v, width, height }));
|
|
20
20
|
}
|
|
21
21
|
updateTargetAspect(ar) {
|
|
22
|
-
this.update(v =>
|
|
22
|
+
this.update(v => {
|
|
23
|
+
if (v.selection) {
|
|
24
|
+
const selection = this.convertToPx(v.selection, v.width, v.height);
|
|
25
|
+
const selar = (selection.right - selection.left) / (selection.bottom - selection.top);
|
|
26
|
+
// adjust the aspect ratio to match the new aspect ratio, keep the center the same
|
|
27
|
+
// and preserve the pixel area of the selection. Compute new width and height
|
|
28
|
+
// from the original area and the new aspect ratio, then recenter the box.
|
|
29
|
+
const currWidth = selection.right - selection.left;
|
|
30
|
+
const currHeight = selection.bottom - selection.top;
|
|
31
|
+
const area = currWidth * currHeight;
|
|
32
|
+
const newWidth = Math.sqrt(area * ar);
|
|
33
|
+
const newHeight = Math.sqrt(area / ar);
|
|
34
|
+
const centerX = (selection.left + selection.right) / 2;
|
|
35
|
+
const centerY = (selection.top + selection.bottom) / 2;
|
|
36
|
+
selection.left = centerX - newWidth / 2;
|
|
37
|
+
selection.right = centerX + newWidth / 2;
|
|
38
|
+
selection.top = centerY - newHeight / 2;
|
|
39
|
+
selection.bottom = centerY + newHeight / 2;
|
|
40
|
+
// shrink the box until its width and height are less than the width and height of the drawing area
|
|
41
|
+
// maintaining the center
|
|
42
|
+
let selWidth = selection.right - selection.left;
|
|
43
|
+
let selHeight = selection.bottom - selection.top;
|
|
44
|
+
if (selWidth > v.width || selHeight > v.height) {
|
|
45
|
+
const scale = Math.min(v.width / selWidth, v.height / selHeight);
|
|
46
|
+
const cX = (selection.left + selection.right) / 2;
|
|
47
|
+
const cY = (selection.top + selection.bottom) / 2;
|
|
48
|
+
const halfW = (selWidth * scale) / 2;
|
|
49
|
+
const halfH = (selHeight * scale) / 2;
|
|
50
|
+
selection.left = cX - halfW;
|
|
51
|
+
selection.right = cX + halfW;
|
|
52
|
+
selection.top = cY - halfH;
|
|
53
|
+
selection.bottom = cY + halfH;
|
|
54
|
+
}
|
|
55
|
+
// translate selection until all edges are within the drawing area
|
|
56
|
+
const dx = Math.max(0 - selection.left, Math.min(v.width - selection.right, 0));
|
|
57
|
+
const dy = Math.max(0 - selection.top, Math.min(v.height - selection.bottom, 0));
|
|
58
|
+
selection.left += dx;
|
|
59
|
+
selection.right += dx;
|
|
60
|
+
selection.top += dy;
|
|
61
|
+
selection.bottom += dy;
|
|
62
|
+
return { ...v, selection: this.convertToPct(selection, v.width, v.height), targetAspect: ar };
|
|
63
|
+
}
|
|
64
|
+
return { ...v, targetAspect: ar };
|
|
65
|
+
});
|
|
23
66
|
}
|
|
24
67
|
/**
|
|
25
68
|
* The svelte component is responsible for making sure x and y are relative to the drawing area,
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
</script>
|
|
115
115
|
|
|
116
116
|
<FieldStandard bind:id {path} {descid} {label} {required} {defaultValue} {conditional} {helptext} let:value let:valid let:invalid let:id let:onBlur let:setVal let:messagesid let:helptextid>
|
|
117
|
-
<Icon icon={`${value.prefix === 'fab' ? '
|
|
117
|
+
<Icon icon={`${value.prefix === 'fab' ? 'fa7-brands' : 'fa7-solid'}:${value.icon?.slice(3) ?? 'graduation-cap'}`}/>
|
|
118
118
|
<button type="button" {id} class="select-icon" on:click={() => { modalOpen = true }} aria-describedby={getDescribedBy([descid, messagesid, helptextid])} on:blur={onBlur}>Select New Icon</button>
|
|
119
119
|
{#if modalOpen}
|
|
120
120
|
<Modal>
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
{#each visibleIcons as icon, idx (icon.class)}
|
|
143
143
|
|
|
144
144
|
<div bind:this={iconElements[idx]} id={icon.class} class="icon-picker-item" role="radio" aria-checked={icon.class === selected.icon} tabindex={icon.class === selected.icon ? 0 : -1} data-index={idx} on:click={() => onSelectIcon(icon.class)} on:keydown={onKeyDown}>
|
|
145
|
-
<Icon icon={`${iconToPrefix[icon.class] === 'fab' ? '
|
|
145
|
+
<Icon icon={`${iconToPrefix[icon.class] === 'fab' ? 'fa7-brands' : 'fa7-solid'}:${icon.class.slice(3)}`}/>
|
|
146
146
|
<ScreenReaderOnly>{icon.label}</ScreenReaderOnly>
|
|
147
147
|
</div>
|
|
148
148
|
{:else}
|