@arclux/arc-ui 1.0.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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/package.json +186 -0
  4. package/src/content/accordion-item.js +27 -0
  5. package/src/content/accordion.js +151 -0
  6. package/src/content/animated-number.js +160 -0
  7. package/src/content/aspect-ratio.js +78 -0
  8. package/src/content/avatar-group.js +101 -0
  9. package/src/content/avatar.js +92 -0
  10. package/src/content/badge.js +98 -0
  11. package/src/content/callout.js +141 -0
  12. package/src/content/card.js +75 -0
  13. package/src/content/carousel.js +300 -0
  14. package/src/content/code-block.js +152 -0
  15. package/src/content/collapsible.js +142 -0
  16. package/src/content/color-swatch.js +86 -0
  17. package/src/content/column.js +28 -0
  18. package/src/content/data-table.js +332 -0
  19. package/src/content/divider.js +153 -0
  20. package/src/content/empty-state.js +78 -0
  21. package/src/content/feature-card.js +142 -0
  22. package/src/content/highlight.js +63 -0
  23. package/src/content/icon-library.js +30 -0
  24. package/src/content/icon-registry.js +39 -0
  25. package/src/content/icon.js +95 -0
  26. package/src/content/index.js +44 -0
  27. package/src/content/infinite-scroll.js +144 -0
  28. package/src/content/kbd.js +40 -0
  29. package/src/content/markdown.js +294 -0
  30. package/src/content/marquee.js +166 -0
  31. package/src/content/meter.js +167 -0
  32. package/src/content/scroll-area.js +107 -0
  33. package/src/content/skeleton.js +85 -0
  34. package/src/content/spinner.js +77 -0
  35. package/src/content/stack.js +68 -0
  36. package/src/content/stat.js +72 -0
  37. package/src/content/step.js +22 -0
  38. package/src/content/stepper.js +202 -0
  39. package/src/content/table.js +134 -0
  40. package/src/content/tag.js +156 -0
  41. package/src/content/text.js +111 -0
  42. package/src/content/timeline-item.js +29 -0
  43. package/src/content/timeline.js +170 -0
  44. package/src/content/truncate.js +161 -0
  45. package/src/content/value-card.js +94 -0
  46. package/src/feedback/alert.js +187 -0
  47. package/src/feedback/command-item.js +28 -0
  48. package/src/feedback/command-palette.js +346 -0
  49. package/src/feedback/context-menu.js +299 -0
  50. package/src/feedback/dialog.js +298 -0
  51. package/src/feedback/dropdown-menu.js +259 -0
  52. package/src/feedback/hover-card.js +186 -0
  53. package/src/feedback/index.js +17 -0
  54. package/src/feedback/modal.js +226 -0
  55. package/src/feedback/notification-panel.js +196 -0
  56. package/src/feedback/popover.js +184 -0
  57. package/src/feedback/progress.js +169 -0
  58. package/src/feedback/sheet.js +249 -0
  59. package/src/feedback/toast.js +207 -0
  60. package/src/feedback/tooltip.js +189 -0
  61. package/src/icons/lucide.d.ts +1915 -0
  62. package/src/icons/lucide.js +1915 -0
  63. package/src/icons/phosphor.d.ts +1517 -0
  64. package/src/icons/phosphor.js +1517 -0
  65. package/src/icons/types.d.ts +8 -0
  66. package/src/index.js +9 -0
  67. package/src/input/button.js +127 -0
  68. package/src/input/calendar.js +340 -0
  69. package/src/input/checkbox.js +159 -0
  70. package/src/input/chip.js +120 -0
  71. package/src/input/color-picker.js +461 -0
  72. package/src/input/combobox.js +295 -0
  73. package/src/input/copy-button.js +144 -0
  74. package/src/input/date-picker.js +534 -0
  75. package/src/input/file-upload.js +333 -0
  76. package/src/input/form.js +179 -0
  77. package/src/input/icon-button.js +179 -0
  78. package/src/input/index.js +31 -0
  79. package/src/input/input.js +158 -0
  80. package/src/input/multi-select.js +392 -0
  81. package/src/input/number-input.js +239 -0
  82. package/src/input/otp-input.js +221 -0
  83. package/src/input/pin-input.js +294 -0
  84. package/src/input/radio-group.js +177 -0
  85. package/src/input/radio.js +28 -0
  86. package/src/input/rating.js +195 -0
  87. package/src/input/search.js +371 -0
  88. package/src/input/segmented-control.js +174 -0
  89. package/src/input/select.js +267 -0
  90. package/src/input/slider.js +217 -0
  91. package/src/input/sortable-list.js +345 -0
  92. package/src/input/suggestion.js +26 -0
  93. package/src/input/textarea.js +203 -0
  94. package/src/input/theme-toggle.js +196 -0
  95. package/src/input/toggle.js +166 -0
  96. package/src/layout/app-shell.js +266 -0
  97. package/src/layout/auth-shell.js +153 -0
  98. package/src/layout/container.js +37 -0
  99. package/src/layout/dashboard-grid.js +62 -0
  100. package/src/layout/index.js +15 -0
  101. package/src/layout/page-header.js +100 -0
  102. package/src/layout/page-layout.js +112 -0
  103. package/src/layout/resizable.js +221 -0
  104. package/src/layout/section.js +54 -0
  105. package/src/layout/settings-layout.js +91 -0
  106. package/src/layout/split-pane.js +172 -0
  107. package/src/layout/status-bar.js +84 -0
  108. package/src/layout/toolbar.js +92 -0
  109. package/src/navigation/breadcrumb-item.js +26 -0
  110. package/src/navigation/breadcrumb.js +129 -0
  111. package/src/navigation/drawer.js +183 -0
  112. package/src/navigation/footer.js +99 -0
  113. package/src/navigation/index.js +22 -0
  114. package/src/navigation/link.js +120 -0
  115. package/src/navigation/nav-item.js +46 -0
  116. package/src/navigation/navigation-menu.js +687 -0
  117. package/src/navigation/pagination.js +186 -0
  118. package/src/navigation/scroll-spy.js +198 -0
  119. package/src/navigation/scroll-to-top.js +163 -0
  120. package/src/navigation/sidebar-link.js +28 -0
  121. package/src/navigation/sidebar-section.js +45 -0
  122. package/src/navigation/sidebar.js +336 -0
  123. package/src/navigation/spy-link.js +26 -0
  124. package/src/navigation/tab.js +26 -0
  125. package/src/navigation/tabs.js +202 -0
  126. package/src/navigation/top-bar.js +263 -0
  127. package/src/navigation/tree-item.js +38 -0
  128. package/src/navigation/tree-view.js +255 -0
  129. package/src/shared/index.js +6 -0
  130. package/src/shared/menu-divider.js +9 -0
  131. package/src/shared/menu-item.js +30 -0
  132. package/src/shared/option.js +31 -0
  133. package/src/shared-styles.js +81 -0
  134. package/src/tokens.js +445 -0
  135. package/types/index.d.ts +973 -0
