@taprootio/espalier 6.7.0 → 6.9.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/CHANGELOG.md +18 -0
- package/custom-elements.json +6622 -6322
- package/dist/button/esp-button.d.ts +6 -0
- package/dist/button/esp-button.js +11 -10
- package/dist/image-upload/esp-image-preview.js +67 -25
- package/dist/image-upload/esp-image-upload.d.ts +78 -1
- package/dist/image-upload/esp-image-upload.js +23 -20
- package/dist/image-upload/image-helpers.d.ts +10 -0
- package/dist/index.d.ts +1 -1
- package/dist/menu/esp-menu-group.d.ts +10 -0
- package/dist/menu/esp-menu-group.js +20 -25
- package/dist/menu/esp-menu-item.d.ts +10 -0
- package/dist/menu/esp-menu-item.js +8 -6
- package/dist/menu/esp-menu.js +19 -12
- package/dist/shared/events.d.ts +17 -0
- package/dist/shared/events.js +1 -1
- package/dist/shared/new-tab-cue.js +1 -0
- package/package.json +1 -1
|
@@ -268,6 +268,12 @@ export declare class EspalierButton extends EspalierElementBase {
|
|
|
268
268
|
* @type {string}
|
|
269
269
|
*/
|
|
270
270
|
rel: string;
|
|
271
|
+
/**
|
|
272
|
+
* Hidden phrase appended to the accessible name of a `target="_blank"` link.
|
|
273
|
+
* Replace for non-English sites. Empty or whitespace text falls back to English.
|
|
274
|
+
* @default "opens in a new tab"
|
|
275
|
+
*/
|
|
276
|
+
newTabLabel: string;
|
|
271
277
|
/**
|
|
272
278
|
* When true and `button-type` is `"submit"`, the enclosing
|
|
273
279
|
* form is submitted without running constraint validation.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var o=function(h,t,s,i){var a=arguments.length,n=a<3?t:i===null?i=Object.getOwnPropertyDescriptor(t,s):i,d;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")n=Reflect.decorate(h,t,s,i);else for(var c=h.length-1;c>=0;c--)(d=h[c])&&(n=(a<3?d(n):a>3?d(t,s,n):d(t,s))||n);return a>3&&n&&Object.defineProperty(t,s,n),n};import{css as g,html as p,nothing as l}from"lit";import{customElement as f,eventOptions as m,property as r,state as v}from"lit/decorators.js";import{classMap as u}from"lit/directives/class-map.js";import{EspalierElementBase as b}from"../shared/esp-element-base.js";import{ESP_EVENTS as y}from"../shared/events.js";import{ref as w}from"lit/directives/ref.js";import{getIconHrefForHost as T}from"../shared/intent-values.js";import{SlottedIconController as k}from"../shared/slotted-icon-controller.js";import{renderSpriteIcon as x}from"../shared/svgs/render-sprite-icon.js";import{DEFAULT_NEW_TAB_LABEL as S,newTabSuffix as $,renderNewTabCue as E}from"../shared/new-tab-cue.js";import{srOnly as C}from"../shared/style-fragments.js";import{BUTTON_INTERACTION_LIGHTNESS_FACTOR as N}from"./action-state-lightness.js";const z=p`<svg
|
|
2
2
|
xmlns="http://www.w3.org/2000/svg"
|
|
3
3
|
viewBox="0 0 24 24"
|
|
4
4
|
fill="none"
|
|
@@ -10,23 +10,24 @@ var n=function(h,t,s,i){var a=arguments.length,o=a<3?t:i===null?i=Object.getOwnP
|
|
|
10
10
|
>
|
|
11
11
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
12
12
|
<path d="M4.05 11a8 8 0 1 1 .5 4m-.5 5v-5h5" />
|
|
13
|
-
</svg>`;let e=class extends b{constructor(){super(...arguments),this.iconSlot=new
|
|
13
|
+
</svg>`;let e=class extends b{constructor(){super(...arguments),this.iconSlot=new k(this),this.innerTabindex=null,this.neutralizingTabindex=!1,this.buttonType="button",this.collapsed=!1,this.disabled=!1,this.href="",this.iconOnly=!1,this.incognito=!1,this.icon="",this.iconPosition="right",this.label="",this.loading=!1,this.target="",this.rel="",this.newTabLabel=S,this.formNoValidate=!1,this.buildIcon=t=>this.loading?p` <span>${z}</span> `:p`
|
|
14
14
|
<span>
|
|
15
15
|
<slot ${w(this.iconSlot.slotRef)} @slotchange=${this.iconSlot.handleSlotChange}></slot>
|
|
16
|
-
${!this.iconSlot.hasSlottedIcon(":scope > *")&&t?
|
|
16
|
+
${!this.iconSlot.hasSlottedIcon(":scope > *")&&t?x(t):l}
|
|
17
17
|
</span>
|
|
18
|
-
`}static get observedAttributes(){return[...super.observedAttributes,"tabindex","aria-pressed"]}attributeChangedCallback(t,s,i){if(super.attributeChangedCallback(t,s,i),t==="aria-pressed"){this.requestUpdate();return}if(t!=="tabindex"||this.neutralizingTabindex)return;const a=i===null?null:Number(i),
|
|
18
|
+
`}static get observedAttributes(){return[...super.observedAttributes,"tabindex","aria-pressed"]}attributeChangedCallback(t,s,i){if(super.attributeChangedCallback(t,s,i),t==="aria-pressed"){this.requestUpdate();return}if(t!=="tabindex"||this.neutralizingTabindex)return;const a=i===null?null:Number(i),n=a===null||Number.isNaN(a)?null:a;n!==null&&n>=0?(this.innerTabindex=n,this.writeHostTabindex(null)):n===null&&i!==null?(this.innerTabindex=null,this.writeHostTabindex(null)):this.innerTabindex=n}writeHostTabindex(t){this.getAttribute("tabindex")!==t&&(this.neutralizingTabindex=!0,t===null?this.removeAttribute("tabindex"):this.setAttribute("tabindex",t),this.neutralizingTabindex=!1)}removeAttribute(t){super.removeAttribute(t),t==="tabindex"&&!this.neutralizingTabindex&&(this.innerTabindex=null)}focus(t){this.focusShadowElementAfterUpdate("button, a",t)}handleClick(t){if(this.dispatchEvent(new CustomEvent(y.CLICKED,{detail:{},bubbles:!0,composed:!0})),this.buttonType==="submit"){const s=this.closest("form");if(s){const i=document.createElement("button");i.type="submit",i.hidden=!0,this.formNoValidate&&(i.formNoValidate=!0),s.appendChild(i),s.requestSubmit(i),i.remove()}}else this.buttonType==="reset"&&this.closest("form")?.reset()}render(){const t=T(this.icon,this),s=this.loading||this.iconSlot.hasSlottedIcon(":scope > *")||t.length>0,{collapsed:i,iconOnly:a}=this,n=a?this.getAttribute("aria-label")||this.label||l:l,d=this.getAttribute("aria-pressed")||l,c={"esp-field":!0,"icon-only":a,incognito:this.incognito,"is-collapsed":i,"has-icon":s,"icon-left":s&&!a&&this.iconPosition==="left"};return this.href.length?p`
|
|
19
19
|
<div part="wrapper" class=${u(c)}>
|
|
20
20
|
<a
|
|
21
21
|
?disabled=${this.disabled}
|
|
22
22
|
href=${this.href}
|
|
23
|
-
target=${this.target}
|
|
23
|
+
target=${this.target||l}
|
|
24
24
|
rel=${this.rel||l}
|
|
25
25
|
tabindex=${this.innerTabindex??l}
|
|
26
|
-
aria-label=${
|
|
26
|
+
aria-label=${n===l?l:`${n}${$(this.target,this.newTabLabel)}`}
|
|
27
27
|
>
|
|
28
28
|
<span>${this.label??""}</span>
|
|
29
29
|
${this.buildIcon(t)}
|
|
30
|
+
${a?l:E(this.target,this.newTabLabel)}
|
|
30
31
|
</a>
|
|
31
32
|
</div>
|
|
32
33
|
`:p`
|
|
@@ -36,14 +37,14 @@ var n=function(h,t,s,i){var a=arguments.length,o=a<3?t:i===null?i=Object.getOwnP
|
|
|
36
37
|
@click=${this.handleClick}
|
|
37
38
|
type=${this.buttonType}
|
|
38
39
|
tabindex=${this.innerTabindex??l}
|
|
39
|
-
aria-label=${
|
|
40
|
+
aria-label=${n}
|
|
40
41
|
aria-pressed=${d}
|
|
41
42
|
>
|
|
42
43
|
<span>${this.label??""}</span>
|
|
43
44
|
${this.buildIcon(t)}
|
|
44
45
|
</button>
|
|
45
46
|
</div>
|
|
46
|
-
`}};e.styles=[...b.styles,g`
|
|
47
|
+
`}};e.styles=[...b.styles,C,g`
|
|
47
48
|
:host {
|
|
48
49
|
--_esp-button-radius: var(--esp-size-border-radius);
|
|
49
50
|
}
|
|
@@ -158,7 +159,7 @@ var n=function(h,t,s,i){var a=arguments.length,o=a<3?t:i===null?i=Object.getOwnP
|
|
|
158
159
|
&:hover,
|
|
159
160
|
&:focus-within {
|
|
160
161
|
background-color: oklch(
|
|
161
|
-
from var(--esp-color-action-background) calc(l * ${
|
|
162
|
+
from var(--esp-color-action-background) calc(l * ${N})
|
|
162
163
|
c h
|
|
163
164
|
);
|
|
164
165
|
}
|
|
@@ -232,4 +233,4 @@ var n=function(h,t,s,i){var a=arguments.length,o=a<3?t:i===null?i=Object.getOwnP
|
|
|
232
233
|
transform: rotate(360deg);
|
|
233
234
|
}
|
|
234
235
|
}
|
|
235
|
-
`],
|
|
236
|
+
`],o([v()],e.prototype,"innerTabindex",void 0),o([r({attribute:"button-type",type:String})],e.prototype,"buttonType",void 0),o([r({type:Boolean})],e.prototype,"collapsed",void 0),o([r({type:Boolean,reflect:!0})],e.prototype,"disabled",void 0),o([r({type:String})],e.prototype,"href",void 0),o([r({attribute:"icon-only",type:Boolean})],e.prototype,"iconOnly",void 0),o([r({type:Boolean,reflect:!0})],e.prototype,"incognito",void 0),o([r({type:String})],e.prototype,"icon",void 0),o([r({attribute:"icon-position",type:String})],e.prototype,"iconPosition",void 0),o([r({type:String})],e.prototype,"label",void 0),o([r({type:Boolean,reflect:!0})],e.prototype,"loading",void 0),o([r({type:String})],e.prototype,"target",void 0),o([r({type:String})],e.prototype,"rel",void 0),o([r({type:String,attribute:"new-tab-label"})],e.prototype,"newTabLabel",void 0),o([r({attribute:"formnovalidate",type:Boolean})],e.prototype,"formNoValidate",void 0),o([m({capture:!1,passive:!0})],e.prototype,"handleClick",null),e=o([f("esp-button")],e);export{e as EspalierButton};
|
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
var
|
|
2
|
-
${
|
|
3
|
-
src=${t||
|
|
1
|
+
var l=function(n,e,t,o){var i=arguments.length,r=i<3?e:o===null?o=Object.getOwnPropertyDescriptor(e,t):o,a;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")r=Reflect.decorate(n,e,t,o);else for(var u=n.length-1;u>=0;u--)(a=n[u])&&(r=(i<3?a(r):i>3?a(e,t,r):a(e,t))||r);return i>3&&r&&Object.defineProperty(e,t,r),r};import{css as v,html as d,nothing as h}from"lit";import{customElement as b,property as c}from"lit/decorators.js";import{classMap as g}from"lit/directives/class-map.js";import{keyed as f}from"lit/directives/keyed.js";import{createRef as w,ref as y}from"lit/directives/ref.js";import"../button/esp-button.js";import"../progress/esp-progress.js";import"../tooltip/esp-tooltip.js";import{EspalierElementBase as m}from"../shared/esp-element-base.js";import{getIconHrefForHost as k}from"../shared/intent-values.js";import{previewLoadQueue as x}from"./preview-load-queue.js";const T="esp-internal-image-preview-remove",E="esp-internal-image-preview-retry",L="esp-internal-image-preview-action",R=2,I=250,C=6e4;function p(n){const e=n.trim().toLowerCase();return e.length>0&&!e.startsWith("blob:")&&!e.startsWith("data:")}let s=class extends m{constructor(){super(...arguments),this.image=w(),this.url="",this.alt="",this.failed=!1,this.positionLabel="",this.sourceToken=0,this.admittedUrl="",this.remoteImageLoaded=!1,this.imageLoadFailed=!1,this.automaticRetryCount=0,this.remotePreviewVisible=!1,this.visibilityObserver=null,this.queuedLoadCancel=null,this.loadSlotRelease=null,this.retryTimer=null,this.loadTimeoutTimer=null,this.activeSourceToken=null}connectedCallback(){super.connectedCallback(),this.startCurrentPreview()}disconnectedCallback(){this.stopVisibilityObserver(),this.cancelRemoteWork(),this.remoteImageLoaded=!1,this.admittedUrl="",this.sourceToken+=1,this.remotePreviewVisible=!1,super.disconnectedCallback()}willUpdate(e){super.willUpdate(e),e.has("url")&&(this.stopVisibilityObserver(),this.cancelRemoteWork(),this.sourceToken+=1,this.admittedUrl="",this.remoteImageLoaded=!1,this.imageLoadFailed=!1,this.automaticRetryCount=0,this.remotePreviewVisible=!1)}updated(e){super.updated(e),e.has("url")&&this.startCurrentPreview()}startCurrentPreview(){if(!(!this.isConnected||!p(this.url))){if(typeof IntersectionObserver>"u"){this.remotePreviewVisible=!0,this.requestRemoteLoad();return}this.visibilityObserver??=new IntersectionObserver((e,t)=>{if(t!==this.visibilityObserver)return;const o=e.find(i=>i.target===this);!o||!this.isConnected||!p(this.url)||(this.remotePreviewVisible=o.isIntersecting,o.isIntersecting?this.requestRemoteLoad():!this.loadSlotRelease&&!this.remoteImageLoaded&&this.cancelQueuedLoad())},{threshold:0}),this.visibilityObserver.observe(this)}}stopVisibilityObserver(){this.visibilityObserver?.disconnect(),this.visibilityObserver=null}requestRemoteLoad(){if(!this.isConnected||!p(this.url)||!this.remotePreviewVisible||this.imageLoadFailed||this.remoteImageLoaded||this.loadSlotRelease||this.queuedLoadCancel||this.retryTimer)return;let e=!1;const t=x.request(o=>{if(e=!0,this.queuedLoadCancel=null,!this.isConnected||!p(this.url)||!this.remotePreviewVisible||this.imageLoadFailed||this.remoteImageLoaded){o();return}this.loadSlotRelease=o;const i=++this.sourceToken;this.activeSourceToken=i,this.admittedUrl=this.url,this.loadTimeoutTimer=setTimeout(()=>this.handleImageFailure(i),C),this.requestUpdate()});e||(this.queuedLoadCancel=t)}handleImageLoad(e,t){this.isActiveImageEvent(e,t)&&(this.completeActiveLoad(),this.remoteImageLoaded=!0,this.stopVisibilityObserver())}handleImageError(e,t){this.isActiveImageEvent(e,t)&&this.handleImageFailure(t)}isActiveImageEvent(e,t){return e.currentTarget instanceof HTMLImageElement&&e.currentTarget.getAttribute("src")===this.url&&t===this.activeSourceToken&&t===this.sourceToken&&this.loadSlotRelease!==null}handleImageFailure(e){if(e!==this.activeSourceToken||e!==this.sourceToken||!this.loadSlotRelease)return;if(this.abandonActiveLoad(),this.automaticRetryCount>=R){this.imageLoadFailed=!0,this.requestUpdate();return}this.automaticRetryCount+=1;const t=.75+Math.random()*.5,o=Math.round(I*2**(this.automaticRetryCount-1)*t),i=this.sourceToken;this.retryTimer=setTimeout(()=>{this.retryTimer=null,i===this.sourceToken&&this.requestRemoteLoad()},o)}completeActiveLoad(){this.releaseActiveLoad()}abandonActiveLoad(){if(!this.loadSlotRelease)return;const e=this.shadowRoot?.querySelector("img");e?.getAttribute("src")===this.admittedUrl&&e.removeAttribute("src"),this.admittedUrl="",this.sourceToken+=1,this.releaseActiveLoad(),this.isConnected&&this.requestUpdate()}releaseActiveLoad(){this.loadTimeoutTimer!==null&&(clearTimeout(this.loadTimeoutTimer),this.loadTimeoutTimer=null),this.activeSourceToken=null;const e=this.loadSlotRelease;this.loadSlotRelease=null,e?.()}cancelQueuedLoad(){const e=this.queuedLoadCancel;this.queuedLoadCancel=null,e?.()}cancelRemoteWork(){this.retryTimer!==null&&(clearTimeout(this.retryTimer),this.retryTimer=null),this.cancelQueuedLoad(),this.abandonActiveLoad()}retryImage(){p(this.url)&&(this.cancelRemoteWork(),this.sourceToken+=1,this.admittedUrl="",this.remoteImageLoaded=!1,this.imageLoadFailed=!1,this.automaticRetryCount=0,this.remotePreviewVisible=!0,this.requestRemoteLoad())}renderImage(){const t=p(this.url)?this.admittedUrl:this.url,o=this.sourceToken;return f(`${t}:${o}`,d`<img
|
|
2
|
+
${y(this.image)}
|
|
3
|
+
src=${t||h}
|
|
4
4
|
alt=${this.alt}
|
|
5
5
|
draggable="false"
|
|
6
6
|
decoding="async"
|
|
7
|
-
@load=${i=>this.handleImageLoad(i,
|
|
8
|
-
@error=${i=>this.handleImageError(i,
|
|
9
|
-
/>`)}
|
|
7
|
+
@load=${i=>this.handleImageLoad(i,o)}
|
|
8
|
+
@error=${i=>this.handleImageError(i,o)}
|
|
9
|
+
/>`)}renderAction(){const{action:e}=this,t=this.alt||this.positionLabel,o=e?.label?.trim();return!e||!o||!k(e.icon,this)?h:d`<esp-tooltip class="preview-action" text=${o}>
|
|
10
|
+
<esp-button
|
|
11
|
+
data-preview-action
|
|
12
|
+
icon-only
|
|
13
|
+
collapsed
|
|
14
|
+
icon=${e.icon}
|
|
15
|
+
label=${t?`${o}: ${t}`:o}
|
|
16
|
+
@esp-clicked=${i=>{i.stopPropagation(),this.dispatchEvent(new CustomEvent(L))}}
|
|
17
|
+
></esp-button>
|
|
18
|
+
</esp-tooltip>`}render(){const e=this.progress!==void 0,t=this.failed&&!e,o=this.imageLoadFailed&&!e&&!t,i=this.alt||this.positionLabel,r=e?i?`Cancel upload and remove ${i}`:"Cancel upload and remove image":i?`Remove ${i}`:"Remove image";return d`<div>
|
|
10
19
|
${this.renderImage()}
|
|
11
20
|
<button
|
|
12
21
|
type="button"
|
|
13
|
-
aria-label=${
|
|
14
|
-
class=${
|
|
22
|
+
aria-label=${r}
|
|
23
|
+
class=${g({uploading:e})}
|
|
15
24
|
@click=${()=>{this.dispatchEvent(new CustomEvent(T,{detail:this,bubbles:!0,composed:!0}))}}
|
|
16
25
|
>
|
|
17
26
|
<svg
|
|
@@ -38,7 +47,7 @@ var n=function(a,e,t,r){var i=arguments.length,o=i<3?e:r===null?r=Object.getOwnP
|
|
|
38
47
|
label="Upload progress"
|
|
39
48
|
></esp-progress>
|
|
40
49
|
</div>
|
|
41
|
-
`:
|
|
50
|
+
`:h}
|
|
42
51
|
${t?d`
|
|
43
52
|
<div class="upload-overlay failed">
|
|
44
53
|
<svg
|
|
@@ -63,13 +72,13 @@ var n=function(a,e,t,r){var i=arguments.length,o=i<3?e:r===null?r=Object.getOwnP
|
|
|
63
72
|
class="retry-btn"
|
|
64
73
|
collapsed
|
|
65
74
|
label="Retry upload"
|
|
66
|
-
@esp-clicked=${
|
|
75
|
+
@esp-clicked=${a=>{a.stopPropagation(),this.dispatchEvent(new CustomEvent(E,{detail:this,bubbles:!0,composed:!0}))}}
|
|
67
76
|
>
|
|
68
77
|
Retry
|
|
69
78
|
</esp-button>
|
|
70
79
|
</div>
|
|
71
|
-
`:
|
|
72
|
-
${
|
|
80
|
+
`:h}
|
|
81
|
+
${o?d`
|
|
73
82
|
<div class="upload-overlay failed image-load-failed">
|
|
74
83
|
<svg
|
|
75
84
|
class="alert-icon"
|
|
@@ -93,17 +102,48 @@ var n=function(a,e,t,r){var i=arguments.length,o=i<3?e:r===null?r=Object.getOwnP
|
|
|
93
102
|
class="image-retry-btn"
|
|
94
103
|
collapsed
|
|
95
104
|
label="Retry image"
|
|
96
|
-
@esp-clicked=${
|
|
105
|
+
@esp-clicked=${a=>{a.stopPropagation(),this.retryImage()}}
|
|
97
106
|
>
|
|
98
107
|
Retry image
|
|
99
108
|
</esp-button>
|
|
100
109
|
</div>
|
|
101
|
-
`:
|
|
110
|
+
`:h}
|
|
111
|
+
${e||t||o?h:this.renderAction()}
|
|
102
112
|
</div>`}};s.styles=[...m.styles,v`
|
|
103
113
|
:host {
|
|
104
114
|
display: block;
|
|
105
115
|
box-sizing: border-box;
|
|
106
116
|
position: relative;
|
|
117
|
+
|
|
118
|
+
container-type: size;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
.preview-action {
|
|
123
|
+
position: absolute;
|
|
124
|
+
right: var(--esp-size-padding);
|
|
125
|
+
bottom: var(--esp-size-padding);
|
|
126
|
+
z-index: 3;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.preview-action esp-button::part(wrapper) {
|
|
130
|
+
box-shadow: 1px 1px 2px var(--esp-color-shadow);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
.preview-action esp-button:focus-within::part(wrapper) {
|
|
135
|
+
box-shadow: 0 0 0 2px var(--esp-color-background);
|
|
136
|
+
outline: 2px solid var(--esp-color-text);
|
|
137
|
+
outline-offset: 2px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@container (max-width: 96px) or (max-height: 150px) {
|
|
141
|
+
.preview-action {
|
|
142
|
+
|
|
143
|
+
right: 4px;
|
|
144
|
+
bottom: 4px;
|
|
145
|
+
--esp-button-padding: calc(var(--esp-size-tiny) / 2);
|
|
146
|
+
}
|
|
107
147
|
}
|
|
108
148
|
|
|
109
149
|
div {
|
|
@@ -122,7 +162,6 @@ var n=function(a,e,t,r){var i=arguments.length,o=i<3?e:r===null?r=Object.getOwnP
|
|
|
122
162
|
}
|
|
123
163
|
|
|
124
164
|
button:not(.retry-btn) {
|
|
125
|
-
visibility: hidden;
|
|
126
165
|
display: grid;
|
|
127
166
|
place-content: center;
|
|
128
167
|
cursor: pointer;
|
|
@@ -148,20 +187,23 @@ var n=function(a,e,t,r){var i=arguments.length,o=i<3?e:r===null?r=Object.getOwnP
|
|
|
148
187
|
border-color: oklch(from var(--esp-color-danger) var(--esp-l-shadow) c h);
|
|
149
188
|
}
|
|
150
189
|
|
|
151
|
-
|
|
152
|
-
|
|
190
|
+
|
|
191
|
+
&:focus-visible {
|
|
192
|
+
box-shadow: 0 0 0 2px var(--esp-color-background);
|
|
193
|
+
outline: 2px solid var(--esp-color-text);
|
|
194
|
+
outline-offset: 2px;
|
|
153
195
|
}
|
|
154
196
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
visibility: visible;
|
|
197
|
+
svg {
|
|
198
|
+
height: var(--esp-size-medium);
|
|
158
199
|
}
|
|
159
200
|
}
|
|
160
201
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
button:not(.retry-btn) {
|
|
164
|
-
|
|
202
|
+
|
|
203
|
+
@media (hover: hover) {
|
|
204
|
+
&:not(:hover, :focus-within) button:not(.retry-btn, .uploading) {
|
|
205
|
+
opacity: 0;
|
|
206
|
+
pointer-events: none;
|
|
165
207
|
}
|
|
166
208
|
}
|
|
167
209
|
}
|
|
@@ -208,4 +250,4 @@ var n=function(a,e,t,r){var i=arguments.length,o=i<3?e:r===null?r=Object.getOwnP
|
|
|
208
250
|
--esp-color-action-text: var(--esp-image-preview-overlay-text, var(--esp-color-layer-1));
|
|
209
251
|
--esp-color-border: var(--esp-image-preview-overlay-text, var(--esp-color-layer-1));
|
|
210
252
|
}
|
|
211
|
-
`],
|
|
253
|
+
`],l([c()],s.prototype,"url",void 0),l([c()],s.prototype,"alt",void 0),l([c({type:Number})],s.prototype,"progress",void 0),l([c({type:Boolean})],s.prototype,"failed",void 0),l([c({attribute:!1})],s.prototype,"action",void 0),l([c({attribute:!1})],s.prototype,"positionLabel",void 0),s=l([b("esp-image-preview")],s);export{s as EspalierImagePreview};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type EspalierUploadImage, type ExistingImage } from "./image-helpers.js";
|
|
1
|
+
import { type EspalierUploadImage, type ExistingImage, type ImagePreviewAction } from "./image-helpers.js";
|
|
2
2
|
import { EspalierElementBase } from "../shared/esp-element-base.js";
|
|
3
3
|
/**
|
|
4
4
|
* Used to upload images.
|
|
@@ -116,6 +116,66 @@ import { EspalierElementBase } from "../shared/esp-element-base.js";
|
|
|
116
116
|
* <esp-image-upload></esp-image-upload>
|
|
117
117
|
* ```
|
|
118
118
|
*
|
|
119
|
+
* **Keyboard and touch**
|
|
120
|
+
*
|
|
121
|
+
* Tab reaches each preview's remove button, then its action if `previewAction` is
|
|
122
|
+
* set, and finally Add photos. Enter or Space removes that image and moves focus to
|
|
123
|
+
* Add photos, scrolling it into view if a long album has pushed it off screen.
|
|
124
|
+
*
|
|
125
|
+
* With a mouse, the remove button appears when the pointer is over a preview or
|
|
126
|
+
* focus is inside it. A device without hover (`(hover: none)`) always shows it,
|
|
127
|
+
* because a tap on a hidden button would remove the image with no warning. A
|
|
128
|
+
* touchscreen laptop whose primary pointer has hover keeps the mouse behaviour, so a
|
|
129
|
+
* tap on a photo's empty corner there can still remove it.
|
|
130
|
+
*
|
|
131
|
+
* Each control's accessible name says which image it acts on. A selected image uses
|
|
132
|
+
* its file name, as in `Remove holiday.jpg`. An existing image uses its position, as
|
|
133
|
+
* in `Remove image 3 of 12`, which follows removal and reordering.
|
|
134
|
+
*
|
|
135
|
+
* **Adding an action to each preview**
|
|
136
|
+
*
|
|
137
|
+
* Set `previewAction` to show a labelled icon button in the lower-right of each
|
|
138
|
+
* completed preview, for example to open your own dialog about one image. There
|
|
139
|
+
* is no button by default.
|
|
140
|
+
*
|
|
141
|
+
* - `label` is the tooltip and the start of the accessible name, which continues
|
|
142
|
+
* with the image's file name or position, as in `Edit details: image 3 of 12`.
|
|
143
|
+
* - `icon` is an icon name from the sprite configured on `esp-root`.
|
|
144
|
+
*
|
|
145
|
+
* Existing images offer the button straight away, and selected images once
|
|
146
|
+
* `onComplete` has run. It is hidden while an image uploads, after `onFailed`,
|
|
147
|
+
* after Retry until the next `onComplete`, and while the preview shows that its
|
|
148
|
+
* image failed to load. Activating it never removes the image, opens the file
|
|
149
|
+
* picker or starts a drag.
|
|
150
|
+
*
|
|
151
|
+
* `detail.image` on `esp-image-upload-preview-action` is the object in
|
|
152
|
+
* `uploadedImages` itself, and it stays the same object through reorder and
|
|
153
|
+
* upload completion. An album can hold the same image twice, so keep anything
|
|
154
|
+
* specific to one use against this object, not against `uploadedId` or an index.
|
|
155
|
+
* If your dialog applies a change later, first check that
|
|
156
|
+
* `uploadedImages.includes(image)` is still true.
|
|
157
|
+
*
|
|
158
|
+
* ```html
|
|
159
|
+
* <script>
|
|
160
|
+
* const uploader = findByTagName("esp-image-upload")[0];
|
|
161
|
+
* uploader.previewAction = { label: "Edit details", icon: "edit" };
|
|
162
|
+
* uploader.setExistingImages([
|
|
163
|
+
* { url: "https://picsum.photos/id/29/1200/800", width: 1200, height: 800, uploadedId: "img-1" },
|
|
164
|
+
* { url: "https://picsum.photos/id/15/900/1200", width: 900, height: 1200, uploadedId: "img-2" },
|
|
165
|
+
* ]);
|
|
166
|
+
* uploader.addEventListener("esp-image-upload-file-selected", (e) => {
|
|
167
|
+
* // Stub — replace with your real upload. The button appears on completion.
|
|
168
|
+
* setTimeout(() => e.detail.onComplete("id-" + e.detail.image.file.name), 1500);
|
|
169
|
+
* });
|
|
170
|
+
* uploader.addEventListener("esp-image-upload-preview-action", (e) => {
|
|
171
|
+
* const { image } = e.detail;
|
|
172
|
+
* const name = image.source === "selected" ? image.file.name : image.uploadedId;
|
|
173
|
+
* showToast({ message: "Edit details for " + name, icon: "info-i", duration: 3 });
|
|
174
|
+
* });
|
|
175
|
+
* </script>
|
|
176
|
+
* <esp-image-upload></esp-image-upload>
|
|
177
|
+
* ```
|
|
178
|
+
*
|
|
119
179
|
* @event {CustomEvent<EspalierUploadImage>} esp-image-upload-file-removed - Fired when
|
|
120
180
|
* an image has been removed by the user. Narrow on `detail.source` to
|
|
121
181
|
* distinguish newly selected images from pre-existing server images.
|
|
@@ -141,6 +201,13 @@ import { EspalierElementBase } from "../shared/esp-element-base.js";
|
|
|
141
201
|
* (`detail.unsupported`) or because they could not be read or decoded
|
|
142
202
|
* (`detail.unreadable`). The component also shows a transient inline notice.
|
|
143
203
|
*
|
|
204
|
+
* @event {CustomEvent<ImagePreviewActionDetail>} esp-image-upload-preview-action - Fired
|
|
205
|
+
* once each time the user activates the `previewAction` button on a completed
|
|
206
|
+
* preview, whether by pointer, touch or keyboard. `detail.image` is the object in
|
|
207
|
+
* `uploadedImages` itself, so it identifies one use of an image even when an album
|
|
208
|
+
* holds the same `uploadedId` twice, and it stays the same object through reorder
|
|
209
|
+
* and upload completion.
|
|
210
|
+
*
|
|
144
211
|
* @cssprop --esp-image-upload-preview-background - The background color of the image preview area.
|
|
145
212
|
* @docPageTitle Image Upload
|
|
146
213
|
* @docUrl /components/image-upload
|
|
@@ -172,6 +239,16 @@ export declare class EspalierImageUpload extends EspalierElementBase {
|
|
|
172
239
|
* @type {number}
|
|
173
240
|
*/
|
|
174
241
|
maxRowHeightVh: number;
|
|
242
|
+
/**
|
|
243
|
+
* An optional action button in the lower-right of each completed preview,
|
|
244
|
+
* reported through `esp-image-upload-preview-action`. Clear it, or leave it
|
|
245
|
+
* unset, for no button. A blank `label` or an invalid `icon` name also shows
|
|
246
|
+
* none. Assign a new object to change it, since the component does not watch
|
|
247
|
+
* the object's fields.
|
|
248
|
+
*
|
|
249
|
+
* @type {ImagePreviewAction}
|
|
250
|
+
*/
|
|
251
|
+
previewAction?: ImagePreviewAction | null;
|
|
175
252
|
connectedCallback(): void;
|
|
176
253
|
disconnectedCallback(): void;
|
|
177
254
|
/**
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
var
|
|
1
|
+
var g=function(_,e,t,i){var o=arguments.length,r=o<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,t):i,d;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")r=Reflect.decorate(_,e,t,i);else for(var l=_.length-1;l>=0;l--)(d=_[l])&&(r=(o<3?d(r):o>3?d(e,t,r):d(e,t))||r);return o>3&&r&&Object.defineProperty(e,t,r),r},m;import{css as D,html as b}from"lit";import{customElement as R,property as w,state as x}from"lit/decorators.js";import{createRef as I,ref as E}from"lit/directives/ref.js";import"./esp-image-preview.js";import{classMap as P}from"lit/directives/class-map.js";import{getImageDetails as M,releasePreviewUrl as y}from"./image-helpers.js";const L=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
2
2
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
3
3
|
<path d="M11.911 3.634a2 2 0 0 1 1.089 1.78l.001 2.586h6.999a2 2 0 0 1 2 2v4l-.005 .15a2 2 0 0 1 -1.995 1.85l-6.999 -.001l-.001 2.587a2 2 0 0 1 -3.414 1.414l-6.586 -6.586a2 2 0 0 1 0 -2.828l6.586 -6.586a2 2 0 0 1 2.18 -.434l.145 .068z" />
|
|
4
|
-
</svg>`,
|
|
4
|
+
</svg>`,U=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
5
5
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
6
6
|
<path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" />
|
|
7
|
-
</svg>`;import{EspalierElementBase as
|
|
7
|
+
</svg>`;import{EspalierElementBase as A}from"../shared/esp-element-base.js";import{calculateAlbumLayout as C,DEFAULT_ALBUM_MAX_ROW_HEIGHT_VH as k,normalizeAlbumMaxRowHeightVh as O}from"../shared/justified-layout.js";import{ESP_EVENTS as v}from"../shared/events.js";import{viewportSize as H}from"../shared/viewport.js";const S=220,T=4,$=8e3;let p=m=class extends A{constructor(){super(...arguments),this.uploadInput=I(),this.previewsDiv=I(),this.dropAreaDiv=I(),this.draggingOver=!1,this.uploadedImages=[],this.accept="image/jpeg, image/png, image/webp",this.maxRowHeightVh=k,this._rejectionNotice="",this._uploadState=new WeakMap,this._selectionGeneration=0,this._chromeWidth=null,this._resizeObserver=new ResizeObserver(()=>{this._chromeWidth=null,this.requestUpdate()}),this._handleViewportResize=()=>{this._chromeWidth=null,this.requestUpdate()},this._dragPointerId=null,this._dragSourceIndex=null,this._dragOverIndex=null,this._dragStartX=0,this._dragStartY=0,this._dragActive=!1,this._ghostEl=null,this._ghostHalfW=0,this._ghostHalfH=0,this._arrowEl=null,this._cachedPreviews=null,this._lastDropHit=null,this._savedBodyUserSelect="",this._handleDragPointerDown=e=>{if(e.button!==0||this._dragPointerId!==null||e.composedPath().some(o=>o instanceof HTMLElement&&(o.tagName==="BUTTON"||o.tagName==="ESP-BUTTON")))return;const i=this._getPreviewIndex(e);i!==null&&(this._dragPointerId=e.pointerId,this._dragSourceIndex=i,this._dragStartX=e.clientX,this._dragStartY=e.clientY,this._dragActive=!1,document.addEventListener("pointermove",this._onDocPointerMove),document.addEventListener("pointerup",this._onDocPointerUp),document.addEventListener("pointercancel",this._onDocPointerUp))},this._onDocPointerMove=e=>{if(this._dragSourceIndex===null||e.pointerId!==this._dragPointerId)return;const t=e.clientX-this._dragStartX,i=e.clientY-this._dragStartY;if(!this._dragActive){if(Math.sqrt(t*t+i*i)<m._DRAG_THRESHOLD)return;this._dragActive=!0,this._createGhost(e.clientX,e.clientY);const l=this.shadowRoot?.querySelectorAll("esp-image-preview");l&&(this._cachedPreviews=Array.from(l).map(s=>({el:s,rect:s.getBoundingClientRect()})),this._dragSourceIndex!==null&&this._dragSourceIndex<this._cachedPreviews.length&&(this._cachedPreviews[this._dragSourceIndex].el.style.opacity="0.3")),this._savedBodyUserSelect=document.body.style.userSelect,document.body.style.userSelect="none",this.previewsDiv.value&&(this.previewsDiv.value.style.touchAction="none")}this._ghostEl&&(this._ghostEl.style.transform=`translate(${e.clientX-this._ghostHalfW}px, ${e.clientY-this._ghostHalfH}px)`);const o=this._cachedPreviews;if(!o||o.length===0)return;let r=null,d=!1;for(let l=0;l<o.length;l++){const s=o[l].rect;if(e.clientX>=s.left&&e.clientX<=s.right&&e.clientY>=s.top&&e.clientY<=s.bottom){r=l,d=e.clientX>s.left+s.width/2;break}}if(r===null){let l=1/0;for(let s=0;s<o.length;s++){const n=o[s].rect,a=e.clientX-(n.left+n.width/2),h=e.clientY-(n.top+n.height/2),c=a*a+h*h;c<l&&(l=c,r=s)}if(r!==null){const s=o[r].rect;d=e.clientX>s.left+s.width/2}}r!==null&&(this._dragOverIndex=d?r+1:r,!(this._lastDropHit&&this._lastDropHit.index===r&&this._lastDropHit.after===d)&&(this._lastDropHit={index:r,after:d},this._updateDropTargets(o.map(l=>l.el),r,d)))},this._onDocPointerUp=e=>{if(e.pointerId===this._dragPointerId){if(this._dragActive&&this._dragSourceIndex!==null&&this._dragOverIndex!==null&&this._dragSourceIndex!==this._dragOverIndex&&this._dragSourceIndex+1!==this._dragOverIndex){const t=[...this.uploadedImages],[i]=t.splice(this._dragSourceIndex,1),o=this._dragOverIndex>this._dragSourceIndex?this._dragOverIndex-1:this._dragOverIndex;t.splice(o,0,i),this.uploadedImages=t,this.dispatchEvent(new CustomEvent(v.IMAGE_UPLOAD_IMAGES_REORDERED,{detail:{images:this.uploadedImages},bubbles:!0,composed:!0}))}this._clearDragState()}},this._arrowColor=null,this.filesSelected=async e=>{const t=[],i=[];for(const a of e)(this._isAccepted(a)?t:i).push(a);if(!t.length){i.length&&this._reportRejectedFiles({unsupported:i,unreadable:[]});return}const o=this._selectionGeneration,r=Math.round(S*Math.min(globalThis.devicePixelRatio||1,2)),d=new Array(t.length).fill(null),l=[];let s=0;const n=async()=>{for(;s<t.length;){const a=s++,h=t[a];try{const c=await M(h,{thumbnailHeight:r});if(o!==this._selectionGeneration){y(c);continue}this._addSelectedImage(d,a,c)}catch{l.push(h)}}};await Promise.all(Array.from({length:Math.min(T,t.length)},()=>n())),(i.length||l.length)&&o===this._selectionGeneration&&this._reportRejectedFiles({unsupported:i,unreadable:l})},this._makeCallbacks=e=>({onProgress:t=>{const i=this._uploadState.get(e);i&&(i.progress=t,i.failed=!1,this.requestUpdate())},onComplete:t=>{if(!this._uploadState.get(e))return;e.uploadedId=t,this._uploadState.delete(e),!this.uploadedImages.find(r=>!r.uploadedId)?this.uploadedImages=[...this.uploadedImages]:this.requestUpdate()},onFailed:()=>{const t=this._uploadState.get(e);t&&(t.progress=void 0,t.failed=!0,this.requestUpdate())}}),this._fullPreviewUrls=new Map,this.setExistingImages=e=>{this._selectionGeneration++,this._releaseSelectedImages(),this.uploadedImages=e.filter(t=>t.url&&t.uploadedId&&Number.isFinite(t.width)&&t.width>0&&Number.isFinite(t.height)&&t.height>0).map(t=>({source:"existing",url:t.url,urls:t.urls,height:t.height,width:t.width,uploadedId:t.uploadedId,orientation:t.width>t.height?"landscape":"portrait"})),this._uploadState=new WeakMap}}connectedCallback(){super.connectedCallback(),this._resizeObserver.observe(this),window.addEventListener("resize",this._handleViewportResize)}disconnectedCallback(){super.disconnectedCallback(),this._resizeObserver.disconnect(),window.removeEventListener("resize",this._handleViewportResize),this._clearDragState(),clearTimeout(this._noticeTimer),this._selectionGeneration++,this._releaseSelectedImages()}_getPreviewIndex(e){const t=this.shadowRoot?.querySelectorAll("esp-image-preview");if(!t)return null;for(const i of e.composedPath())if(i instanceof HTMLElement&&i.tagName==="ESP-IMAGE-PREVIEW"){for(let o=0;o<t.length;o++)if(t[o]===i)return o}return null}_clearDragState(){document.removeEventListener("pointermove",this._onDocPointerMove),document.removeEventListener("pointerup",this._onDocPointerUp),document.removeEventListener("pointercancel",this._onDocPointerUp),this._dragActive&&(this._cachedPreviews&&this._dragSourceIndex!==null&&this._dragSourceIndex<this._cachedPreviews.length&&(this._cachedPreviews[this._dragSourceIndex].el.style.opacity=""),this._clearDropTargets(),document.body.style.userSelect=this._savedBodyUserSelect,this.previewsDiv.value&&(this.previewsDiv.value.style.touchAction="")),this._ghostEl?.remove(),this._ghostEl=null,this._cachedPreviews=null,this._lastDropHit=null,this._dragPointerId=null,this._dragSourceIndex=null,this._dragOverIndex=null,this._dragActive=!1}_createGhost(e,t){const i=this.shadowRoot?.querySelectorAll("esp-image-preview");if(!i||this._dragSourceIndex===null)return;const o=i[this._dragSourceIndex],r=o.getBoundingClientRect(),d=document.createElement("div"),l=Math.min(120/r.width,120/r.height),s=r.width*l,n=r.height*l;d.style.cssText=`
|
|
8
8
|
position: fixed;
|
|
9
9
|
left: 0;
|
|
10
10
|
top: 0;
|
|
11
11
|
z-index: 10001;
|
|
12
12
|
pointer-events: none;
|
|
13
13
|
width: ${s}px;
|
|
14
|
-
height: ${
|
|
14
|
+
height: ${n}px;
|
|
15
15
|
border: 2px dashed var(--esp-color-action-background, #4aa);
|
|
16
16
|
border-radius: var(--esp-size-border-radius, 4px);
|
|
17
17
|
background-size: cover;
|
|
18
18
|
background-position: center;
|
|
19
19
|
opacity: 0.85;
|
|
20
20
|
will-change: transform;
|
|
21
|
-
transform: translate(${e-s/2}px, ${t-
|
|
22
|
-
`;const a=o.shadowRoot?.querySelector("img");a&&(d.style.backgroundImage=`url(${a.src})`),document.body.appendChild(d),this._ghostEl=d,this._ghostHalfW=s/2,this._ghostHalfH=
|
|
21
|
+
transform: translate(${e-s/2}px, ${t-n/2}px);
|
|
22
|
+
`;const a=o.shadowRoot?.querySelector("img");a&&(d.style.backgroundImage=`url(${a.src})`),document.body.appendChild(d),this._ghostEl=d,this._ghostHalfW=s/2,this._ghostHalfH=n/2}_updateDropTargets(e,t,i){for(let a=0;a<e.length;a++){const h=e[a];a===t&&a!==this._dragSourceIndex?h.setAttribute("data-drop-side",i?"right":"left"):h.removeAttribute("data-drop-side")}if(t===this._dragSourceIndex){this._arrowEl&&(this._arrowEl.style.display="none");return}const o=this._cachedPreviews,r=o?o[t].rect:e[t].getBoundingClientRect(),d=i?U:L;if(!this._arrowEl){const a=document.createElement("div");a.style.cssText=`
|
|
23
23
|
position: fixed;
|
|
24
24
|
left: 0;
|
|
25
25
|
top: 0;
|
|
@@ -30,27 +30,30 @@ var f=function(_,e,t,i){var o=arguments.length,r=o<3?e:i===null?i=Object.getOwnP
|
|
|
30
30
|
justify-content: center;
|
|
31
31
|
will-change: transform;
|
|
32
32
|
filter: drop-shadow(0 1px 3px rgba(0,0,0,0.4));
|
|
33
|
-
`,document.body.appendChild(a),this._arrowEl=a;const h=e[t],u=getComputedStyle(h).getPropertyValue("--esp-color-complementary").trim();this._arrowColor=u?`oklch(from ${u} var(--esp-l-accent) c h)`:"oklch(0.7 0.2 330)"}this._arrowEl.innerHTML=d;const
|
|
33
|
+
`,document.body.appendChild(a),this._arrowEl=a;const h=e[t],u=getComputedStyle(h).getPropertyValue("--esp-color-complementary").trim();this._arrowColor=u?`oklch(from ${u} var(--esp-l-accent) c h)`:"oklch(0.7 0.2 330)"}this._arrowEl.innerHTML=d;const l=this._arrowEl.querySelector("svg");l&&(l.style.width="40px",l.style.height="40px",l.style.color=this._arrowColor??"oklch(0.7 0.2 330)"),this._arrowEl.style.display="flex";const s=i?r.left+r.width*.75:r.left+r.width*.25,n=r.top+r.height/2;this._arrowEl.style.transform=`translate(${s-20}px, ${n-20}px)`}_clearDropTargets(){const e=this.shadowRoot?.querySelectorAll("esp-image-preview");if(e)for(const t of e)t.removeAttribute("data-drop-side");this._arrowEl?.remove(),this._arrowEl=null,this._arrowColor=null}getContainerWidth(){const e=this.parentElement??this.offsetParent,t=e?e.getBoundingClientRect().width:window.innerWidth;if(t<=0){const i=window.innerWidth;return i<640?i-32:i<1024?i*90/100-32:Math.min(i*85/100-32,1400)}if(this._chromeWidth===null){const i=this.previewsDiv.value;if(i){const o=n=>{const a=parseFloat(n);return Number.isFinite(a)?a:0},r=getComputedStyle(i),d=o(r.paddingLeft)+o(r.paddingRight),l=this.shadowRoot?.querySelector(".esp-field"),s=l?o(getComputedStyle(l).borderLeftWidth)+o(getComputedStyle(l).borderRightWidth):0;this._chromeWidth=d+s}}return Math.max(t-(this._chromeWidth??0),1)}_isAccepted(e){const t=this.accept.split(",").map(r=>r.trim().toLowerCase()).filter(Boolean);if(!t.length)return e.type.toLowerCase().startsWith("image/");const i=e.type.toLowerCase(),o=e.name.toLowerCase();return t.some(r=>r.startsWith(".")?o.endsWith(r):r.endsWith("/*")?i.startsWith(r.slice(0,-1)):i===r)}_addSelectedImage(e,t,i){e[t]=i;const o=[...this.uploadedImages];let r=-1;for(let l=t-1;l>=0&&r<0;l--){const s=e[l];if(!s)continue;const n=o.indexOf(s);n>=0&&(r=n+1)}for(let l=t+1;l<e.length&&r<0;l++){const s=e[l];if(!s)continue;const n=o.indexOf(s);n>=0&&(r=n)}r<0&&(r=o.length),o.splice(r,0,i);const d=new AbortController;this._uploadState.set(i,{progress:null,failed:!1,controller:d}),this.uploadedImages=o,this.dispatchEvent(new CustomEvent(v.IMAGE_UPLOAD_FILE_SELECTED,{detail:{image:i,signal:d.signal,...this._makeCallbacks(i)},bubbles:!0,composed:!0}))}_reportRejectedFiles(e){this.dispatchEvent(new CustomEvent(v.IMAGE_UPLOAD_FILES_REJECTED,{detail:e,bubbles:!0,composed:!0}));const t=o=>o===1?"file":"files",i=[];e.unsupported.length&&i.push(`${e.unsupported.length} unsupported ${t(e.unsupported.length)} skipped`),e.unreadable.length&&i.push(`${e.unreadable.length} ${t(e.unreadable.length)} could not be read`),this._rejectionNotice=i.join("; "),clearTimeout(this._noticeTimer),this._noticeTimer=setTimeout(()=>{this._rejectionNotice=""},$)}_emitPreviewAction(e){!this.uploadedImages.includes(e)||this._uploadState.has(e)||this.dispatchEvent(new CustomEvent(v.IMAGE_UPLOAD_PREVIEW_ACTION,{detail:{image:e},bubbles:!0,composed:!0}))}_previewUrlFor(e,t,i){const o=e.width>0&&e.height>0?e.width/e.height:1,r=Math.max(t,(i??0)/o);if(e.source!=="selected")return this._pickExistingPreviewUrl(e,r);const d=Math.min(globalThis.devicePixelRatio||1,2),l=S*d;if(r*d<=l*m._THUMBNAIL_UPSCALE_SLACK||e.height<=l)return e.url;let a=this._fullPreviewUrls.get(e);return a||(a=URL.createObjectURL(e.file),this._fullPreviewUrls.set(e,a)),a}_pickExistingPreviewUrl(e,t){const i=e.urls;if(!i?.length)return e.url;const o=Math.min(globalThis.devicePixelRatio||1,2),r=e.height>0?e.width/e.height:1,d=t*r*o,l=[...i].sort((n,a)=>n.minWidth-a.minWidth);return(l.find(n=>n.minWidth>=d)??l[l.length-1]).url}_releaseFullPreviewUrl(e){if(e.source!=="selected")return;const t=this._fullPreviewUrls.get(e);t&&(URL.revokeObjectURL(t),this._fullPreviewUrls.delete(e))}_releaseSelectedImages(){for(const e of this.uploadedImages)e.source==="selected"&&(this._uploadState.get(e)?.controller.abort(),y(e));for(const e of this._fullPreviewUrls.values())URL.revokeObjectURL(e);this._fullPreviewUrls.clear()}render(){const{draggingOver:e,uploadedImages:t}=this,i={"dragging-over":e,"has-previews":t.length},o=this.getContainerWidth(),{height:r}=H(),d=r>0?r*O(this.maxRowHeightVh)/100:Number.POSITIVE_INFINITY,l=C(t,{containerWidth:o,targetRowHeight:S,gap:8,maxRowHeight:d,maxImagesPerRow:this.maxImagesPerRow});return b` <div
|
|
34
34
|
class="esp-field"
|
|
35
|
-
@click=${()=>
|
|
35
|
+
@click=${s=>{const n=s.composedPath();if(n.some(c=>c instanceof HTMLElement&&c.hasAttribute("data-preview-action")))return;const h=s.detail===0&&n.some(c=>c instanceof HTMLElement&&c.localName==="esp-image-preview");this.dropAreaDiv.value?.focus({preventScroll:!h})}}
|
|
36
36
|
@dragover=${s=>{s.preventDefault(),this.draggingOver=!0}}
|
|
37
37
|
@dragleave=${s=>{s.preventDefault(),this.draggingOver=!1}}
|
|
38
|
-
@drop=${s=>{s.preventDefault(),this.draggingOver=!1;const
|
|
38
|
+
@drop=${s=>{s.preventDefault(),this.draggingOver=!1;const n=s.dataTransfer?.files;n&&n.length&&this.filesSelected(n)}}
|
|
39
39
|
>
|
|
40
40
|
<div
|
|
41
41
|
${E(this.previewsDiv)}
|
|
42
42
|
class=${P({previews:!0,...i})}
|
|
43
43
|
@pointerdown=${this._handleDragPointerDown}
|
|
44
44
|
>
|
|
45
|
-
${
|
|
46
|
-
${s.items.map(({image:
|
|
45
|
+
${l.map(s=>b`<div class="photo-row" style="height: ${s.height}px;">
|
|
46
|
+
${s.items.map(({image:n,width:a})=>{const h=s.height,c=this._uploadState.get(n);return b`<esp-image-preview
|
|
47
47
|
style="width: ${a}px; height: ${h}px; flex-shrink: 0;"
|
|
48
|
-
.url=${this._previewUrlFor(
|
|
49
|
-
.alt=${
|
|
50
|
-
.
|
|
51
|
-
.
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
.url=${this._previewUrlFor(n,h,a)}
|
|
49
|
+
.alt=${n.source==="selected"?n.file.name:""}
|
|
50
|
+
.positionLabel=${`image ${t.indexOf(n)+1} of ${t.length}`}
|
|
51
|
+
.progress=${c?.progress}
|
|
52
|
+
.failed=${c?.failed??!1}
|
|
53
|
+
.action=${this.previewAction}
|
|
54
|
+
@esp-internal-image-preview-action=${()=>this._emitPreviewAction(n)}
|
|
55
|
+
@esp-internal-image-preview-remove=${()=>{const u=this.uploadedImages.indexOf(n);if(u<0)return;this._uploadState.get(n)?.controller.abort(),this._uploadState.delete(n),y(n),this._releaseFullPreviewUrl(n);const f=[...this.uploadedImages];f.splice(u,1),this.uploadedImages=f,this.dispatchEvent(new CustomEvent(v.IMAGE_UPLOAD_FILE_REMOVED,{detail:n,bubbles:!0,composed:!0}))}}
|
|
56
|
+
@esp-internal-image-preview-retry=${()=>{if(n.source!=="selected")return;const u=new AbortController,f=this._uploadState.get(n);f?(f.failed=!1,f.progress=null,f.controller=u):this._uploadState.set(n,{progress:null,failed:!1,controller:u}),this.requestUpdate(),this.dispatchEvent(new CustomEvent(v.IMAGE_UPLOAD_RETRY,{detail:{image:n,signal:u.signal,...this._makeCallbacks(n)},bubbles:!0,composed:!0}))}}
|
|
54
57
|
></esp-image-preview>`})}
|
|
55
58
|
</div>`)}
|
|
56
59
|
</div>
|
|
@@ -61,7 +64,7 @@ var f=function(_,e,t,i){var o=arguments.length,r=o<3?e:i===null?i=Object.getOwnP
|
|
|
61
64
|
aria-label="Add photos"
|
|
62
65
|
class=${P({"drop-area":!0,...i})}
|
|
63
66
|
@click=${()=>{this.uploadInput.value?.click()}}
|
|
64
|
-
@keydown=${s=>{(s.code==="Enter"||s.code==="Space")&&(s.preventDefault(),this.uploadInput.value?.click())}}
|
|
67
|
+
@keydown=${s=>{(s.code==="Enter"||s.code==="Space")&&(s.preventDefault(),s.repeat||this.uploadInput.value?.click())}}
|
|
65
68
|
>
|
|
66
69
|
<svg
|
|
67
70
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -111,7 +114,7 @@ var f=function(_,e,t,i){var o=arguments.length,r=o<3?e:i===null?i=Object.getOwnP
|
|
|
111
114
|
hidden
|
|
112
115
|
@change=${()=>{const s=this.uploadInput.value;!s||!s.files||!s.files.length||(this.filesSelected(s.files),s.value="")}}
|
|
113
116
|
/>
|
|
114
|
-
</div>`}};
|
|
117
|
+
</div>`}};p._DRAG_THRESHOLD=8,p._THUMBNAIL_UPSCALE_SLACK=1.1,p.styles=[...A.styles,D`
|
|
115
118
|
:host {
|
|
116
119
|
overflow: hidden;
|
|
117
120
|
}
|
|
@@ -238,4 +241,4 @@ var f=function(_,e,t,i){var o=arguments.length,r=o<3?e:i===null?i=Object.getOwnP
|
|
|
238
241
|
}
|
|
239
242
|
}
|
|
240
243
|
}
|
|
241
|
-
`],
|
|
244
|
+
`],g([x()],p.prototype,"draggingOver",void 0),g([x()],p.prototype,"uploadedImages",void 0),g([w({type:String})],p.prototype,"accept",void 0),g([w({attribute:"max-images-per-row",type:Number})],p.prototype,"maxImagesPerRow",void 0),g([w({attribute:"max-row-height-vh",type:Number})],p.prototype,"maxRowHeightVh",void 0),g([w({attribute:!1})],p.prototype,"previewAction",void 0),g([x()],p.prototype,"_rejectionNotice",void 0),p=m=g([R("esp-image-upload")],p);export{p as EspalierImageUpload};
|
|
@@ -31,6 +31,16 @@ export type ExistingUploadImage = UploadImageBase & {
|
|
|
31
31
|
* Narrow on `source` to access variant-specific fields.
|
|
32
32
|
*/
|
|
33
33
|
export type EspalierUploadImage = SelectedUploadImage | ExistingUploadImage;
|
|
34
|
+
/**
|
|
35
|
+
* Configures the optional action button on each completed preview of
|
|
36
|
+
* `esp-image-upload`. Assign a new object to change it.
|
|
37
|
+
*/
|
|
38
|
+
export interface ImagePreviewAction {
|
|
39
|
+
/** Tooltip text and accessible name of the button. */
|
|
40
|
+
label: string;
|
|
41
|
+
/** Icon name from the sprite configured on the surrounding `esp-root`. */
|
|
42
|
+
icon: string;
|
|
43
|
+
}
|
|
34
44
|
/** Callbacks attached to `esp-image-upload-file-selected` and `esp-image-upload-retry` event details. */
|
|
35
45
|
export interface UploadCallbacks {
|
|
36
46
|
/** Update the progress overlay (0–100 determinate, `null` for indeterminate). */
|
package/dist/index.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ export { showFlyout, closeFlyout, type FlyoutConfig, type FlyoutCloseReason, } f
|
|
|
72
72
|
export { requestHelp, type HelpRequest, type HelpState } from "./shared/help-events.js";
|
|
73
73
|
export { getEspBus, type EspBusEventMap, type SchemeEvents, type ToastEvents, type FlyoutEvents, type HelpEvents, type PopoverEvents, type SizeEvents, type PageEventMap, type SeedColorRoot, } from "./shared/bus-events.js";
|
|
74
74
|
export * from "./shared/events.js";
|
|
75
|
-
export { getImageDetails, releasePreviewUrl, type EspalierUploadImage, type ImageDetailsOptions, type SelectedUploadImage, type ExistingUploadImage, type ExistingImage, type ResponsiveImageUrl, type UploadCallbacks, type UploadEventDetail, } from "./image-upload/image-helpers.js";
|
|
75
|
+
export { getImageDetails, releasePreviewUrl, type EspalierUploadImage, type ImageDetailsOptions, type ImagePreviewAction, type SelectedUploadImage, type ExistingUploadImage, type ExistingImage, type ResponsiveImageUrl, type UploadCallbacks, type UploadEventDetail, } from "./image-upload/image-helpers.js";
|
|
76
76
|
export { calculateAlbumLayout, DEFAULT_ALBUM_MAX_ROW_HEIGHT_VH, normalizeAlbumMaxRowHeightVh, normalizeMaxImagesPerRow, type AlbumLayoutItem, type AlbumLayoutOptions, type AlbumLayoutRow, type LayoutImage, } from "./shared/justified-layout.js";
|
|
77
77
|
export { type TypeaheadFetchItems } from "./pickers/types.js";
|
|
78
78
|
export { type EspalierTheme, type LightnessKey, type LightnessReference, type PartialTheme, type PartialThemeContexts, type PartialThemeTones, type ThemeContext, type ThemeContexts, type ThemeTones, type ToneReference, type VariantColorSource, encodeTheme, parseTheme, mergePartials, layerThemes, validateThemePair, resolveContextTheme, isObjectRecord, isSemanticColorName, buildTaprootLightTheme, buildTaprootDarkTheme, NESTED_THEME_KEYS, } from "./shared/theme.js";
|
|
@@ -37,6 +37,16 @@ export declare class EspalierMenuGroup extends EspalierElementBase {
|
|
|
37
37
|
label: string;
|
|
38
38
|
/** Optional URL for the group label. */
|
|
39
39
|
url: string;
|
|
40
|
+
/** Target browsing context for the link. Empty omits the attribute. */
|
|
41
|
+
target: string;
|
|
42
|
+
/** Link relationship, passed through unchanged. Empty omits the attribute. */
|
|
43
|
+
rel: string;
|
|
44
|
+
/**
|
|
45
|
+
* Hidden phrase appended to the accessible name of a `target="_blank"` link.
|
|
46
|
+
* Replace for non-English sites. Empty or whitespace text falls back to English.
|
|
47
|
+
* @default "opens in a new tab"
|
|
48
|
+
*/
|
|
49
|
+
newTabLabel: string;
|
|
40
50
|
/** Optional icon name from the configured Espalier SVG sprite. */
|
|
41
51
|
icon: string;
|
|
42
52
|
/** Whether the group's children are visible. */
|