@iyulab/desktop-patterns 0.5.0 → 0.6.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/README.md CHANGED
@@ -51,7 +51,7 @@ document.querySelector<HTMLElementTagNameMap['dp-sidebar']>('#nav')!.items = [
51
51
  { id: 'overview', icon: '■', label: 'Overview' },
52
52
  ]
53
53
 
54
- import { desktopMinWidth } from '@iyulab/desktop-patterns'
54
+ import { desktopMinWidth } from '@iyulab/desktop-patterns/breakpoints' // no elements registered
55
55
 
56
56
  const shell = document.querySelector('dp-shell')!
57
57
  const sidebar = document.querySelector('dp-sidebar')!
@@ -66,7 +66,9 @@ function sync() {
66
66
  wide.addEventListener('change', sync)
67
67
  sidebar.addEventListener('dp-sidebar-select', () => {
68
68
  // consumer owns activeId / routing
69
- shell.sidebarOpen = false // a drawer gives way to what was picked
69
+ })
70
+ sidebar.addEventListener('dp-sidebar-activate', () => {
71
+ shell.sidebarOpen = false // a drawer gives way to what was picked — the place already shown included
70
72
  sync()
71
73
  })
72
74
  toolbar.addEventListener('dp-toolbar-toggle', () => {
@@ -85,6 +87,23 @@ collapsed rail (`dp-sidebar` `collapsed`). Give the toggle `expanded` so assisti
85
87
  which state it is in. The consumer owns both states — start the drawer closed, close it when the
86
88
  shell asks (backdrop click, Escape) and when a place is picked, or a narrow window stays covered.
87
89
 
90
+ ### A list beside the item picked from it
91
+
92
+ ```html
93
+ <dp-page fill max-width="full">
94
+ <dp-list-detail>
95
+ <nav slot="list" aria-label="People">…</nav>
96
+ <article>…the person picked…</article>
97
+ </dp-list-detail>
98
+ </dp-page>
99
+ ```
100
+
101
+ `dp-list-detail` puts the `list` slot beside the default slot, each scrolling on its own, the list
102
+ `--dp-list-detail-list-width` wide (by default the sidebar's width — navigation, list and item read as
103
+ 1:1:rest). Below the desktop breakpoint there is room for one pane: the item while `detail-open` is
104
+ set, the list otherwise. The consumer owns `detail-open` — set it when an item is picked, and give
105
+ the item a way back that clears it, shown only below `desktopMinWidth`.
106
+
88
107
  ### Sidebar items: links, groups, icons, actions
89
108
 
90
109
  ```ts
@@ -108,6 +127,9 @@ nav.setAttribute('nav-label', 'Main') // names t
108
127
  items show, separated by a rule.
109
128
  - **Icons**: each item's icon is a slot named `icon-<id>` whose fallback is the item's `icon` text — slot any icon
110
129
  element (`<svg slot="icon-overview">`) to replace it.
130
+ - **`dp-sidebar-activate`** fires on every pick of a navigation item, the active one included (after
131
+ `dp-sidebar-select` when the place changes; not when that was cancelled). Close a drawer on it: picking the
132
+ place already shown still means "take me there", and `dp-sidebar-select` does not fire for it.
111
133
  - **Bottom items are actions**: each click fires `dp-sidebar-action` (`itemId`) — every time, so a failed action can
112
134
  be retried — and never moves `aria-current`. (In 0.2.x they fired `dp-sidebar-select` and took the active mark.)
113
135
 
@@ -125,7 +147,7 @@ import '@iyulab/desktop-patterns/tokens.css'
125
147
 
126
148
  Color/spacing/radius/typography all come from `desktop-compact`'s `--dc-*` tokens — this package adds
127
149
  only the structural dimensions `desktop-compact` has no concept of (`--dp-sidebar-width`,
128
- `--dp-sidebar-width-collapsed`, `--dp-header-height`). Every component ships with sane fallback
150
+ `--dp-sidebar-width-collapsed`, `--dp-header-height`, `--dp-list-detail-list-width`). Every component ships with sane fallback
129
151
  values, so it still renders correctly even without either stylesheet.
130
152
 
131
153
  ## Platform boundary — drag regions
@@ -147,11 +169,12 @@ dp-toolbar[drag-region]::part(drag-handle) {
147
169
  `toolbar`, so ownership of the drag handle stays entirely with whatever renders there (typically
148
170
  `dp-toolbar`).
149
171
 
150
- ## Components (v1 — complete, 5/5)
172
+ ## Components
151
173
 
152
174
  | Component | Description |
153
175
  |---|---|
154
176
  | `dp-page` | Scrollable content region with a centered, max-width column — the page-body every view renders into. With `fill` it does not scroll and its column is as tall as the region, for a view that scrolls its own parts (a list beside the item open) |
177
+ | `dp-list-detail` | A list beside the item picked from it (master-detail) — each pane scrolls on its own; one pane at a time below 1024px, the item while `detail-open` is set |
155
178
  | `dp-sidebar` | App-shell navigation rail — expanded/collapsed states, an optional pinned bottom group, a `footer` slot for whatever a consumer wants to anchor there |
156
179
  | `dp-toolbar` | App-shell header bar — `heading`/`subtitle`, an optional sidebar toggle (drawer in a narrow window, collapse in a wide one), a right-side `actions` slot |
157
180
  | `dp-shell` | Top-level layout composing `sidebar`/`toolbar`/`banner`/main-content regions, with a responsive drawer (backdrop + overlay sidebar) below a 1024px breakpoint |
@@ -0,0 +1,5 @@
1
+ import { unsafeCSS as e } from "lit";
2
+ //#region src/breakpoints.ts
3
+ var t = 1024, n = e(`(min-width: ${t}px)`);
4
+ //#endregion
5
+ export { n as desktopMedia, t as desktopMinWidth };
@@ -0,0 +1,25 @@
1
+ import { LitElement } from 'lit';
2
+ /**
3
+ * A list beside the item picked from it — the master-detail view of mail, files or records. The
4
+ * `list` slot holds what to pick from, the default slot what was picked. Each pane scrolls on its
5
+ * own, so the list stays where it was while the item is read.
6
+ *
7
+ * At desktop width the list sits at the start, `--dp-list-detail-list-width` wide (by default the
8
+ * sidebar's width, so navigation and list line up as equal columns), and the item takes the rest.
9
+ * Below it there is room for one pane: the item while `detail-open` is set, the list otherwise. The
10
+ * consumer owns `detail-open` — set it when an item is picked and clear it to go back to the list
11
+ * (a back control in the item, which it shows only while narrow).
12
+ *
13
+ * Give the view a definite height — inside `dp-page fill`, or any box with one.
14
+ */
15
+ export declare class DpListDetail extends LitElement {
16
+ static styles: import('lit').CSSResult;
17
+ /** Below desktop width, show the picked item instead of the list. Above it both are always shown. */
18
+ detailOpen: boolean;
19
+ render(): import('lit-html').TemplateResult<1>;
20
+ }
21
+ declare global {
22
+ interface HTMLElementTagNameMap {
23
+ 'dp-list-detail': DpListDetail;
24
+ }
25
+ }
@@ -13,7 +13,9 @@ export declare class DpShortcutOverlayDismissEvent extends Event {
13
13
  * `shift+/` shortcut and read from a module-level, framework-hook-specific
14
14
  * registry; both are the consumer's responsibility here (see
15
15
  * docs/superpowers/specs/2026-09-02-desktop-patterns-design.md, the
16
- * dp-shortcut-overlay row). This component only dismisses itself (outside
16
+ * dp-shortcut-overlay row). While open it is a modal dialog: focus moves to
17
+ * its close button, Tab stays inside it, and focus goes back where it was once
18
+ * it closes. This component only dismisses itself (outside
17
19
  * click / Escape) by asking — it dispatches `dp-shortcut-overlay-dismiss`
18
20
  * rather than setting its own `open` to false, since `open` is the
19
21
  * consumer's state to own, not this component's.
@@ -32,6 +32,16 @@ export declare class DpSidebarSelectEvent extends Event {
32
32
  readonly itemId: string;
33
33
  constructor(itemId: string);
34
34
  }
35
+ /**
36
+ * Fired whenever a navigation item is picked, the active one included — after `dp-sidebar-select`
37
+ * when the place changes, alone when the item picked is the one already active. Not cancelable.
38
+ * It is the moment a drawer holding the sidebar gives way: picking the place already shown still
39
+ * means "take me there".
40
+ */
41
+ export declare class DpSidebarActivateEvent extends Event {
42
+ readonly itemId: string;
43
+ constructor(itemId: string);
44
+ }
35
45
  /** Fired on every click of a bottom item — those are actions, not places. */
36
46
  export declare class DpSidebarActionEvent extends Event {
37
47
  readonly itemId: string;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './breakpoints.js';
2
2
  export * from './components/page/dp-page.js';
3
+ export * from './components/list-detail/dp-list-detail.js';
3
4
  export * from './components/sidebar/dp-sidebar.js';
4
5
  export * from './components/toolbar/dp-toolbar.js';
5
6
  export * from './components/shell/dp-shell.js';
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
- import { i as e, n as t, r as n, t as r } from "./dp-shell-CvV92B8B.js";
2
- import { DpPage as i } from "./page.js";
3
- import { DpSidebar as a, DpSidebarActionEvent as o, DpSidebarSelectEvent as s } from "./sidebar.js";
1
+ import { desktopMedia as e, desktopMinWidth as t } from "./breakpoints.js";
2
+ import { DpPage as n } from "./page.js";
3
+ import { DpListDetail as r } from "./list-detail.js";
4
+ import { DpSidebar as i, DpSidebarActionEvent as a, DpSidebarActivateEvent as o, DpSidebarSelectEvent as s } from "./sidebar.js";
4
5
  import { DpToolbar as c, DpToolbarToggleEvent as l } from "./toolbar.js";
5
- import { DpShortcutOverlay as u, DpShortcutOverlayDismissEvent as d } from "./shortcut-overlay.js";
6
- export { i as DpPage, r as DpShell, t as DpShellSidebarCloseEvent, u as DpShortcutOverlay, d as DpShortcutOverlayDismissEvent, a as DpSidebar, o as DpSidebarActionEvent, s as DpSidebarSelectEvent, c as DpToolbar, l as DpToolbarToggleEvent, n as desktopMedia, e as desktopMinWidth };
6
+ import { DpShell as u, DpShellSidebarCloseEvent as d } from "./shell.js";
7
+ import { DpShortcutOverlay as f, DpShortcutOverlayDismissEvent as p } from "./shortcut-overlay.js";
8
+ export { r as DpListDetail, n as DpPage, u as DpShell, d as DpShellSidebarCloseEvent, f as DpShortcutOverlay, p as DpShortcutOverlayDismissEvent, i as DpSidebar, a as DpSidebarActionEvent, o as DpSidebarActivateEvent, s as DpSidebarSelectEvent, c as DpToolbar, l as DpToolbarToggleEvent, e as desktopMedia, t as desktopMinWidth };
@@ -0,0 +1,65 @@
1
+ import { desktopMedia as e } from "./breakpoints.js";
2
+ import { t } from "./decorate-CafwLXg5.js";
3
+ import { LitElement as n, css as r, html as i } from "lit";
4
+ import { customElement as a, property as o } from "lit/decorators.js";
5
+ //#region src/components/list-detail/dp-list-detail.ts
6
+ var s = class extends n {
7
+ constructor(...e) {
8
+ super(...e), this.detailOpen = !1;
9
+ }
10
+ static {
11
+ this.styles = r`
12
+ :host {
13
+ display: flex;
14
+ flex: 1;
15
+ min-height: 0;
16
+ height: 100%;
17
+ box-sizing: border-box;
18
+ }
19
+ .list,
20
+ .detail {
21
+ min-width: 0;
22
+ min-height: 0;
23
+ overflow: auto;
24
+ box-sizing: border-box;
25
+ }
26
+ .list {
27
+ flex: 1;
28
+ }
29
+ .detail {
30
+ flex: 1;
31
+ display: none;
32
+ }
33
+ :host([detail-open]) .list {
34
+ display: none;
35
+ }
36
+ :host([detail-open]) .detail {
37
+ display: block;
38
+ }
39
+ @media ${e} {
40
+ :host([detail-open]) .list,
41
+ .list {
42
+ display: block;
43
+ flex: 0 0 var(--dp-list-detail-list-width, var(--dp-sidebar-width, 220px));
44
+ border-inline-end: 1px solid var(--dc-color-border, #e2e2e4);
45
+ }
46
+ .detail {
47
+ display: block;
48
+ }
49
+ }
50
+ `;
51
+ }
52
+ render() {
53
+ return i`
54
+ <div class="list" part="list"><slot name="list"></slot></div>
55
+ <div class="detail" part="detail"><slot></slot></div>
56
+ `;
57
+ }
58
+ };
59
+ t([o({
60
+ type: Boolean,
61
+ reflect: !0,
62
+ attribute: "detail-open"
63
+ })], s.prototype, "detailOpen", void 0), s = t([a("dp-list-detail")], s);
64
+ //#endregion
65
+ export { s as DpListDetail };
package/dist/shell.js CHANGED
@@ -1,2 +1,109 @@
1
- import { n as e, t } from "./dp-shell-CvV92B8B.js";
2
- export { t as DpShell, e as DpShellSidebarCloseEvent };
1
+ import { desktopMedia as e } from "./breakpoints.js";
2
+ import { t } from "./decorate-CafwLXg5.js";
3
+ import { LitElement as n, css as r, html as i, nothing as a } from "lit";
4
+ import { customElement as o, property as s } from "lit/decorators.js";
5
+ //#region src/components/shell/dp-shell.ts
6
+ var c = class extends Event {
7
+ constructor() {
8
+ super("dp-shell-sidebar-close", {
9
+ bubbles: !0,
10
+ composed: !0
11
+ });
12
+ }
13
+ }, l = class extends n {
14
+ constructor(...e) {
15
+ super(...e), this.sidebarOpen = !1, this.onKeydown = (e) => {
16
+ e.key === "Escape" && !e.defaultPrevented && this.sidebarOpen && this.dispatchEvent(new c());
17
+ };
18
+ }
19
+ static {
20
+ this.styles = r`
21
+ :host {
22
+ display: flex;
23
+ width: 100vw;
24
+ height: 100vh;
25
+ overflow: hidden;
26
+ box-sizing: border-box;
27
+ background: var(--dc-color-bg, #ffffff);
28
+ color: var(--dc-color-text, #1a1a1e);
29
+ font-family: var(--dc-font-family, system-ui, sans-serif);
30
+ }
31
+ .backdrop {
32
+ position: fixed;
33
+ inset: 0;
34
+ z-index: 30;
35
+ background: var(--dc-color-backdrop, rgba(0, 0, 0, 0.4));
36
+ }
37
+ .sidebar-region {
38
+ flex-shrink: 0;
39
+ display: none;
40
+ }
41
+ :host([sidebar-open]) .sidebar-region {
42
+ display: block;
43
+ position: fixed;
44
+ inset-block: 0;
45
+ inset-inline-start: 0;
46
+ z-index: 40;
47
+ }
48
+ @media ${e} {
49
+ /* Must match (or exceed) \`:host([sidebar-open]) .sidebar-region\`'s
50
+ specificity above (0,3,0) — a bare \`.sidebar-region\` here is only
51
+ (0,1,0), so on equal-or-narrower cascades the desktop override would
52
+ lose to the sidebar-open rule regardless of this media query
53
+ matching, leaving the sidebar permanently \`position: fixed\` (full-
54
+ height overlay) at any width once \`sidebar-open\` is set. Repeating
55
+ the \`:host([sidebar-open])\` prefix here, rather than only the bare
56
+ selector, is what actually lets desktop width win. */
57
+ :host([sidebar-open]) .sidebar-region,
58
+ .sidebar-region {
59
+ display: block;
60
+ position: relative;
61
+ inset: auto;
62
+ z-index: auto;
63
+ }
64
+ .backdrop {
65
+ display: none;
66
+ }
67
+ }
68
+ .main-column {
69
+ display: flex;
70
+ flex-direction: column;
71
+ flex: 1;
72
+ min-width: 0;
73
+ }
74
+ .toolbar-row {
75
+ flex-shrink: 0;
76
+ }
77
+ main {
78
+ flex: 1;
79
+ overflow: auto;
80
+ }
81
+ `;
82
+ }
83
+ connectedCallback() {
84
+ super.connectedCallback(), document.addEventListener("keydown", this.onKeydown);
85
+ }
86
+ disconnectedCallback() {
87
+ document.removeEventListener("keydown", this.onKeydown), super.disconnectedCallback();
88
+ }
89
+ render() {
90
+ return i`
91
+ ${this.sidebarOpen ? i`<div class="backdrop" @click=${() => this.dispatchEvent(new c())}></div>` : a}
92
+ <div class="sidebar-region"><slot name="sidebar"></slot></div>
93
+ <div class="main-column">
94
+ <div class="toolbar-row">
95
+ <slot name="toolbar"></slot>
96
+ </div>
97
+ <slot name="banner"></slot>
98
+ <main><slot></slot></main>
99
+ </div>
100
+ `;
101
+ }
102
+ };
103
+ t([s({
104
+ type: Boolean,
105
+ reflect: !0,
106
+ attribute: "sidebar-open"
107
+ })], l.prototype, "sidebarOpen", void 0), l = t([o("dp-shell")], l);
108
+ //#endregion
109
+ export { l as DpShell, c as DpShellSidebarCloseEvent };
@@ -11,8 +11,8 @@ var c = class extends Event {
11
11
  }
12
12
  }, l = 0, u = class extends t {
13
13
  constructor(...e) {
14
- super(...e), this.shortcuts = [], this.open = !1, this.heading = "Keyboard shortcuts", this.emptyMessage = "No shortcuts registered.", this.closeLabel = "Close", this.headingId = `dp-shortcut-overlay-heading-${++l}`, this.#e = (e) => {
15
- e.key === "Escape" && (e.preventDefault(), this.#t());
14
+ super(...e), this.shortcuts = [], this.open = !1, this.heading = "Keyboard shortcuts", this.emptyMessage = "No shortcuts registered.", this.closeLabel = "Close", this.headingId = `dp-shortcut-overlay-heading-${++l}`, this.#t = (e) => {
15
+ e.key === "Escape" ? (e.preventDefault(), this.#r()) : e.key === "Tab" && (e.preventDefault(), this.#n()?.focus());
16
16
  };
17
17
  }
18
18
  static {
@@ -104,18 +104,22 @@ var c = class extends Event {
104
104
  `;
105
105
  }
106
106
  #e;
107
+ #t;
108
+ #n() {
109
+ return this.renderRoot.querySelector(".close");
110
+ }
107
111
  updated(e) {
108
- e.has("open") && (this.open ? window.addEventListener("keydown", this.#e) : window.removeEventListener("keydown", this.#e));
112
+ e.has("open") && (this.open ? (window.addEventListener("keydown", this.#t), this.#e = d(), this.#n()?.focus()) : (window.removeEventListener("keydown", this.#t), e.get("open") === !0 && this.#e?.focus(), this.#e = void 0));
109
113
  }
110
114
  disconnectedCallback() {
111
- super.disconnectedCallback(), window.removeEventListener("keydown", this.#e);
115
+ super.disconnectedCallback(), window.removeEventListener("keydown", this.#t);
112
116
  }
113
- #t() {
117
+ #r() {
114
118
  this.dispatchEvent(new c());
115
119
  }
116
120
  render() {
117
121
  return this.open ? r`
118
- <div class="backdrop" @click=${() => this.#t()}>
122
+ <div class="backdrop" @click=${() => this.#r()}>
119
123
  <div
120
124
  class="panel"
121
125
  role="dialog"
@@ -125,7 +129,7 @@ var c = class extends Event {
125
129
  >
126
130
  <div class="header">
127
131
  <h2 id=${this.headingId}>${this.heading}</h2>
128
- <button class="close" aria-label=${this.closeLabel} @click=${() => this.#t()}>×</button>
132
+ <button class="close" aria-label=${this.closeLabel} @click=${() => this.#r()}>×</button>
129
133
  </div>
130
134
  <div class="body">
131
135
  ${this.shortcuts.length === 0 ? r`<p class="empty">${this.emptyMessage}</p>` : r`
@@ -148,5 +152,10 @@ e([o({ type: Array })], u.prototype, "shortcuts", void 0), e([o({
148
152
  type: Boolean,
149
153
  reflect: !0
150
154
  })], u.prototype, "open", void 0), e([o()], u.prototype, "heading", void 0), e([o({ attribute: "empty-message" })], u.prototype, "emptyMessage", void 0), e([o({ attribute: "close-label" })], u.prototype, "closeLabel", void 0), e([s()], u.prototype, "headingId", void 0), u = e([a("dp-shortcut-overlay")], u);
155
+ function d() {
156
+ let e = document.activeElement;
157
+ for (; e?.shadowRoot?.activeElement;) e = e.shadowRoot.activeElement;
158
+ return e instanceof HTMLElement && e !== document.body ? e : void 0;
159
+ }
151
160
  //#endregion
152
161
  export { u as DpShortcutOverlay, c as DpShortcutOverlayDismissEvent };
package/dist/sidebar.js CHANGED
@@ -11,13 +11,20 @@ var c = (e) => Array.isArray(e.items), l = class extends Event {
11
11
  }), this.itemId = e;
12
12
  }
13
13
  }, u = class extends Event {
14
+ constructor(e) {
15
+ super("dp-sidebar-activate", {
16
+ bubbles: !0,
17
+ composed: !0
18
+ }), this.itemId = e;
19
+ }
20
+ }, d = class extends Event {
14
21
  constructor(e) {
15
22
  super("dp-sidebar-action", {
16
23
  bubbles: !0,
17
24
  composed: !0
18
25
  }), this.itemId = e;
19
26
  }
20
- }, d = class extends t {
27
+ }, f = class extends t {
21
28
  constructor(...e) {
22
29
  super(...e), this.items = [], this.bottomItems = [], this.activeId = "", this.header = "", this.navLabel = "", this.collapsed = !1, this.toggledGroups = /* @__PURE__ */ new Set();
23
30
  }
@@ -194,7 +201,7 @@ var c = (e) => Array.isArray(e.items), l = class extends Event {
194
201
  <button
195
202
  class="item"
196
203
  title=${this.collapsed ? e.label : i}
197
- @click=${() => this.dispatchEvent(new u(e.id))}
204
+ @click=${() => this.dispatchEvent(new d(e.id))}
198
205
  >
199
206
  ${this.#r(e.id, e.icon)} ${this.collapsed ? i : r`<span class="label">${e.label}</span>`}
200
207
  </button>
@@ -204,12 +211,15 @@ var c = (e) => Array.isArray(e.items), l = class extends Event {
204
211
  return r`<span class="icon" aria-hidden="true"><slot name=${`icon-${e}`}>${t}</slot></span>`;
205
212
  }
206
213
  #i(e, t) {
207
- if (!(t.href !== void 0 && (e.button !== 0 || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey)) && t.id !== this.activeId) {
208
- if (!this.dispatchEvent(new l(t.id))) {
209
- e.preventDefault();
210
- return;
214
+ if (!(t.href !== void 0 && (e.button !== 0 || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey))) {
215
+ if (t.id !== this.activeId) {
216
+ if (!this.dispatchEvent(new l(t.id))) {
217
+ e.preventDefault();
218
+ return;
219
+ }
220
+ this.activeId = t.id;
211
221
  }
212
- this.activeId = t.id;
222
+ this.dispatchEvent(new u(t.id));
213
223
  }
214
224
  }
215
225
  #a(e) {
@@ -217,12 +227,12 @@ var c = (e) => Array.isArray(e.items), l = class extends Event {
217
227
  t.delete(e) || t.add(e), this.toggledGroups = t;
218
228
  }
219
229
  };
220
- e([o({ type: Array })], d.prototype, "items", void 0), e([o({
230
+ e([o({ type: Array })], f.prototype, "items", void 0), e([o({
221
231
  type: Array,
222
232
  attribute: "bottom-items"
223
- })], d.prototype, "bottomItems", void 0), e([o({ attribute: "active-id" })], d.prototype, "activeId", void 0), e([o()], d.prototype, "header", void 0), e([o({ attribute: "nav-label" })], d.prototype, "navLabel", void 0), e([o({
233
+ })], f.prototype, "bottomItems", void 0), e([o({ attribute: "active-id" })], f.prototype, "activeId", void 0), e([o()], f.prototype, "header", void 0), e([o({ attribute: "nav-label" })], f.prototype, "navLabel", void 0), e([o({
224
234
  type: Boolean,
225
235
  reflect: !0
226
- })], d.prototype, "collapsed", void 0), e([s()], d.prototype, "toggledGroups", void 0), d = e([a("dp-sidebar")], d);
236
+ })], f.prototype, "collapsed", void 0), e([s()], f.prototype, "toggledGroups", void 0), f = e([a("dp-sidebar")], f);
227
237
  //#endregion
228
- export { d as DpSidebar, u as DpSidebarActionEvent, l as DpSidebarSelectEvent };
238
+ export { f as DpSidebar, d as DpSidebarActionEvent, u as DpSidebarActivateEvent, l as DpSidebarSelectEvent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/desktop-patterns",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,11 +25,21 @@
25
25
  "import": "./dist/index.js",
26
26
  "default": "./dist/index.js"
27
27
  },
28
+ "./breakpoints": {
29
+ "types": "./dist/breakpoints.d.ts",
30
+ "import": "./dist/breakpoints.js",
31
+ "default": "./dist/breakpoints.js"
32
+ },
28
33
  "./page": {
29
34
  "types": "./dist/components/page/dp-page.d.ts",
30
35
  "import": "./dist/page.js",
31
36
  "default": "./dist/page.js"
32
37
  },
38
+ "./list-detail": {
39
+ "types": "./dist/components/list-detail/dp-list-detail.d.ts",
40
+ "import": "./dist/list-detail.js",
41
+ "default": "./dist/list-detail.js"
42
+ },
33
43
  "./sidebar": {
34
44
  "types": "./dist/components/sidebar/dp-sidebar.d.ts",
35
45
  "import": "./dist/sidebar.js",
@@ -77,6 +87,7 @@
77
87
  "@types/mocha": "^10.0.10",
78
88
  "@web/dev-server-esbuild": "^2.0.0",
79
89
  "@web/test-runner": "^1.0.0",
90
+ "@web/test-runner-commands": "^1.0.1",
80
91
  "axe-core": "^4.13.0",
81
92
  "storybook": "^10.6.0",
82
93
  "typescript": "^6.0.3",
package/tokens.css CHANGED
@@ -11,4 +11,6 @@
11
11
  --dp-sidebar-width: 220px;
12
12
  --dp-sidebar-width-collapsed: 52px;
13
13
  --dp-header-height: 52px;
14
+ /* dp-list-detail's list pane: as wide as the sidebar, so navigation and list are equal columns. */
15
+ --dp-list-detail-list-width: var(--dp-sidebar-width);
14
16
  }
@@ -1,108 +0,0 @@
1
- import { t as e } from "./decorate-CafwLXg5.js";
2
- import { LitElement as t, css as n, html as r, nothing as i, unsafeCSS as a } from "lit";
3
- import { customElement as o, property as s } from "lit/decorators.js";
4
- //#region src/breakpoints.ts
5
- var c = 1024, l = a(`(min-width: ${c}px)`), u = class extends Event {
6
- constructor() {
7
- super("dp-shell-sidebar-close", {
8
- bubbles: !0,
9
- composed: !0
10
- });
11
- }
12
- }, d = class extends t {
13
- constructor(...e) {
14
- super(...e), this.sidebarOpen = !1, this.onKeydown = (e) => {
15
- e.key === "Escape" && !e.defaultPrevented && this.sidebarOpen && this.dispatchEvent(new u());
16
- };
17
- }
18
- static {
19
- this.styles = n`
20
- :host {
21
- display: flex;
22
- width: 100vw;
23
- height: 100vh;
24
- overflow: hidden;
25
- box-sizing: border-box;
26
- background: var(--dc-color-bg, #ffffff);
27
- color: var(--dc-color-text, #1a1a1e);
28
- font-family: var(--dc-font-family, system-ui, sans-serif);
29
- }
30
- .backdrop {
31
- position: fixed;
32
- inset: 0;
33
- z-index: 30;
34
- background: var(--dc-color-backdrop, rgba(0, 0, 0, 0.4));
35
- }
36
- .sidebar-region {
37
- flex-shrink: 0;
38
- display: none;
39
- }
40
- :host([sidebar-open]) .sidebar-region {
41
- display: block;
42
- position: fixed;
43
- inset-block: 0;
44
- inset-inline-start: 0;
45
- z-index: 40;
46
- }
47
- @media ${l} {
48
- /* Must match (or exceed) \`:host([sidebar-open]) .sidebar-region\`'s
49
- specificity above (0,3,0) — a bare \`.sidebar-region\` here is only
50
- (0,1,0), so on equal-or-narrower cascades the desktop override would
51
- lose to the sidebar-open rule regardless of this media query
52
- matching, leaving the sidebar permanently \`position: fixed\` (full-
53
- height overlay) at any width once \`sidebar-open\` is set. Repeating
54
- the \`:host([sidebar-open])\` prefix here, rather than only the bare
55
- selector, is what actually lets desktop width win. */
56
- :host([sidebar-open]) .sidebar-region,
57
- .sidebar-region {
58
- display: block;
59
- position: relative;
60
- inset: auto;
61
- z-index: auto;
62
- }
63
- .backdrop {
64
- display: none;
65
- }
66
- }
67
- .main-column {
68
- display: flex;
69
- flex-direction: column;
70
- flex: 1;
71
- min-width: 0;
72
- }
73
- .toolbar-row {
74
- flex-shrink: 0;
75
- }
76
- main {
77
- flex: 1;
78
- overflow: auto;
79
- }
80
- `;
81
- }
82
- connectedCallback() {
83
- super.connectedCallback(), document.addEventListener("keydown", this.onKeydown);
84
- }
85
- disconnectedCallback() {
86
- document.removeEventListener("keydown", this.onKeydown), super.disconnectedCallback();
87
- }
88
- render() {
89
- return r`
90
- ${this.sidebarOpen ? r`<div class="backdrop" @click=${() => this.dispatchEvent(new u())}></div>` : i}
91
- <div class="sidebar-region"><slot name="sidebar"></slot></div>
92
- <div class="main-column">
93
- <div class="toolbar-row">
94
- <slot name="toolbar"></slot>
95
- </div>
96
- <slot name="banner"></slot>
97
- <main><slot></slot></main>
98
- </div>
99
- `;
100
- }
101
- };
102
- e([s({
103
- type: Boolean,
104
- reflect: !0,
105
- attribute: "sidebar-open"
106
- })], d.prototype, "sidebarOpen", void 0), d = e([o("dp-shell")], d);
107
- //#endregion
108
- export { c as i, u as n, l as r, d as t };