@@ -0,0 +1,184 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { tokenStyles } from '../shared-styles.js';
3
+
4
+ export class ArcPopover extends LitElement {
5
+ static properties = {
6
+ open: { type: Boolean, reflect: true },
7
+ position: { type: String, reflect: true },
8
+ trigger: { type: String },
9
+ };
10
+
11
+ static styles = [
12
+ tokenStyles,
13
+ css`
14
+ :host {
15
+ display: inline-block;
16
+ position: relative;
17
+ }
18
+
19
+ .popover__trigger {
20
+ display: inline-block;
21
+ cursor: pointer;
22
+ }
23
+
24
+ .popover__panel {
25
+ position: absolute;
26
+ z-index: 100;
27
+ min-width: 200px;
28
+ background: var(--bg-card);
29
+ border: 1px solid var(--border-default);
30
+ border-radius: var(--radius-md);
31
+ padding: var(--space-md);
32
+ box-shadow: var(--shadow-overlay);
33
+ opacity: 0;
34
+ visibility: hidden;
35
+ transform: scale(0.95);
36
+ transition:
37
+ opacity var(--transition-base),
38
+ visibility var(--transition-base),
39
+ transform var(--transition-base);
40
+ pointer-events: none;
41
+ }
42
+
43
+ :host([open]) .popover__panel {
44
+ opacity: 1;
45
+ visibility: visible;
46
+ transform: scale(1);
47
+ pointer-events: auto;
48
+ }
49
+
50
+ /* Positions */
51
+ :host(:not([position])) .popover__panel,
52
+ :host([position="bottom"]) .popover__panel {
53
+ top: calc(100% + var(--space-sm));
54
+ left: 50%;
55
+ transform: translateX(-50%) scale(0.95);
56
+ }
57
+ :host([open]:not([position])) .popover__panel,
58
+ :host([open][position="bottom"]) .popover__panel {
59
+ transform: translateX(-50%) scale(1);
60
+ }
61
+
62
+ :host([position="top"]) .popover__panel {
63
+ bottom: calc(100% + var(--space-sm));
64
+ left: 50%;
65
+ transform: translateX(-50%) scale(0.95);
66
+ }
67
+ :host([open][position="top"]) .popover__panel {
68
+ transform: translateX(-50%) scale(1);
69
+ }
70
+
71
+ :host([position="left"]) .popover__panel {
72
+ right: calc(100% + var(--space-sm));
73
+ top: 50%;
74
+ transform: translateY(-50%) scale(0.95);
75
+ }
76
+ :host([open][position="left"]) .popover__panel {
77
+ transform: translateY(-50%) scale(1);
78
+ }
79
+
80
+ :host([position="right"]) .popover__panel {
81
+ left: calc(100% + var(--space-sm));
82
+ top: 50%;
83
+ transform: translateY(-50%) scale(0.95);
84
+ }
85
+ :host([open][position="right"]) .popover__panel {
86
+ transform: translateY(-50%) scale(1);
87
+ }
88
+
89
+ @media (prefers-reduced-motion: reduce) {
90
+ :host *,
91
+ :host *::before,
92
+ :host *::after {
93
+ animation-duration: 0.01ms !important;
94
+ animation-iteration-count: 1 !important;
95
+ transition-duration: 0.01ms !important;
96
+ }
97
+ }
98
+ `,
99
+ ];
100
+
101
+ constructor() {
102
+ super();
103
+ this.open = false;
104
+ this.position = 'bottom';
105
+ this.trigger = '';
106
+ this._onDocumentClick = this._onDocumentClick.bind(this);
107
+ this._onKeyDown = this._onKeyDown.bind(this);
108
+ }
109
+
110
+ updated(changed) {
111
+ if (changed.has('open')) {
112
+ if (this.open) {
113
+ // Defer so the opening click doesn't immediately close
114
+ requestAnimationFrame(() => {
115
+ document.addEventListener('click', this._onDocumentClick);
116
+ });
117
+ document.addEventListener('keydown', this._onKeyDown);
118
+ } else {
119
+ document.removeEventListener('click', this._onDocumentClick);
120
+ document.removeEventListener('keydown', this._onKeyDown);
121
+ }
122
+ }
123
+ }
124
+
125
+ disconnectedCallback() {
126
+ super.disconnectedCallback();
127
+ document.removeEventListener('click', this._onDocumentClick);
128
+ document.removeEventListener('keydown', this._onKeyDown);
129
+ }
130
+
131
+ _onDocumentClick(e) {
132
+ const path = e.composedPath();
133
+ if (!path.includes(this)) {
134
+ this._close();
135
+ }
136
+ }
137
+
138
+ _onKeyDown(e) {
139
+ if (e.key === 'Escape') {
140
+ this._close();
141
+ }
142
+ }
143
+
144
+ _toggle() {
145
+ this.open = !this.open;
146
+ this.dispatchEvent(new CustomEvent(this.open ? 'arc-open' : 'arc-close', {
147
+ bubbles: true,
148
+ composed: true,
149
+ }));
150
+ }
151
+
152
+ _close() {
153
+ if (!this.open) return;
154
+ this.open = false;
155
+ this.dispatchEvent(new CustomEvent('arc-close', {
156
+ bubbles: true,
157
+ composed: true,
158
+ }));
159
+ }
160
+
161
+ render() {
162
+ return html`
163
+ <div
164
+ class="popover__trigger"
165
+ @click=${this._toggle}
166
+ aria-haspopup="true"
167
+ aria-expanded=${this.open ? 'true' : 'false'}
168
+ part="trigger"
169
+ >
170
+ <slot name="trigger"></slot>
171
+ </div>
172
+ <div
173
+ class="popover__panel"
174
+ role="dialog"
175
+ aria-hidden=${this.open ? 'false' : 'true'}
176
+ part="panel"
177
+ >
178
+ <slot></slot>
179
+ </div>
180
+ `;
181
+ }
182
+ }
183
+
184
+ customElements.define('arc-popover', ArcPopover);
@@ -0,0 +1,169 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { tokenStyles } from '../shared-styles.js';
3
+
4
+ export class ArcProgress extends LitElement {
5
+ static properties = {
6
+ value: { type: Number },
7
+ variant: { type: String, reflect: true },
8
+ size: { type: String, reflect: true },
9
+ indeterminate: { type: Boolean, reflect: true },
10
+ label: { type: String },
11
+ };
12
+
13
+ static styles = [
14
+ tokenStyles,
15
+ css`
16
+ :host { display: block; }
17
+
18
+ .progress__label {
19
+ font-family: var(--font-accent);
20
+ font-weight: 600;
21
+ font-size: var(--label-inline-size);
22
+ letter-spacing: var(--label-inline-spacing);
23
+ text-transform: uppercase;
24
+ color: var(--text-muted);
25
+ margin-bottom: var(--space-xs);
26
+ display: block;
27
+ }
28
+
29
+ /* Bar */
30
+ .progress__track {
31
+ width: 100%;
32
+ border-radius: var(--radius-full);
33
+ background: var(--border-subtle);
34
+ overflow: hidden;
35
+ }
36
+
37
+ :host([size="sm"]) .progress__track { height: 4px; }
38
+ :host(:not([size])) .progress__track,
39
+ :host([size="md"]) .progress__track { height: 8px; }
40
+ :host([size="lg"]) .progress__track { height: 12px; }
41
+
42
+ .progress__fill {
43
+ height: 100%;
44
+ border-radius: var(--radius-full);
45
+ background: var(--gradient-accent-text);
46
+ transition: width var(--transition-base);
47
+ box-shadow: 0 0 8px rgba(var(--accent-primary-rgb), 0.3);
48
+ }
49
+
50
+ :host([indeterminate]) .progress__fill {
51
+ width: 30% !important;
52
+ animation: progress-indeterminate 1.5s ease-in-out infinite;
53
+ }
54
+
55
+ /* Spinner */
56
+ .progress__spinner {
57
+ display: inline-block;
58
+ }
59
+
60
+ :host([size="sm"]) .progress__spinner { width: 20px; height: 20px; }
61
+ :host(:not([size])) .progress__spinner,
62
+ :host([size="md"]) .progress__spinner { width: 32px; height: 32px; }
63
+ :host([size="lg"]) .progress__spinner { width: 48px; height: 48px; }
64
+
65
+ .progress__spinner svg {
66
+ width: 100%;
67
+ height: 100%;
68
+ animation: spinner-rotate 1.4s linear infinite;
69
+ }
70
+
71
+ .progress__spinner-track {
72
+ fill: none;
73
+ stroke: var(--border-subtle);
74
+ stroke-width: 3;
75
+ }
76
+
77
+ .progress__spinner-fill {
78
+ fill: none;
79
+ stroke: var(--accent-primary);
80
+ stroke-width: 3;
81
+ stroke-linecap: round;
82
+ stroke-dasharray: 80, 200;
83
+ stroke-dashoffset: 0;
84
+ animation: spinner-dash 1.4s ease-in-out infinite;
85
+ }
86
+
87
+ @keyframes progress-indeterminate {
88
+ 0% { transform: translateX(-100%); }
89
+ 100% { transform: translateX(400%); }
90
+ }
91
+
92
+ @keyframes spinner-rotate {
93
+ 100% { transform: rotate(360deg); }
94
+ }
95
+
96
+ @keyframes spinner-dash {
97
+ 0% { stroke-dasharray: 1, 200; stroke-dashoffset: 0; }
98
+ 50% { stroke-dasharray: 80, 200; stroke-dashoffset: -35; }
99
+ 100% { stroke-dasharray: 80, 200; stroke-dashoffset: -125; }
100
+ }
101
+
102
+ @media (prefers-reduced-motion: reduce) {
103
+ .progress__fill,
104
+ .progress__spinner svg,
105
+ .progress__spinner-fill {
106
+ animation: none;
107
+ }
108
+ }
109
+ `,
110
+ ];
111
+
112
+ constructor() {
113
+ super();
114
+ this.value = 0;
115
+ this.variant = 'bar';
116
+ this.size = 'md';
117
+ this.indeterminate = false;
118
+ this.label = '';
119
+ }
120
+
121
+ render() {
122
+ const clampedValue = Math.max(0, Math.min(100, this.value));
123
+
124
+ if (this.variant === 'spinner') {
125
+ return html`
126
+ <div part="progress">
127
+ ${this.label ? html`<span class="progress__label" part="label">${this.label}</span>` : ''}
128
+ <div
129
+ class="progress__spinner"
130
+ role="progressbar"
131
+ aria-valuenow=${this.indeterminate ? undefined : clampedValue}
132
+ aria-valuemin="0"
133
+ aria-valuemax="100"
134
+ aria-label=${this.label || 'Loading'}
135
+ part="spinner"
136
+ >
137
+ <svg viewBox="0 0 44 44">
138
+ <circle class="progress__spinner-track" cx="22" cy="22" r="20"></circle>
139
+ <circle class="progress__spinner-fill" cx="22" cy="22" r="20"></circle>
140
+ </svg>
141
+ </div>
142
+ </div>
143
+ `;
144
+ }
145
+
146
+ return html`
147
+ <div part="progress">
148
+ ${this.label ? html`<span class="progress__label" part="label">${this.label}</span>` : ''}
149
+ <div
150
+ class="progress__track"
151
+ role="progressbar"
152
+ aria-valuenow=${this.indeterminate ? undefined : clampedValue}
153
+ aria-valuemin="0"
154
+ aria-valuemax="100"
155
+ aria-label=${this.label || 'Progress'}
156
+ part="track"
157
+ >
158
+ <div
159
+ class="progress__fill"
160
+ style="width: ${this.indeterminate ? '30' : clampedValue}%"
161
+ part="fill"
162
+ ></div>
163
+ </div>
164
+ </div>
165
+ `;
166
+ }
167
+ }
168
+
169
+ customElements.define('arc-progress', ArcProgress);
@@ -0,0 +1,249 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { tokenStyles } from '../shared-styles.js';
3
+
4
+ export class ArcSheet extends LitElement {
5
+ static properties = {
6
+ open: { type: Boolean, reflect: true },
7
+ side: { type: String, reflect: true },
8
+ heading: { type: String },
9
+ };
10
+
11
+ static styles = [
12
+ tokenStyles,
13
+ css`
14
+ :host { display: contents; }
15
+
16
+ .sheet__backdrop {
17
+ position: fixed;
18
+ inset: 0;
19
+ background: var(--overlay-backdrop);
20
+ backdrop-filter: blur(4px);
21
+ z-index: 1000;
22
+ opacity: 0;
23
+ visibility: hidden;
24
+ transition:
25
+ opacity var(--duration-exit) ease,
26
+ visibility var(--duration-exit) ease;
27
+ }
28
+
29
+ :host([open]) .sheet__backdrop {
30
+ opacity: 1;
31
+ visibility: visible;
32
+ transition-duration: var(--duration-enter);
33
+ }
34
+
35
+ .sheet__panel {
36
+ position: fixed;
37
+ z-index: 1001;
38
+ background: var(--bg-card);
39
+ border: 1px solid var(--border-subtle);
40
+ box-shadow: var(--shadow-overlay);
41
+ display: flex;
42
+ flex-direction: column;
43
+ box-sizing: border-box;
44
+ transition: transform var(--duration-exit) var(--ease-out-expo);
45
+ }
46
+
47
+ :host([open]) .sheet__panel {
48
+ transition-duration: var(--duration-enter);
49
+ }
50
+
51
+ /* Bottom sheet */
52
+ :host(:not([side])) .sheet__panel,
53
+ :host([side="bottom"]) .sheet__panel {
54
+ bottom: 0;
55
+ left: 0;
56
+ right: 0;
57
+ max-height: 80vh;
58
+ border-radius: var(--radius-xl) var(--radius-xl) 0 0;
59
+ transform: translateY(100%);
60
+ }
61
+
62
+ :host([open]:not([side])) .sheet__panel,
63
+ :host([open][side="bottom"]) .sheet__panel {
64
+ transform: translateY(0);
65
+ }
66
+
67
+ /* Right sheet */
68
+ :host([side="right"]) .sheet__panel {
69
+ top: 0;
70
+ right: 0;
71
+ bottom: 0;
72
+ width: 400px;
73
+ max-width: 90vw;
74
+ border-radius: var(--radius-xl) 0 0 var(--radius-xl);
75
+ transform: translateX(100%);
76
+ }
77
+
78
+ :host([open][side="right"]) .sheet__panel {
79
+ transform: translateX(0);
80
+ }
81
+
82
+ .sheet__header {
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: space-between;
86
+ padding: var(--space-lg) var(--space-xl);
87
+ border-bottom: 1px solid var(--border-subtle);
88
+ flex-shrink: 0;
89
+ }
90
+
91
+ .sheet__heading {
92
+ font-family: var(--font-body);
93
+ font-size: var(--text-md);
94
+ font-weight: 600;
95
+ color: var(--text-primary);
96
+ margin: 0;
97
+ }
98
+
99
+ .sheet__close {
100
+ background: none;
101
+ border: none;
102
+ color: var(--text-ghost);
103
+ cursor: pointer;
104
+ font-size: var(--text-md);
105
+ width: 32px;
106
+ height: 32px;
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: center;
110
+ border-radius: var(--radius-sm);
111
+ transition: color var(--transition-fast), background var(--transition-fast);
112
+ line-height: 1;
113
+ }
114
+
115
+ .sheet__close:hover {
116
+ color: var(--text-primary);
117
+ background: var(--bg-hover);
118
+ }
119
+
120
+ .sheet__close:focus-visible {
121
+ outline: none;
122
+ box-shadow: var(--focus-ring);
123
+ }
124
+
125
+ .sheet__body {
126
+ padding: var(--space-lg) var(--space-xl);
127
+ color: var(--text-secondary);
128
+ font-size: var(--body-size);
129
+ line-height: var(--body-lh);
130
+ flex: 1;
131
+ overflow-y: auto;
132
+ }
133
+
134
+ .sheet__footer {
135
+ padding: var(--space-md) var(--space-xl);
136
+ border-top: 1px solid var(--border-subtle);
137
+ display: flex;
138
+ justify-content: flex-end;
139
+ gap: var(--space-sm);
140
+ flex-shrink: 0;
141
+ }
142
+
143
+ /* Handle for bottom sheet */
144
+ :host(:not([side])) .sheet__handle,
145
+ :host([side="bottom"]) .sheet__handle {
146
+ display: flex;
147
+ justify-content: center;
148
+ padding: var(--space-sm) 0 0;
149
+ }
150
+
151
+ :host([side="right"]) .sheet__handle { display: none; }
152
+
153
+ .sheet__handle-bar {
154
+ width: 40px;
155
+ height: 4px;
156
+ border-radius: var(--radius-full);
157
+ background: var(--border-bright);
158
+ }
159
+
160
+ @media (prefers-reduced-motion: reduce) {
161
+ .sheet__backdrop,
162
+ .sheet__panel { transition: none; }
163
+ }
164
+ `,
165
+ ];
166
+
167
+ constructor() {
168
+ super();
169
+ this.open = false;
170
+ this.side = 'bottom';
171
+ this.heading = '';
172
+ this._handleKeydown = this._handleKeydown.bind(this);
173
+ }
174
+
175
+ disconnectedCallback() {
176
+ super.disconnectedCallback();
177
+ document.removeEventListener('keydown', this._handleKeydown);
178
+ document.body.style.overflow = '';
179
+ }
180
+
181
+ _handleKeydown(e) {
182
+ if (e.key === 'Escape') {
183
+ this._close();
184
+ }
185
+ }
186
+
187
+ _close() {
188
+ this.open = false;
189
+ this.dispatchEvent(new CustomEvent('arc-close', { bubbles: true, composed: true }));
190
+ }
191
+
192
+ _backdropClick(e) {
193
+ if (e.target === e.currentTarget) {
194
+ this._close();
195
+ }
196
+ }
197
+
198
+ updated(changed) {
199
+ if (changed.has('open')) {
200
+ if (this.open) {
201
+ document.addEventListener('keydown', this._handleKeydown);
202
+ document.body.style.overflow = 'hidden';
203
+ this.dispatchEvent(new CustomEvent('arc-open', { bubbles: true, composed: true }));
204
+ this.updateComplete.then(() => {
205
+ const closeBtn = this.shadowRoot.querySelector('.sheet__close');
206
+ closeBtn?.focus();
207
+ });
208
+ } else {
209
+ document.removeEventListener('keydown', this._handleKeydown);
210
+ document.body.style.overflow = '';
211
+ }
212
+ }
213
+ }
214
+
215
+ render() {
216
+ return html`
217
+ <div
218
+ class="sheet__backdrop"
219
+ @click=${this._backdropClick}
220
+ part="backdrop"
221
+ ></div>
222
+ <div
223
+ class="sheet__panel"
224
+ role="dialog"
225
+ aria-modal="true"
226
+ aria-label=${this.heading || 'Sheet'}
227
+ part="panel"
228
+ >
229
+ <div class="sheet__handle" part="handle">
230
+ <div class="sheet__handle-bar"></div>
231
+ </div>
232
+ <div class="sheet__header" part="header">
233
+ <slot name="header">
234
+ <h2 class="sheet__heading">${this.heading}</h2>
235
+ </slot>
236
+ <button class="sheet__close" aria-label="Close" @click=${this._close} part="close">&times;</button>
237
+ </div>
238
+ <div class="sheet__body" part="body">
239
+ <slot></slot>
240
+ </div>
241
+ <div class="sheet__footer" part="footer">
242
+ <slot name="footer"></slot>
243
+ </div>
244
+ </div>
245
+ `;
246
+ }
247
+ }
248
+
249
+ customElements.define('arc-sheet', ArcSheet);