@taprootio/espalier 6.3.0 → 6.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.
@@ -136,6 +136,41 @@ export declare class EspalierBox extends EspalierElementBase {
136
136
  * </script>
137
137
  * ```
138
138
  *
139
+ * A full-screen box fills the viewport. Inside a
140
+ * [bounded dialog](/components/dialog) it fills exactly the dialog's
141
+ * rectangle instead: it takes its position and size from the same
142
+ * `--esp-dialog-inset-*` custom properties, set on the `esp-dialog` or any
143
+ * ancestor of the box.
144
+ *
145
+ * ```html
146
+ * <style>
147
+ * esp-dialog.bounded-box {
148
+ * --esp-dialog-inset-block-start: 6rem;
149
+ * --esp-dialog-inset-inline-start: 3rem;
150
+ * --esp-dialog-inset-inline-end: 3rem;
151
+ * --esp-dialog-inset-block-end: 2rem;
152
+ * }
153
+ * </style>
154
+ * <esp-dialog class="bounded-box" full-screen>
155
+ * <esp-box full-screen>
156
+ * <h2>Box in a bounded dialog</h2>
157
+ * <p>This box fills the rectangle the dialog is bounded to.</p>
158
+ * <esp-button intent="danger" label="Close"></esp-button>
159
+ * </esp-box>
160
+ * </esp-dialog>
161
+ * <esp-button label="Show Dialog"></esp-button>
162
+ * <script>
163
+ * const theDialog = findByTagName("esp-dialog")[0];
164
+ * const buttons = findByTagName("esp-button");
165
+ * buttons[0].addEventListener("esp-clicked", () => {
166
+ * theDialog.toggleOpen();
167
+ * });
168
+ * buttons[1].addEventListener("esp-clicked", () => {
169
+ * theDialog.toggleOpen();
170
+ * });
171
+ * </script>
172
+ * ```
173
+ *
139
174
  * @type {boolean}
140
175
  */
141
176
  fullScreen: boolean;
@@ -1,10 +1,10 @@
1
- var n=function(t,e,r,i){var a=arguments.length,o=a<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,r):i,l;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")o=Reflect.decorate(t,e,r,i);else for(var p=t.length-1;p>=0;p--)(l=t[p])&&(o=(a<3?l(o):a>3?l(e,r,o):l(e,r))||o);return a>3&&o&&Object.defineProperty(e,r,o),o};import{paintedSurface as c}from"../shared/surface-layer.js";import{css as b,html as v}from"lit";import{customElement as h,property as f}from"lit/decorators.js";import{classMap as u}from"lit/directives/class-map.js";import{EspalierElementBase as d}from"../shared/esp-element-base.js";let s=class extends d{constructor(){super(...arguments),this.fullScreen=!1}render(){const{fullScreen:e}=this;return v`
1
+ var p=function(r,e,s,i){var a=arguments.length,o=a<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,s):i,l;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")o=Reflect.decorate(r,e,s,i);else for(var n=r.length-1;n>=0;n--)(l=r[n])&&(o=(a<3?l(o):a>3?l(e,s,o):l(e,s))||o);return a>3&&o&&Object.defineProperty(e,s,o),o};import{paintedSurface as c}from"../shared/surface-layer.js";import{css as v,html as b}from"lit";import{customElement as f,property as h}from"lit/decorators.js";import{classMap as u}from"lit/directives/class-map.js";import{EspalierElementBase as d}from"../shared/esp-element-base.js";let t=class extends d{constructor(){super(...arguments),this.fullScreen=!1}render(){const{fullScreen:e}=this;return b`
2
2
  <div class=${u({"esp-box":!0,"full-screen":e})}>
3
3
  <div part="box">
4
4
  <slot></slot>
5
5
  </div>
6
6
  </div>
7
- `}};s.styles=[...d.styles,c(1),b`
7
+ `}};t.styles=[...d.styles,c(1),v`
8
8
  :host {
9
9
  display: block;
10
10
  }
@@ -42,16 +42,31 @@ var n=function(t,e,r,i){var a=arguments.length,o=a<3?e:i===null?i=Object.getOwnP
42
42
  }
43
43
 
44
44
  &.full-screen {
45
- width: 100vw;
46
-
47
- height: 100vh;
48
- height: 100dvh;
45
+ width: calc(
46
+ 100vw - var(--esp-dialog-inset-inline-start, 0px) - var(
47
+ --esp-dialog-inset-inline-end,
48
+ 0px
49
+ )
50
+ );
51
+ height: calc(
52
+ 100vh - var(--esp-dialog-inset-block-start, 0px) - var(
53
+ --esp-dialog-inset-block-end,
54
+ 0px
55
+ )
56
+ );
57
+ height: calc(
58
+ 100dvh - var(--esp-dialog-inset-block-start, 0px) - var(
59
+ --esp-dialog-inset-block-end,
60
+ 0px
61
+ )
62
+ );
49
63
  position: fixed;
50
- top: 0;
51
- left: 0;
64
+ inset-block-start: var(--esp-dialog-inset-block-start, 0px);
65
+ inset-inline-start: var(--esp-dialog-inset-inline-start, 0px);
52
66
  z-index: 10;
53
67
 
54
68
  > div {
69
+ position: relative;
55
70
  overflow-y: auto;
56
71
 
57
72
  padding-bottom: max(
@@ -61,4 +76,4 @@ var n=function(t,e,r,i){var a=arguments.length,o=a<3?e:i===null?i=Object.getOwnP
61
76
  }
62
77
  }
63
78
  }
64
- `],n([f({attribute:"full-screen",type:Boolean})],s.prototype,"fullScreen",void 0),s=n([h("esp-box")],s);export{s as EspalierBox};
79
+ `],p([h({attribute:"full-screen",type:Boolean})],t.prototype,"fullScreen",void 0),t=p([f("esp-box")],t);export{t as EspalierBox};
@@ -20,6 +20,78 @@ import { LitElement } from "lit";
20
20
  * - Restores focus to the element that was focused before the
21
21
  * dialog was opened.
22
22
  *
23
+ * ### Bounded dialog
24
+ *
25
+ * A dialog covers the whole viewport by default. Set the four inset custom
26
+ * properties on the `esp-dialog` host to confine it to a rectangle and leave
27
+ * the rest of the page visible, for example to keep a header or a live preview
28
+ * on screen while a full-screen editor is open. The vellum (the dialog's
29
+ * scrolling surface) and its backdrop paint only inside the rectangle, and a
30
+ * slotted `esp-box full-screen` fills exactly that rectangle.
31
+ *
32
+ * The insets are lengths (`0px`, `4rem`, `var(--header-height)`), not unitless
33
+ * numbers, and they are measured from the viewport edges. They are logical, so
34
+ * "inline start" is the left edge in left-to-right text and the right edge in
35
+ * right-to-left text. Because custom properties inherit, a dialog nested inside
36
+ * a bounded dialog's markup is bounded the same way unless it sets its own
37
+ * insets.
38
+ *
39
+ * Bounds change only where the dialog paints. What stays reachable is decided
40
+ * by the same rules as an unbounded dialog:
41
+ *
42
+ * - The dialog has no click-outside dismissal, bounded or not. It closes on
43
+ * `Escape`, an `esp-form-dialog-close-requested` event, or `toggleOpen()`, so
44
+ * a click on the uncovered region never counts as an "outside" click.
45
+ * - The dialog marks `inert` the siblings of its anchor element, not the
46
+ * regions its bounds leave uncovered. The anchor is `esp-page`'s dialog drop
47
+ * zone when the dialog sits in the page's markup, so there the page's
48
+ * internal tracks (header, menus, preview, help, footer) are inert and not
49
+ * clickable even where the dialog does not cover them. A dialog that is a
50
+ * direct child of another component's shadow root is anchored on the nearest
51
+ * ancestor host that has a light-DOM parent, and only that host's siblings
52
+ * go inert; the page around it stays live. A dialog wrapped in an element
53
+ * inside that shadow root is anchored on itself, so its siblings in the
54
+ * wrapper go inert instead.
55
+ * - A region that is not inert receives clicks and keyboard focus. The focus
56
+ * trap wraps `Tab` and `Shift+Tab` at the dialog's first and last focusable
57
+ * elements, and `Escape` is handled on the dialog, both only while focus is
58
+ * inside it. A click on an uncovered control moves focus there, and from then
59
+ * on `Tab` continues through the page and `Escape` no longer closes the
60
+ * dialog. Set `inert` on the uncovered regions that must not be reachable
61
+ * while the dialog is open (an inert region is also skipped by screen
62
+ * readers).
63
+ * - Bounds assume a horizontal writing mode: the insets are logical, but the
64
+ * dialog's width and height are physical.
65
+ *
66
+ * ```html
67
+ * <style>
68
+ * esp-dialog.bounded {
69
+ * --esp-dialog-inset-block-start: 6rem;
70
+ * --esp-dialog-inset-inline-start: 3rem;
71
+ * --esp-dialog-inset-inline-end: 3rem;
72
+ * --esp-dialog-inset-block-end: 2rem;
73
+ * }
74
+ * </style>
75
+ * <esp-dialog class="bounded" full-screen>
76
+ * <esp-box full-screen>
77
+ * <h2>Bounded dialog</h2>
78
+ * <p>The page above and around this box stays visible.</p>
79
+ * <esp-button intent="danger" label="Close"></esp-button>
80
+ * </esp-box>
81
+ * </esp-dialog>
82
+ * <esp-button label="Show Dialog"></esp-button>
83
+ * <script>
84
+ * const theDialog = findByTagName("esp-dialog")[0];
85
+ * const buttons = findByTagName("esp-button");
86
+ * buttons[0].addEventListener("esp-clicked", () => {
87
+ * theDialog.toggleOpen();
88
+ * });
89
+ * buttons[1].addEventListener("esp-clicked", () => {
90
+ * theDialog.toggleOpen();
91
+ * });
92
+ * </script>
93
+ * ```
94
+ *
23
95
  * @slot - Place content to show modally in the default slot.
24
96
  *
25
97
  * ```html
@@ -105,6 +177,10 @@ import { LitElement } from "lit";
105
177
  *
106
178
  * @cssprop --esp-color-dialog-bg - The background color of the dialog overlay.
107
179
  * @cssprop --esp-dialog-bg-opacity - The opacity of the dialog overlay background.
180
+ * @cssprop --esp-dialog-inset-block-start - The top edge of the area the dialog covers, as a length from the viewport top. Defaults to `0px`.
181
+ * @cssprop --esp-dialog-inset-inline-start - The leading edge (left in left-to-right text) of the area the dialog covers, as a length from the viewport edge. Defaults to `0px`.
182
+ * @cssprop --esp-dialog-inset-inline-end - The trailing edge (right in left-to-right text) of the area the dialog covers, as a length from the viewport edge. Defaults to `0px`.
183
+ * @cssprop --esp-dialog-inset-block-end - The bottom edge of the area the dialog covers, as a length from the viewport bottom. Defaults to `0px`.
108
184
  * @attr {"1"|"2"|"3"|"4"} surface-layer - Declares the painted surface depth; defaults to layer 3.
109
185
  * @customElement esp-dialog
110
186
  * @docPageTitle Dialog
@@ -1,14 +1,14 @@
1
- var c=function(l,e,t,o){var n=arguments.length,i=n<3?e:o===null?o=Object.getOwnPropertyDescriptor(e,t):o,r;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")i=Reflect.decorate(l,e,t,o);else for(var a=l.length-1;a>=0;a--)(r=l[a])&&(i=(n<3?r(i):n>3?r(e,t,i):r(e,t))||i);return n>3&&i&&Object.defineProperty(e,t,i),i};import{paintedSurface as f,surfaceLayerAttributes as v}from"../shared/surface-layer.js";import{LitElement as m,css as b,html as g,nothing as u}from"lit";import{customElement as y,property as _,state as O}from"lit/decorators.js";import{classMap as k}from"lit/directives/class-map.js";import{createRef as d,ref as h}from"lit/directives/ref.js";import{OverlayController as E}from"../shared/overlay-controller.js";import{ESP_EVENTS as p}from"../shared/events.js";let s=class extends m{constructor(){super(...arguments),this.dialogSlot=d(),this.vellumRef=d(),this.isOpen=!1,this._overlay=new E({host:this,getFocusTrapContainer:()=>this.vellumRef.value??null,getFocusFallback:()=>this.vellumRef.value??null}),this._closeReason="api",this._lifecycleToken=0,this.fullScreen=!1}toggleOpen(){if(this.isOpen){const e=this._closeReason;this._closeReason="api";const t=new CustomEvent(p.DIALOG_CLOSING,{detail:{reason:e},bubbles:!0,composed:!0,cancelable:!0});if(!this.dispatchEvent(t))return;const o=++this._lifecycleToken;this.removeAttribute("is-open"),this.isOpen=!1,this._overlay.close(),this.updateComplete.then(()=>{this.isOpen||this._lifecycleToken!==o||this.dispatchEvent(new CustomEvent(p.DIALOG_CLOSED,{detail:{reason:e},bubbles:!0,composed:!0}))})}else{const e=++this._lifecycleToken;this.setAttribute("is-open","true"),this.isOpen=!0,this._overlay.open(),this.updateComplete.then(()=>{!this.isOpen||this._lifecycleToken!==e||(this._moveFocusIntoDialog(),this.dispatchEvent(new CustomEvent(p.DIALOG_OPENED,{detail:{},bubbles:!0,composed:!0})))})}}_handleKeyDown(e){if(this.isOpen){if(e.key==="Escape"){e.preventDefault(),this._closeReason="escape",this.toggleOpen();return}e.key==="Tab"&&this._overlay.trapFocus(e)}}_moveFocusIntoDialog(){this._overlay.moveFocusInto()}firstUpdated(){this.dialogSlot.value&&this.dialogSlot.value.addEventListener("esp-form-dialog-close-requested",e=>{this._closeReason="close-dialog",this.toggleOpen(),e.preventDefault()})}disconnectedCallback(){super.disconnectedCallback()}render(){const{isOpen:e,fullScreen:t}=this,o={"is-open":e,"full-screen":t};return g`<div
1
+ var p=function(l,e,t,i){var n=arguments.length,s=n<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,t):i,a;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")s=Reflect.decorate(l,e,t,i);else for(var r=l.length-1;r>=0;r--)(a=l[r])&&(s=(n<3?a(s):n>3?a(e,t,s):a(e,t))||s);return n>3&&s&&Object.defineProperty(e,t,s),s};import{paintedSurface as v,surfaceLayerAttributes as f}from"../shared/surface-layer.js";import{LitElement as g,css as b,html as m,nothing as d}from"lit";import{customElement as y,property as k,state as _}from"lit/decorators.js";import{classMap as x}from"lit/directives/class-map.js";import{createRef as u,ref as h}from"lit/directives/ref.js";import{OverlayController as O}from"../shared/overlay-controller.js";import{ESP_EVENTS as c}from"../shared/events.js";let o=class extends g{constructor(){super(...arguments),this.dialogSlot=u(),this.vellumRef=u(),this.isOpen=!1,this._overlay=new O({host:this,getFocusTrapContainer:()=>this.vellumRef.value??null,getFocusFallback:()=>this.vellumRef.value??null}),this._closeReason="api",this._lifecycleToken=0,this.fullScreen=!1}toggleOpen(){if(this.isOpen){const e=this._closeReason;this._closeReason="api";const t=new CustomEvent(c.DIALOG_CLOSING,{detail:{reason:e},bubbles:!0,composed:!0,cancelable:!0});if(!this.dispatchEvent(t))return;const i=++this._lifecycleToken;this.removeAttribute("is-open"),this.isOpen=!1,this._overlay.close(),this.updateComplete.then(()=>{this.isOpen||this._lifecycleToken!==i||this.dispatchEvent(new CustomEvent(c.DIALOG_CLOSED,{detail:{reason:e},bubbles:!0,composed:!0}))})}else{const e=++this._lifecycleToken;this.setAttribute("is-open","true"),this.isOpen=!0,this._overlay.open(),this.updateComplete.then(()=>{!this.isOpen||this._lifecycleToken!==e||(this._moveFocusIntoDialog(),this.dispatchEvent(new CustomEvent(c.DIALOG_OPENED,{detail:{},bubbles:!0,composed:!0})))})}}_handleKeyDown(e){if(this.isOpen){if(e.key==="Escape"){e.preventDefault(),this._closeReason="escape",this.toggleOpen();return}e.key==="Tab"&&this._overlay.trapFocus(e)}}_moveFocusIntoDialog(){this._overlay.moveFocusInto()}firstUpdated(){this.dialogSlot.value&&this.dialogSlot.value.addEventListener("esp-form-dialog-close-requested",e=>{this._closeReason="close-dialog",this.toggleOpen(),e.preventDefault()})}disconnectedCallback(){super.disconnectedCallback()}render(){const{isOpen:e,fullScreen:t}=this,i={"is-open":e,"full-screen":t};return m`<div
2
2
  ${h(this.vellumRef)}
3
- class="vellum ${k(o)}"
3
+ class="vellum ${x(i)}"
4
4
  data-no-swipe
5
- role=${e?"dialog":u}
6
- aria-modal=${e?"true":u}
5
+ role=${e?"dialog":d}
6
+ aria-modal=${e?"true":d}
7
7
  tabindex="-1"
8
8
  @keydown=${this._handleKeyDown}
9
9
  >
10
10
  <slot ${h(this.dialogSlot)}></slot>
11
- </div>`}};s.styles=[f(3),v(":host"),b`
11
+ </div>`}};o.styles=[v(3),f(":host"),b`
12
12
  :host {
13
13
  display: block;
14
14
  }
@@ -20,12 +20,21 @@ var c=function(l,e,t,o){var n=arguments.length,i=n<3?e:o===null?o=Object.getOwnP
20
20
  visibility: hidden;
21
21
  position: fixed;
22
22
  z-index: 10000;
23
- left: 0;
24
- top: 0;
23
+ inset-inline-start: var(--esp-dialog-inset-inline-start, 0px);
24
+ inset-block-start: var(--esp-dialog-inset-block-start, 0px);
25
25
 
26
- height: 100vh;
27
- height: 100dvh;
28
- width: 100vw;
26
+ width: calc(
27
+ 100vw - var(--esp-dialog-inset-inline-start, 0px) - var(
28
+ --esp-dialog-inset-inline-end,
29
+ 0px
30
+ )
31
+ );
32
+ height: calc(
33
+ 100vh - var(--esp-dialog-inset-block-start, 0px) - var(--esp-dialog-inset-block-end, 0px)
34
+ );
35
+ height: calc(
36
+ 100dvh - var(--esp-dialog-inset-block-start, 0px) - var(--esp-dialog-inset-block-end, 0px)
37
+ );
29
38
  overflow-y: auto;
30
39
  outline: none;
31
40
 
@@ -34,11 +43,18 @@ var c=function(l,e,t,o){var n=arguments.length,i=n<3?e:o===null?o=Object.getOwnP
34
43
  position: relative;
35
44
  }
36
45
 
37
- &::before {
46
+ &::before,
47
+ &::after {
38
48
  content: "";
39
49
  display: block;
40
50
  position: fixed;
41
- inset: 0;
51
+ inset-inline: var(--esp-dialog-inset-inline-start, 0px)
52
+ var(--esp-dialog-inset-inline-end, 0px);
53
+ inset-block: var(--esp-dialog-inset-block-start, 0px)
54
+ var(--esp-dialog-inset-block-end, 0px);
55
+ }
56
+
57
+ &::before {
42
58
  opacity: var(--esp-dialog-bg-opacity, var(--esp-vellum-opacity, 0.85));
43
59
  background: var(
44
60
  --esp-color-dialog-bg,
@@ -48,10 +64,6 @@ var c=function(l,e,t,o){var n=arguments.length,i=n<3?e:o===null?o=Object.getOwnP
48
64
  }
49
65
 
50
66
  &::after {
51
- content: "";
52
- display: block;
53
- position: fixed;
54
- inset: 0;
55
67
  background-image: var(--esp-vellum-background-image, none);
56
68
  background-repeat: repeat;
57
69
  opacity: var(--esp-vellum-background-image-opacity, 0.3);
@@ -74,4 +86,4 @@ var c=function(l,e,t,o){var n=arguments.length,i=n<3?e:o===null?o=Object.getOwnP
74
86
  }
75
87
  }
76
88
  }
77
- `],c([O()],s.prototype,"isOpen",void 0),c([_({attribute:"full-screen",type:Boolean})],s.prototype,"fullScreen",void 0),s=c([y("esp-dialog")],s);export{s as EspalierDialog};
89
+ `],p([_()],o.prototype,"isOpen",void 0),p([k({attribute:"full-screen",type:Boolean})],o.prototype,"fullScreen",void 0),o=p([y("esp-dialog")],o);export{o as EspalierDialog};
@@ -469,6 +469,22 @@
469
469
  "name": "--esp-dialog-bg-opacity",
470
470
  "description": "The opacity of the dialog overlay background."
471
471
  },
472
+ {
473
+ "name": "--esp-dialog-inset-block-end",
474
+ "description": "The bottom edge of the area the dialog covers, as a length from the viewport bottom. Defaults to `0px`."
475
+ },
476
+ {
477
+ "name": "--esp-dialog-inset-block-start",
478
+ "description": "The top edge of the area the dialog covers, as a length from the viewport top. Defaults to `0px`."
479
+ },
480
+ {
481
+ "name": "--esp-dialog-inset-inline-end",
482
+ "description": "The trailing edge (right in left-to-right text) of the area the dialog covers, as a length from the viewport edge. Defaults to `0px`."
483
+ },
484
+ {
485
+ "name": "--esp-dialog-inset-inline-start",
486
+ "description": "The leading edge (left in left-to-right text) of the area the dialog covers, as a length from the viewport edge. Defaults to `0px`."
487
+ },
472
488
  {
473
489
  "name": "--esp-field-background",
474
490
  "description": "Background color of the shared field shell. Defaults to one layer above the nearest declared surface, capped at `var(--esp-color-layer-4)`, or `var(--esp-color-layer-2)` without a declared surface. An explicit override wins."
@@ -633,6 +633,30 @@
633
633
  "component": "esp-dialog",
634
634
  "description": "The opacity of the dialog overlay background."
635
635
  },
636
+ {
637
+ "name": "--esp-dialog-inset-block-end",
638
+ "category": "component",
639
+ "component": "esp-dialog",
640
+ "description": "The bottom edge of the area the dialog covers, as a length from the viewport bottom. Defaults to `0px`."
641
+ },
642
+ {
643
+ "name": "--esp-dialog-inset-block-start",
644
+ "category": "component",
645
+ "component": "esp-dialog",
646
+ "description": "The top edge of the area the dialog covers, as a length from the viewport top. Defaults to `0px`."
647
+ },
648
+ {
649
+ "name": "--esp-dialog-inset-inline-end",
650
+ "category": "component",
651
+ "component": "esp-dialog",
652
+ "description": "The trailing edge (right in left-to-right text) of the area the dialog covers, as a length from the viewport edge. Defaults to `0px`."
653
+ },
654
+ {
655
+ "name": "--esp-dialog-inset-inline-start",
656
+ "category": "component",
657
+ "component": "esp-dialog",
658
+ "description": "The leading edge (left in left-to-right text) of the area the dialog covers, as a length from the viewport edge. Defaults to `0px`."
659
+ },
636
660
  {
637
661
  "name": "--esp-field-background",
638
662
  "category": "shared-hook",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taprootio/espalier",
3
- "version": "6.3.0",
3
+ "version": "6.4.0",
4
4
  "packageManager": "bun@1.3.12",
5
5
  "description": "Espalier — a themeable, accessible, framework-agnostic, enterprise-grade design system built on web standards and love.",
6
6
  "customElements": "custom-elements.json",