@konomi-app/ui 3.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,150 +1,397 @@
1
- // src/overlay/style.ts
2
- import { css } from "@emotion/css";
3
- var getBodyStyle = () => css`
4
- overflow: visible;
5
- &[${ATTRIBUTE_KEY}] {
6
- overflow: hidden;
7
- }
8
- `;
9
- var getRootStyle = () => css`
10
- font-family: 'Yu Gothic Medium', '游ゴシック', YuGothic, 'メイリオ', 'Hiragino Kaku Gothic ProN',
11
- Meiryo, sans-serif;
12
- color: #356;
13
- font-size: 14px;
14
-
15
- overflow: hidden;
16
- background-color: #fffb;
17
- backdrop-filter: blur(4px);
18
- box-sizing: content-box;
19
-
20
- position: fixed;
21
- inset: 0;
22
- width: 100vw;
23
- height: 100vh;
24
-
25
- display: grid;
26
- transition: all 250ms ease;
27
- z-index: 1000;
28
- opacity: 0;
29
- transition: all 250ms ease;
30
-
31
- place-items: end stretch;
32
- @media (min-width: 640px) {
33
- font-size: 16px;
34
- place-items: center;
35
- }
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __typeError = (msg) => {
4
+ throw TypeError(msg);
5
+ };
6
+ var __decorateClass = (decorators, target, key, kind) => {
7
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
8
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
9
+ if (decorator = decorators[i])
10
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
11
+ if (kind && result) __defProp(target, key, result);
12
+ return result;
13
+ };
14
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
15
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
16
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
17
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
18
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
36
19
 
37
- opacity: 0;
38
- pointer-events: none;
39
- &[${ATTRIBUTE_SHOWN}] {
40
- opacity: 1;
41
- pointer-events: all;
42
- }
43
- `;
20
+ // src/types.ts
21
+ var createInitialState = () => ({
22
+ open: false,
23
+ dialogType: "loading",
24
+ icon: null,
25
+ label: "",
26
+ description: "",
27
+ html: "",
28
+ progress: null,
29
+ showConfirmButton: true,
30
+ showCancelButton: false,
31
+ confirmButtonText: "OK",
32
+ cancelButtonText: "\u30AD\u30E3\u30F3\u30BB\u30EB",
33
+ allowOutsideClick: true,
34
+ allowEscapeKey: true,
35
+ queues: [],
36
+ steps: [],
37
+ timer: null,
38
+ title: ""
39
+ });
44
40
 
45
- // src/overlay/index.ts
46
- var ATTRIBUTE_KEY = "data-konomi-ui-overlay";
47
- var ATTRIBUTE_SHOWN = "data-shown";
48
- var Overlay = class {
49
- /**
50
- * オーバーレイをレンダリングするルート要素。
51
- */
52
- #root;
53
- /**
54
- * オーバーレイをレンダリングするルート要素の`dataset`。
55
- */
56
- _rootDataset;
57
- /**
58
- * オーバーレイが表示されているかどうか。
59
- */
60
- _shown;
61
- /**
62
- * オーバーレイを表示中にページを離れようとした場合にアラートを表示するかどうかを設定します。
63
- */
64
- _disableBeforeUnload;
41
+ // src/controller.ts
42
+ var _state, _listeners, _resolver, _timerId, _DialogController_instances, emit_fn, update_fn, createPromise_fn, resolve_fn, clearTimer_fn, updateItemStatus_fn;
43
+ var DialogController = class {
65
44
  constructor() {
66
- this._shown = false;
67
- this._disableBeforeUnload = false;
68
- this._rootDataset = {};
69
- const root = document.createElement("div");
70
- this.#root = root;
71
- this.#root.classList.add(getRootStyle());
72
- document.body.classList.add(getBodyStyle());
73
- this.#root.setAttribute(ATTRIBUTE_KEY, "");
74
- document.body.append(root);
75
- }
76
- show() {
77
- this._shown = true;
78
- this.#root.setAttribute(ATTRIBUTE_SHOWN, "");
79
- document.body.setAttribute(ATTRIBUTE_KEY, "");
80
- window.addEventListener("beforeunload", this.beforeunload);
81
- this.render();
45
+ __privateAdd(this, _DialogController_instances);
46
+ __privateAdd(this, _state);
47
+ __privateAdd(this, _listeners, /* @__PURE__ */ new Set());
48
+ __privateAdd(this, _resolver, null);
49
+ __privateAdd(this, _timerId, null);
50
+ __privateSet(this, _state, createInitialState());
51
+ }
52
+ // ─── Observable ──────────────────────────────────────────
53
+ get state() {
54
+ return __privateGet(this, _state);
55
+ }
56
+ subscribe(fn) {
57
+ __privateGet(this, _listeners).add(fn);
58
+ return () => __privateGet(this, _listeners).delete(fn);
59
+ }
60
+ // ─── Core ────────────────────────────────────────────────
61
+ show(options = { type: "loading" }) {
62
+ __privateMethod(this, _DialogController_instances, clearTimer_fn).call(this);
63
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, {
64
+ open: true,
65
+ dialogType: options.type,
66
+ label: options.label ?? "",
67
+ description: options.description ?? "",
68
+ icon: options.icon ?? null,
69
+ progress: options.progress ?? null,
70
+ allowOutsideClick: options.allowOutsideClick ?? false,
71
+ showConfirmButton: false,
72
+ showCancelButton: false
73
+ });
82
74
  }
83
75
  hide() {
84
- this._shown = false;
85
- this.#root.removeAttribute(ATTRIBUTE_SHOWN);
86
- document.body.removeAttribute(ATTRIBUTE_KEY);
87
- window.removeEventListener("beforeunload", this.beforeunload);
88
- this.render();
76
+ __privateMethod(this, _DialogController_instances, clearTimer_fn).call(this);
77
+ const wasOpen = __privateGet(this, _state).open;
78
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { ...createInitialState(), open: false });
79
+ if (wasOpen && __privateGet(this, _resolver)) {
80
+ __privateMethod(this, _DialogController_instances, resolve_fn).call(this, { isConfirmed: false, isCanceled: false, isDismissed: true });
81
+ }
89
82
  }
90
- render() {
83
+ // ─── Alert ───────────────────────────────────────────────
84
+ alert(optionsOrLabel) {
85
+ __privateMethod(this, _DialogController_instances, clearTimer_fn).call(this);
86
+ const opts = typeof optionsOrLabel === "string" ? { label: optionsOrLabel } : optionsOrLabel;
87
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, {
88
+ open: true,
89
+ dialogType: "alert",
90
+ icon: opts.type ?? "info",
91
+ label: opts.label ?? "",
92
+ description: opts.description ?? "",
93
+ html: opts.html ?? "",
94
+ showConfirmButton: true,
95
+ showCancelButton: opts.showCancelButton ?? false,
96
+ confirmButtonText: opts.confirmButtonText ?? "OK",
97
+ cancelButtonText: opts.cancelButtonText ?? "\u30AD\u30E3\u30F3\u30BB\u30EB",
98
+ allowOutsideClick: opts.allowOutsideClick ?? true,
99
+ allowEscapeKey: opts.allowEscapeKey ?? true,
100
+ progress: null,
101
+ timer: opts.timer ?? null
102
+ });
103
+ return __privateMethod(this, _DialogController_instances, createPromise_fn).call(this, opts.timer ?? null);
104
+ }
105
+ // ─── Confirm ─────────────────────────────────────────────
106
+ confirm(optionsOrLabel) {
107
+ __privateMethod(this, _DialogController_instances, clearTimer_fn).call(this);
108
+ const opts = typeof optionsOrLabel === "string" ? { label: optionsOrLabel } : optionsOrLabel;
109
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, {
110
+ open: true,
111
+ dialogType: "confirm",
112
+ icon: opts.type ?? "warning",
113
+ label: opts.label ?? "",
114
+ description: opts.description ?? "",
115
+ showConfirmButton: true,
116
+ showCancelButton: true,
117
+ confirmButtonText: opts.confirmButtonText ?? "OK",
118
+ cancelButtonText: opts.cancelButtonText ?? "\u30AD\u30E3\u30F3\u30BB\u30EB",
119
+ allowOutsideClick: opts.allowOutsideClick ?? false,
120
+ allowEscapeKey: opts.allowEscapeKey ?? true,
121
+ progress: null,
122
+ timer: null
123
+ });
124
+ return __privateMethod(this, _DialogController_instances, createPromise_fn).call(this, null).then((r) => r.isConfirmed);
125
+ }
126
+ // ─── Loading helpers ─────────────────────────────────────
127
+ loading(label) {
128
+ this.show({ type: "loading", label });
129
+ }
130
+ progress(percent) {
131
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { progress: percent });
132
+ }
133
+ label(label) {
134
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { label });
135
+ }
136
+ description(description) {
137
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { description });
138
+ }
139
+ // ─── Queue ───────────────────────────────────────────────
140
+ setQueues(labels) {
141
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, {
142
+ queues: labels.map((label) => ({ label, status: "pending" }))
143
+ });
144
+ }
145
+ queue(labels, title) {
146
+ this.setQueues(labels);
147
+ this.show({ type: "queue" });
148
+ if (title !== void 0) __privateMethod(this, _DialogController_instances, update_fn).call(this, { title });
149
+ }
150
+ queueTitle(title) {
151
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { title });
152
+ }
153
+ startQueue(label) {
154
+ __privateMethod(this, _DialogController_instances, updateItemStatus_fn).call(this, "queues", label, "active");
155
+ }
156
+ finishQueue(label) {
157
+ __privateMethod(this, _DialogController_instances, updateItemStatus_fn).call(this, "queues", label, "done");
158
+ }
159
+ skipQueue(label) {
160
+ __privateMethod(this, _DialogController_instances, updateItemStatus_fn).call(this, "queues", label, "skipped");
161
+ }
162
+ errorQueue(label) {
163
+ __privateMethod(this, _DialogController_instances, updateItemStatus_fn).call(this, "queues", label, "error");
164
+ }
165
+ clearQueues() {
166
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { queues: [] });
167
+ }
168
+ // ─── Steps ───────────────────────────────────────────────
169
+ setSteps(labels) {
170
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, {
171
+ steps: labels.map((label) => ({ label, status: "pending" }))
172
+ });
173
+ }
174
+ steps(labels) {
175
+ this.setSteps(labels);
176
+ this.show({ type: "steps" });
177
+ }
178
+ startStep(label) {
179
+ __privateMethod(this, _DialogController_instances, updateItemStatus_fn).call(this, "steps", label, "active");
180
+ }
181
+ finishStep(label) {
182
+ __privateMethod(this, _DialogController_instances, updateItemStatus_fn).call(this, "steps", label, "done");
183
+ }
184
+ skipStep(label) {
185
+ __privateMethod(this, _DialogController_instances, updateItemStatus_fn).call(this, "steps", label, "skipped");
186
+ }
187
+ errorStep(label) {
188
+ __privateMethod(this, _DialogController_instances, updateItemStatus_fn).call(this, "steps", label, "error");
91
189
  }
92
- /** JavaScript中にページを離れようとした場合にアラートを表示します */
93
- beforeunload(event) {
94
- event.preventDefault();
190
+ clearSteps() {
191
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { steps: [] });
95
192
  }
96
- get root() {
97
- return this.#root;
193
+ // ─── Button actions (called from the component) ──────────
194
+ onConfirm() {
195
+ __privateMethod(this, _DialogController_instances, clearTimer_fn).call(this);
196
+ const r = { isConfirmed: true, isCanceled: false, isDismissed: false };
197
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { ...createInitialState(), open: false });
198
+ __privateMethod(this, _DialogController_instances, resolve_fn).call(this, r);
98
199
  }
99
- /** @deprecated このメソッドは非推奨です。代わりに`show`メソッドを使用してください。 */
100
- start() {
101
- this.show();
200
+ onCancel() {
201
+ __privateMethod(this, _DialogController_instances, clearTimer_fn).call(this);
202
+ const r = { isConfirmed: false, isCanceled: true, isDismissed: false };
203
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { ...createInitialState(), open: false });
204
+ __privateMethod(this, _DialogController_instances, resolve_fn).call(this, r);
102
205
  }
103
- /** @deprecated このメソッドは非推奨です。代わりに`hide`メソッドを使用してください。 */
104
- stop() {
105
- this.hide();
206
+ onOutsideClick() {
207
+ if (!__privateGet(this, _state).allowOutsideClick) return;
208
+ this.onCancel();
209
+ }
210
+ onEscapeKey() {
211
+ if (!__privateGet(this, _state).allowEscapeKey) return;
212
+ this.onCancel();
213
+ }
214
+ };
215
+ _state = new WeakMap();
216
+ _listeners = new WeakMap();
217
+ _resolver = new WeakMap();
218
+ _timerId = new WeakMap();
219
+ _DialogController_instances = new WeakSet();
220
+ emit_fn = function() {
221
+ const snapshot = { ...__privateGet(this, _state) };
222
+ for (const fn of __privateGet(this, _listeners)) fn(snapshot);
223
+ };
224
+ update_fn = function(patch) {
225
+ Object.assign(__privateGet(this, _state), patch);
226
+ __privateMethod(this, _DialogController_instances, emit_fn).call(this);
227
+ };
228
+ // ─── Internal ────────────────────────────────────────────
229
+ createPromise_fn = function(timer) {
230
+ return new Promise((resolve) => {
231
+ __privateSet(this, _resolver, resolve);
232
+ if (timer != null && timer > 0) {
233
+ __privateSet(this, _timerId, setTimeout(() => {
234
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { ...createInitialState(), open: false });
235
+ resolve({ isConfirmed: false, isCanceled: false, isDismissed: true });
236
+ __privateSet(this, _resolver, null);
237
+ }, timer));
238
+ }
239
+ });
240
+ };
241
+ resolve_fn = function(result) {
242
+ const resolver = __privateGet(this, _resolver);
243
+ __privateSet(this, _resolver, null);
244
+ resolver?.(result);
245
+ };
246
+ clearTimer_fn = function() {
247
+ if (__privateGet(this, _timerId) != null) {
248
+ clearTimeout(__privateGet(this, _timerId));
249
+ __privateSet(this, _timerId, null);
250
+ }
251
+ };
252
+ updateItemStatus_fn = function(key, label, status) {
253
+ const items = [...__privateGet(this, _state)[key]];
254
+ const idx = items.findIndex((i) => i.label === label);
255
+ if (idx >= 0) {
256
+ items[idx] = { ...items[idx], status };
257
+ __privateMethod(this, _DialogController_instances, update_fn).call(this, { [key]: items });
106
258
  }
107
259
  };
108
260
 
109
- // src/loading-overlay/style.ts
110
- import { css as css2 } from "@emotion/css";
111
- var containerStyle = css2`
112
- display: flex;
113
- flex-direction: column;
114
- align-items: center;
115
- justify-content: center;
116
- gap: 32px;
117
- padding: 24px;
118
- background-color: #fff;
119
- border-radius: 0;
120
- box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
121
- border: 1px solid #f3f4f6;
122
- min-height: 200px;
123
- position: relative;
124
- overflow: hidden;
125
- transition: all 250ms ease;
261
+ // src/overlay-dialog.ts
262
+ import { LitElement, html, nothing } from "lit";
263
+ import { customElement, property, state } from "lit/decorators.js";
264
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
265
+
266
+ // src/styles.ts
267
+ import { css } from "lit";
268
+ var overlayStyles = css`
269
+ :host {
270
+ /* ─── Customizable CSS Variables ─── */
271
+ --dialog-font-family:
272
+ 'Yu Gothic Medium', '游ゴシック', YuGothic, 'メイリオ', 'Hiragino Kaku Gothic ProN', Meiryo,
273
+ sans-serif;
274
+ --dialog-text-color: #356;
275
+ --dialog-font-size: 14px;
276
+ --dialog-font-size-desktop: 16px;
277
+ --dialog-z-index: 1000;
278
+ --dialog-backdrop-color: rgb(255 255 255 / 0.73);
279
+ --dialog-backdrop-blur: 4px;
280
+ --dialog-transition-duration: 250ms;
281
+
282
+ /* Card */
283
+ --dialog-card-bg: #fff;
284
+ --dialog-card-border: #f3f4f6;
285
+ --dialog-card-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
286
+ --dialog-card-radius: 4px;
287
+ --dialog-card-width: 400px;
288
+ --dialog-card-min-height: 200px;
289
+ --dialog-card-padding: 24px;
290
+ --dialog-card-padding-desktop: 32px;
291
+
292
+ /* Colors */
293
+ --dialog-primary: #3b82f6;
294
+ --dialog-primary-hover: #2563eb;
295
+ --dialog-success: #22c55e;
296
+ --dialog-error: #ef4444;
297
+ --dialog-warning: #f59e0b;
298
+ --dialog-info: #3b82f6;
299
+
300
+ /* Progress */
301
+ --dialog-progress-height: 2px;
302
+ --dialog-progress-color: var(--dialog-primary-hover);
303
+ --dialog-progress-transition: all 350ms ease;
304
+
305
+ /* Button */
306
+ --dialog-btn-radius: 6px;
307
+ --dialog-btn-padding: 8px 24px;
308
+ --dialog-btn-font-size: 14px;
309
+
310
+ /* Spinner */
311
+ --dialog-spinner-size: 60px;
312
+ --dialog-spinner-track: rgb(59 130 246 / 0.2);
313
+ --dialog-spinner-arc: var(--dialog-primary);
314
+
315
+ display: contents;
316
+ font-family: var(--dialog-font-family);
317
+ color: var(--dialog-text-color);
318
+ font-size: var(--dialog-font-size);
319
+ }
320
+
321
+ /* ─── Backdrop ─── */
322
+
323
+ .backdrop {
324
+ position: fixed;
325
+ inset: 0;
326
+ width: 100vw;
327
+ height: 100vh;
328
+ display: grid;
329
+ place-items: end stretch;
330
+ z-index: var(--dialog-z-index);
331
+ overflow: hidden;
332
+ background-color: var(--dialog-backdrop-color);
333
+ backdrop-filter: blur(var(--dialog-backdrop-blur));
334
+ box-sizing: border-box;
335
+ transition: opacity var(--dialog-transition-duration) ease;
336
+ opacity: 0;
337
+ pointer-events: none;
338
+ }
339
+
340
+ .backdrop[data-open] {
341
+ opacity: 1;
342
+ pointer-events: all;
343
+ }
126
344
 
127
345
  @media (min-width: 640px) {
128
- width: 400px;
129
- max-width: 90vw;
130
- border-radius: 4px;
131
- padding: 32px;
346
+ :host {
347
+ font-size: var(--dialog-font-size-desktop);
348
+ }
349
+ .backdrop {
350
+ place-items: center;
351
+ }
132
352
  }
133
- `;
134
- var getLoaderStyle = () => css2`
135
- font-size: 60px;
136
- width: 1em;
137
- height: 1em;
138
- border-radius: 50%;
139
- box-shadow: inset 0 0 0 1px #3b82f633;
140
- position: relative;
141
353
 
142
- animation: none;
143
- &[${ATTRIBUTE_ANIMATION}] {
144
- animation: rotate 1.2s infinite linear;
354
+ /* ─── Card ─── */
355
+
356
+ .card {
357
+ display: flex;
358
+ flex-direction: column;
359
+ align-items: center;
360
+ justify-content: center;
361
+ gap: 16px;
362
+ padding: var(--dialog-card-padding);
363
+ background-color: var(--dialog-card-bg);
364
+ border-radius: 0;
365
+ box-shadow: var(--dialog-card-shadow);
366
+ border: 1px solid var(--dialog-card-border);
367
+ min-height: var(--dialog-card-min-height);
368
+ position: relative;
369
+ overflow: hidden;
370
+ transition: all var(--dialog-transition-duration) ease;
371
+ }
372
+
373
+ @media (min-width: 640px) {
374
+ .card {
375
+ width: var(--dialog-card-width);
376
+ max-width: 90vw;
377
+ border-radius: var(--dialog-card-radius);
378
+ padding: var(--dialog-card-padding-desktop);
379
+ }
380
+ }
381
+
382
+ /* ─── Spinner ─── */
383
+
384
+ .spinner {
385
+ font-size: var(--dialog-spinner-size);
386
+ width: 1em;
387
+ height: 1em;
388
+ border-radius: 50%;
389
+ box-shadow: inset 0 0 0 1px var(--dialog-spinner-track);
390
+ position: relative;
391
+ animation: spin 1.2s infinite linear;
145
392
  }
146
393
 
147
- > div {
394
+ .spinner-half {
148
395
  position: absolute;
149
396
  left: 50%;
150
397
  top: 50%;
@@ -154,17 +401,18 @@ var getLoaderStyle = () => css2`
154
401
  margin-top: -0.5em;
155
402
  overflow: hidden;
156
403
  transform-origin: 0.5em 0.5em;
157
- mask-image: linear-gradient(top, #000f, #0000);
158
- -webkit-mask-image: -webkit-linear-gradient(top, #000f, #0000);
159
-
160
- > div {
161
- width: 1em;
162
- height: 1em;
163
- border-radius: 50%;
164
- box-shadow: inset 0 0 0 1px #3b82f6;
165
- }
404
+ mask-image: linear-gradient(to bottom, #000f, #0000);
405
+ -webkit-mask-image: linear-gradient(to bottom, #000f, #0000);
406
+ }
407
+
408
+ .spinner-inner {
409
+ width: 1em;
410
+ height: 1em;
411
+ border-radius: 50%;
412
+ box-shadow: inset 0 0 0 1px var(--dialog-spinner-arc);
166
413
  }
167
- @keyframes rotate {
414
+
415
+ @keyframes spin {
168
416
  0% {
169
417
  transform: rotate(0deg);
170
418
  }
@@ -172,315 +420,232 @@ var getLoaderStyle = () => css2`
172
420
  transform: rotate(360deg);
173
421
  }
174
422
  }
175
- `;
176
- var progressStyle = css2`
177
- position: absolute;
178
- bottom: 0px;
179
- left: 0;
180
- width: 0%;
181
- height: 2px;
182
- background-color: #2563eb;
183
- transition: all 350ms ease;
184
- `;
185
423
 
186
- // src/loading-overlay/index.ts
187
- var ATTRIBUTE_ANIMATION = "data-animation";
188
- var LoadingOverlay = class extends Overlay {
189
- #label;
190
- #html;
191
- #progress;
192
- _loaderElement;
193
- _progressElement;
194
- _contentElement;
195
- constructor(props = {}) {
196
- super();
197
- this.#label = props.label ?? "";
198
- this.#html = "";
199
- this.#progress = props.progress ?? null;
200
- const container = document.createElement("div");
201
- container.classList.add(containerStyle);
202
- this.root.append(container);
203
- const loaderElement = document.createElement("div");
204
- loaderElement.innerHTML = "<div><div></div></div>";
205
- loaderElement.classList.add(getLoaderStyle());
206
- this._loaderElement = loaderElement;
207
- container.append(loaderElement);
208
- const progressElement = document.createElement("div");
209
- progressElement.classList.add(progressStyle);
210
- this._progressElement = progressElement;
211
- container.append(progressElement);
212
- const contentElement = document.createElement("div");
213
- this._contentElement = contentElement;
214
- container.append(contentElement);
215
- this.render();
216
- }
217
- show() {
218
- super.show();
219
- this._loaderElement.setAttribute(ATTRIBUTE_ANIMATION, "");
424
+ /* ─── Icon ─── */
425
+
426
+ .icon-container {
427
+ width: 64px;
428
+ height: 64px;
429
+ border-radius: 50%;
430
+ display: flex;
431
+ align-items: center;
432
+ justify-content: center;
433
+ flex-shrink: 0;
220
434
  }
221
- hide() {
222
- super.hide();
223
- this._loaderElement.removeAttribute(ATTRIBUTE_ANIMATION);
224
- this.#progress = 0;
225
- this.#html = "";
226
- this.#label = "";
227
- }
228
- set label(label) {
229
- this.#label = label;
230
- this.render();
231
- }
232
- set html(html) {
233
- this.#html = html;
234
- this.render();
235
- }
236
- /**
237
- * 進捗状況を設定します。
238
- * @param percent 進捗のパーセンテージ(0-100)
239
- */
240
- set progress(percent) {
241
- this.#progress = percent;
242
- this.render();
435
+
436
+ .icon-container svg {
437
+ width: 36px;
438
+ height: 36px;
243
439
  }
244
- render() {
245
- super.render();
246
- this._progressElement.style.width = `${this.#progress}%`;
247
- if (this.#html) {
248
- this._contentElement.innerHTML = this.#html;
249
- } else {
250
- if (this.#label instanceof Array) {
251
- this._contentElement.innerHTML = `<div>${this.#label.join("</div><div>")}</div>`;
252
- } else {
253
- this._contentElement.innerText = this.#label;
254
- }
255
- }
440
+
441
+ .icon-success {
442
+ background-color: rgb(34 197 94 / 0.1);
443
+ color: var(--dialog-success);
256
444
  }
257
- };
258
445
 
259
- // src/task-list-overlay.ts
260
- import { css as css3 } from "@emotion/css";
261
- var TaskListOverlay = class extends Overlay {
262
- #taskList;
263
- _contentElement;
264
- constructor(props = {}) {
265
- super();
266
- this.#taskList = props.taskList ?? [];
267
- const container = document.createElement("div");
268
- container.classList.add(this.containerStyle);
269
- this.root.append(container);
270
- const contentElement = document.createElement("div");
271
- this._contentElement = contentElement;
272
- contentElement.classList.add(this.contentStyle);
273
- container.append(contentElement);
274
- this.render();
446
+ .icon-error {
447
+ background-color: rgb(239 68 68 / 0.1);
448
+ color: var(--dialog-error);
275
449
  }
276
- hide() {
277
- this.#taskList = [];
278
- super.hide();
279
- }
280
- done(key) {
281
- const task = this.#taskList.find((t) => t.key === key);
282
- if (task) {
283
- task.status = "done";
284
- this.render();
285
- }
450
+
451
+ .icon-warning {
452
+ background-color: rgb(245 158 11 / 0.1);
453
+ color: var(--dialog-warning);
454
+ }
455
+
456
+ .icon-info {
457
+ background-color: rgb(59 130 246 / 0.1);
458
+ color: var(--dialog-info);
459
+ }
460
+
461
+ /* ─── Check Animation ─── */
462
+
463
+ .check-circle {
464
+ width: 64px;
465
+ height: 64px;
466
+ border-radius: 50%;
467
+ stroke-width: 2;
468
+ stroke: var(--dialog-success);
469
+ fill: none;
470
+ stroke-dasharray: 200;
471
+ stroke-dashoffset: 200;
472
+ animation: check-circle-draw 0.6s ease forwards;
473
+ }
474
+
475
+ .check-mark {
476
+ stroke: var(--dialog-success);
477
+ stroke-width: 3;
478
+ stroke-linecap: round;
479
+ stroke-linejoin: round;
480
+ fill: none;
481
+ stroke-dasharray: 50;
482
+ stroke-dashoffset: 50;
483
+ animation: check-mark-draw 0.4s 0.4s ease forwards;
286
484
  }
287
- error(key) {
288
- const task = this.#taskList.find((t) => t.key === key);
289
- if (task) {
290
- task.status = "error";
291
- this.render();
485
+
486
+ @keyframes check-circle-draw {
487
+ to {
488
+ stroke-dashoffset: 0;
292
489
  }
293
490
  }
294
- inProgress(key) {
295
- const task = this.#taskList.find((t) => t.key === key);
296
- if (task) {
297
- task.status = "in-progress";
298
- this.render();
491
+
492
+ @keyframes check-mark-draw {
493
+ to {
494
+ stroke-dashoffset: 0;
299
495
  }
300
496
  }
301
- addTask(...tasks) {
302
- this.#taskList.push(...tasks.map(({ key, label }) => ({ key, label, status: "new" })));
303
- this.render();
497
+
498
+ /* ─── Text ─── */
499
+
500
+ .dialog-title {
501
+ font-size: 14px;
502
+ font-weight: 600;
503
+ color: #374151;
504
+ text-align: center;
505
+ margin: 0;
506
+ word-break: break-word;
507
+ width: 100%;
508
+ padding-bottom: 12px;
509
+ border-bottom: 1px solid var(--dialog-card-border);
510
+ }
511
+
512
+ .label {
513
+ font-size: 18px;
514
+ font-weight: 600;
515
+ color: #1f2937;
516
+ text-align: center;
517
+ margin: 0;
518
+ word-break: break-word;
304
519
  }
305
- render() {
306
- super.render();
307
- this._contentElement.innerHTML = `
308
- <div class="${this.taskListContainerStyle}">
309
- ${this.#taskList.map((task) => this.renderTask(task)).join("")}
310
- </div>
311
- `;
520
+
521
+ .description {
522
+ font-size: 14px;
523
+ color: #6b7280;
524
+ text-align: center;
525
+ margin: 0;
526
+ word-break: break-word;
527
+ line-height: 1.6;
312
528
  }
313
- renderTask(task) {
314
- const label = Array.isArray(task.label) ? task.label.join(" ") : task.label;
315
- const status = task.status;
316
- return `
317
- <div class="${this.taskContainerStyle}">
318
- <div class="${this.taskStatusStyle} status-${status}">${this.getTaskStatusElement(
319
- task.status
320
- )}</div>
321
- <div class="${this.taskLabelStyle}">${label}</div>
322
- </div>
323
- `;
529
+
530
+ .html-content {
531
+ font-size: 14px;
532
+ color: #6b7280;
533
+ text-align: center;
534
+ word-break: break-word;
535
+ line-height: 1.6;
536
+ width: 100%;
324
537
  }
325
- getTaskStatusElement(status) {
326
- switch (status) {
327
- case "new":
328
- return `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="#ddd" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-minus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="8" y1="12" x2="16" y2="12"></line></svg>`;
329
- case "in-progress":
330
- return `<div><div></div></div>`;
331
- case "done":
332
- return `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="#80beaf" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check-circle"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>`;
333
- default:
334
- return ``;
335
- }
538
+
539
+ /* ─── Progress ─── */
540
+
541
+ .progress-bar {
542
+ position: absolute;
543
+ bottom: 0;
544
+ left: 0;
545
+ width: 0%;
546
+ height: var(--dialog-progress-height);
547
+ background-color: var(--dialog-progress-color);
548
+ transition: var(--dialog-progress-transition);
336
549
  }
337
- containerStyle = css3`
550
+
551
+ /* ─── Buttons ─── */
552
+
553
+ .actions {
338
554
  display: flex;
339
555
  flex-direction: column;
340
- align-items: center;
341
- justify-content: center;
342
- gap: 32px;
343
- padding: 32px 64px;
344
- background-color: #fffc;
345
- border-radius: 8px;
346
- box-shadow: 0 5px 24px -6px #0002;
347
- width: 300px;
348
- max-width: 90vw;
349
- min-height: 200px;
350
- position: relative;
351
- overflow: hidden;
352
- transition: all 250ms ease;
556
+ gap: 8px;
557
+ width: 100%;
558
+ margin-top: 8px;
559
+ }
353
560
 
354
- @keyframes spin {
355
- 0% {
356
- transform: rotate(0deg);
357
- border-radius: 1em;
358
- }
359
- 20% {
360
- transform: rotate(0deg);
361
- }
362
- 30%,
363
- 60% {
364
- border-radius: 0.25em;
365
- }
366
- 70% {
367
- transform: rotate(180deg);
368
- }
369
- 100% {
370
- transform: rotate(180deg);
371
- border-radius: 1em;
372
- }
561
+ @media (min-width: 640px) {
562
+ .actions {
563
+ flex-direction: row;
564
+ justify-content: center;
373
565
  }
374
- `;
375
- contentStyle = css3`
376
- width: 100%;
377
- `;
378
- taskListContainerStyle = css3`
566
+ }
567
+
568
+ .btn {
569
+ padding: var(--dialog-btn-padding);
570
+ font-size: var(--dialog-btn-font-size);
571
+ font-family: inherit;
572
+ border: none;
573
+ border-radius: var(--dialog-btn-radius);
574
+ cursor: pointer;
575
+ font-weight: 500;
576
+ transition:
577
+ background-color 150ms ease,
578
+ transform 80ms ease;
579
+ min-width: 100px;
580
+ text-align: center;
581
+ }
582
+
583
+ .btn:active {
584
+ transform: scale(0.97);
585
+ }
586
+
587
+ .btn-confirm {
588
+ background-color: var(--dialog-primary);
589
+ color: #fff;
590
+ }
591
+
592
+ .btn-confirm:hover {
593
+ background-color: var(--dialog-primary-hover);
594
+ }
595
+
596
+ .btn-cancel {
597
+ background-color: #f3f4f6;
598
+ color: #374151;
599
+ }
600
+
601
+ .btn-cancel:hover {
602
+ background-color: #e5e7eb;
603
+ }
604
+
605
+ /* ─── Queue / Steps list ─── */
606
+
607
+ .task-list {
379
608
  display: flex;
380
609
  flex-direction: column;
381
- gap: 1em;
610
+ gap: 12px;
382
611
  width: 100%;
383
- `;
384
- taskContainerStyle = css3`
612
+ padding: 0;
613
+ margin: 0;
614
+ list-style: none;
615
+ }
616
+
617
+ .task-item {
385
618
  display: flex;
386
619
  align-items: center;
387
- gap: 1em;
388
- `;
389
- taskLabelStyle = css3``;
390
- taskStatusStyle = css3`
391
- font-size: 32px;
392
- width: 1em;
393
- height: 1em;
620
+ gap: 12px;
621
+ font-size: 14px;
622
+ }
623
+
624
+ .task-icon {
625
+ width: 24px;
626
+ height: 24px;
394
627
  display: flex;
395
- justify-content: center;
396
628
  align-items: center;
397
-
398
- &.status-new {
399
- }
400
-
401
- &.status-in-progress {
402
- border-radius: 50%;
403
- box-shadow: inset 0 0 0 1px #3b82f633;
404
- position: relative;
405
- animation: rotate 1.2s infinite linear;
406
- > div {
407
- position: absolute;
408
- left: 50%;
409
- top: 50%;
410
- width: 0.5em;
411
- height: 1em;
412
- margin-left: -0.5em;
413
- margin-top: -0.5em;
414
- overflow: hidden;
415
- transform-origin: 0.5em 0.5em;
416
- mask-image: linear-gradient(top, #000f, #0000);
417
- -webkit-mask-image: -webkit-linear-gradient(top, #000f, #0000);
418
-
419
- > div {
420
- width: 1em;
421
- height: 1em;
422
- border-radius: 50%;
423
- box-shadow: inset 0 0 0 1px #3b82f6;
424
- }
425
- }
426
- @keyframes rotate {
427
- 0% {
428
- transform: rotate(0deg);
429
- }
430
- 100% {
431
- transform: rotate(360deg);
432
- }
433
- }
434
- }
435
- `;
436
- };
437
-
438
- // src/modal/style.ts
439
- import { css as css4 } from "@emotion/css";
440
- var containerStyle2 = css4`
441
- display: grid;
442
- gap: 2px;
443
- padding: 24px;
444
- background-color: #fff;
445
- border-radius: 0;
446
- box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
447
- border: 1px solid #f3f4f6;
448
- min-height: 200px;
449
- position: relative;
450
- overflow: hidden;
451
- transition: all 250ms ease;
452
-
453
- @media (min-width: 640px) {
454
- width: 400px;
455
- max-width: 90vw;
456
- border-radius: 4px;
457
- padding: 32px;
629
+ justify-content: center;
630
+ flex-shrink: 0;
458
631
  }
459
- `;
460
- var iconStyle = css4``;
461
- var titleStyle = css4`
462
- font-size: 18px;
463
- font-weight: 600;
464
- color: #1f2937;
465
- text-align: center;
466
- @media (min-width: 640px) {
467
- text-align: left;
632
+
633
+ .task-icon svg {
634
+ width: 20px;
635
+ height: 20px;
468
636
  }
469
- `;
470
- var loaderStyle = css4`
471
- font-size: 60px;
472
- width: 1em;
473
- height: 1em;
474
- border-radius: 50%;
475
- box-shadow: inset 0 0 0 1px #3b82f633;
476
- position: relative;
477
637
 
478
- animation: none;
479
- &[data-state='loading'] {
480
- animation: rotate 1s linear infinite;
638
+ .task-icon .mini-spinner {
639
+ font-size: 20px;
640
+ width: 1em;
641
+ height: 1em;
642
+ border-radius: 50%;
643
+ box-shadow: inset 0 0 0 1px var(--dialog-spinner-track);
644
+ position: relative;
645
+ animation: spin 1.2s infinite linear;
481
646
  }
482
647
 
483
- > div {
648
+ .task-icon .mini-spinner-half {
484
649
  position: absolute;
485
650
  left: 50%;
486
651
  top: 50%;
@@ -490,243 +655,531 @@ var loaderStyle = css4`
490
655
  margin-top: -0.5em;
491
656
  overflow: hidden;
492
657
  transform-origin: 0.5em 0.5em;
493
- mask-image: linear-gradient(top, #000f, #0000);
494
- -webkit-mask-image: -webkit-linear-gradient(top, #000f, #0000);
495
-
496
- > div {
497
- width: 1em;
498
- height: 1em;
499
- border-radius: 50%;
500
- box-shadow: inset 0 0 0 1px #3b82f6;
501
- }
658
+ mask-image: linear-gradient(to bottom, #000f, #0000);
659
+ -webkit-mask-image: linear-gradient(to bottom, #000f, #0000);
502
660
  }
503
- @keyframes rotate {
504
- 0% {
505
- transform: rotate(0deg);
506
- }
507
- 100% {
508
- transform: rotate(360deg);
509
- }
661
+
662
+ .task-icon .mini-spinner-inner {
663
+ width: 1em;
664
+ height: 1em;
665
+ border-radius: 50%;
666
+ box-shadow: inset 0 0 0 1px var(--dialog-spinner-arc);
510
667
  }
511
- `;
512
- var progressStyle2 = css4`
513
- position: absolute;
514
- bottom: 0px;
515
- left: 0;
516
- width: 0%;
517
- height: 2px;
518
- background-color: #2563eb;
519
- transition: all 350ms ease;
520
- `;
521
- var actionsStyle = css4`
522
- display: flex;
523
- flex-direction: column;
524
- justify-content: flex-end;
525
- align-items: flex-end;
526
- gap: 8px;
527
668
 
528
- @media (min-width: 640px) {
529
- flex-direction: row;
669
+ .task-label {
670
+ color: #6b7280;
671
+ }
672
+
673
+ .task-label[data-status='active'] {
674
+ color: var(--dialog-text-color);
675
+ font-weight: 500;
676
+ }
677
+
678
+ .task-label[data-status='done'] {
679
+ color: var(--dialog-success);
680
+ }
681
+
682
+ .task-label[data-status='error'] {
683
+ color: var(--dialog-error);
684
+ }
685
+
686
+ .task-label[data-status='skipped'] {
687
+ color: #9ca3af;
688
+ text-decoration: line-through;
689
+ }
690
+
691
+ /* ─── Queue ellipsis ─── */
692
+
693
+ .queue-ellipsis {
694
+ display: flex;
695
+ flex-direction: column;
696
+ align-items: center;
697
+ gap: 3px;
698
+ padding: 4px 0;
699
+ /* アイコン列 (24px) の中央に配置: (24px - 4px dot) / 2 = 10px */
700
+ padding-left: 10px;
701
+ align-self: flex-start;
702
+ list-style: none;
703
+ }
704
+
705
+ .queue-ellipsis span {
706
+ display: block;
707
+ width: 4px;
708
+ height: 4px;
709
+ border-radius: 50%;
710
+ background-color: #d1d5db;
711
+ }
712
+
713
+ /* ─── Steps indicator ─── */
714
+
715
+ .steps-header {
716
+ display: flex;
717
+ align-items: center;
718
+ gap: 4px;
719
+ width: 100%;
720
+ justify-content: center;
721
+ margin-bottom: 8px;
722
+ }
723
+
724
+ .step-dot {
725
+ width: 8px;
726
+ height: 8px;
727
+ border-radius: 50%;
728
+ background-color: #d1d5db;
729
+ transition: background-color 200ms ease;
730
+ }
731
+
732
+ .step-dot[data-status='active'] {
733
+ background-color: var(--dialog-primary);
734
+ box-shadow: 0 0 0 3px rgb(59 130 246 / 0.2);
735
+ }
736
+
737
+ .step-dot[data-status='done'] {
738
+ background-color: var(--dialog-success);
739
+ }
740
+
741
+ .step-dot[data-status='error'] {
742
+ background-color: var(--dialog-error);
743
+ }
744
+
745
+ .step-dot[data-status='skipped'] {
746
+ background-color: #9ca3af;
747
+ }
748
+
749
+ .step-connector {
750
+ flex: 1;
751
+ max-width: 24px;
752
+ height: 2px;
753
+ background-color: #e5e7eb;
530
754
  }
531
755
  `;
532
756
 
533
- // src/modal/index.ts
534
- var Modal = class extends Overlay {
535
- #title;
536
- #html;
537
- #label;
538
- #progress;
539
- #state;
540
- _containerElement;
541
- _iconElement;
542
- _titleElement;
543
- _loaderElement;
544
- _progressElement;
545
- _contentElement;
546
- _actionsElement;
547
- _okButtonElement;
548
- _cancelButtonElement;
549
- constructor(props = {}) {
550
- super();
551
- this.#title = "";
552
- this.#html = "";
553
- this.#label = props.label ?? "";
554
- this.#progress = props.progress ?? null;
555
- this.#state = "hidden";
556
- const container = document.createElement("div");
557
- container.classList.add(containerStyle2);
558
- this._containerElement = container;
559
- this.root.append(container);
560
- const iconElement = document.createElement("div");
561
- iconElement.classList.add(iconStyle);
562
- this._iconElement = iconElement;
563
- container.append(iconElement);
564
- const titleElement = document.createElement("div");
565
- titleElement.classList.add(titleStyle);
566
- this._titleElement = titleElement;
567
- container.append(titleElement);
568
- const loaderElement = document.createElement("div");
569
- loaderElement.innerHTML = "<div><div></div></div>";
570
- loaderElement.classList.add(loaderStyle);
571
- this._loaderElement = loaderElement;
572
- const progressElement = document.createElement("div");
573
- progressElement.classList.add(progressStyle2);
574
- this._progressElement = progressElement;
575
- container.append(progressElement);
576
- const contentElement = document.createElement("div");
577
- this._contentElement = contentElement;
578
- container.append(contentElement);
579
- const actionsElement = document.createElement("div");
580
- actionsElement.classList.add(actionsStyle);
581
- this._actionsElement = actionsElement;
582
- container.append(actionsElement);
583
- const okButtonElement = document.createElement("button");
584
- okButtonElement.type = "button";
585
- okButtonElement.textContent = "OK";
586
- this._okButtonElement = okButtonElement;
587
- actionsElement.append(okButtonElement);
588
- const cancelButtonElement = document.createElement("button");
589
- cancelButtonElement.type = "button";
590
- cancelButtonElement.textContent = "\u30AD\u30E3\u30F3\u30BB\u30EB";
591
- this._cancelButtonElement = cancelButtonElement;
592
- actionsElement.append(cancelButtonElement);
593
- this.render();
594
- }
595
- alert(params) {
596
- const {
597
- title = "",
598
- text = "",
599
- icon = "",
600
- disableClose = false,
601
- disableEscape = false
602
- } = params;
603
- this.#title = title;
604
- this.#label = text;
605
- this.changeState("alert");
606
- this.show();
607
- return new Promise((resolve) => {
608
- this._okButtonElement.addEventListener("click", () => {
609
- if (!disableClose) {
610
- this.hide();
611
- }
612
- resolve({
613
- isConfirmed: true
614
- });
615
- });
616
- this._cancelButtonElement.addEventListener("click", () => {
617
- if (!disableClose) {
618
- this.hide();
619
- }
620
- resolve({
621
- isConfirmed: false
622
- });
623
- });
624
- this.root.addEventListener("click", (event) => {
625
- if (event.currentTarget === event.target) {
626
- if (!disableClose) {
627
- this.hide();
628
- }
629
- resolve({
630
- isConfirmed: false
631
- });
632
- }
633
- });
634
- this.root.addEventListener("keydown", (event) => {
635
- if (event.key === "Escape") {
636
- if (!disableEscape) {
637
- this.hide();
638
- }
639
- resolve({
640
- isConfirmed: false
641
- });
757
+ // src/overlay-dialog.ts
758
+ var OverlayDialog = class extends LitElement {
759
+ constructor() {
760
+ super(...arguments);
761
+ this._state = createInitialState();
762
+ this._beforeUnloadHandler = (e) => e.preventDefault();
763
+ this._onKeyDown = (e) => {
764
+ if (e.key === "Escape" && this._state.open) {
765
+ this.controller.onEscapeKey();
766
+ }
767
+ };
768
+ }
769
+ connectedCallback() {
770
+ super.connectedCallback();
771
+ if (this.controller) {
772
+ this._state = { ...this.controller.state };
773
+ this._unsubscribe = this.controller.subscribe((s) => {
774
+ const wasOpen = this._state.open;
775
+ this._state = s;
776
+ this._syncBodyScroll(s.open);
777
+ if (s.open && !wasOpen) {
778
+ window.addEventListener("beforeunload", this._beforeUnloadHandler);
779
+ } else if (!s.open && wasOpen) {
780
+ window.removeEventListener("beforeunload", this._beforeUnloadHandler);
642
781
  }
643
782
  });
644
- });
783
+ }
784
+ window.addEventListener("keydown", this._onKeyDown);
645
785
  }
646
- loading() {
647
- this._iconElement.append(this._loaderElement);
648
- this.changeState("loading");
649
- this.show();
786
+ disconnectedCallback() {
787
+ super.disconnectedCallback();
788
+ this._unsubscribe?.();
789
+ this._syncBodyScroll(false);
790
+ window.removeEventListener("beforeunload", this._beforeUnloadHandler);
791
+ window.removeEventListener("keydown", this._onKeyDown);
650
792
  }
651
- hide() {
652
- this.#progress = 0;
653
- this.#html = "";
654
- this.#label = "";
655
- this.changeState("hidden");
656
- super.hide();
657
- }
658
- set label(label) {
659
- this.#label = label;
660
- this.render();
661
- }
662
- set html(html) {
663
- this.#html = html;
664
- this.render();
665
- }
666
- set progress(progress) {
667
- this.#progress = progress;
668
- this.render();
669
- }
670
- changeState(state) {
671
- this.#state = state;
672
- const elements = [
673
- this.root,
674
- this._actionsElement,
675
- this._contentElement,
676
- this._iconElement,
677
- this._loaderElement,
678
- this._progressElement,
679
- this._titleElement
680
- ];
681
- for (const element of elements) {
682
- element.dataset.state = state;
793
+ _syncBodyScroll(lock) {
794
+ if (lock) {
795
+ document.body.style.overflow = "hidden";
796
+ } else {
797
+ document.body.style.overflow = "";
683
798
  }
684
- this.render();
685
799
  }
686
- render() {
687
- super.render();
688
- this._progressElement.style.width = `${this.#progress}%`;
689
- switch (this.#state) {
690
- case "loading":
691
- break;
692
- case "alert":
693
- this._iconElement.innerHTML = "";
694
- break;
695
- case "hidden":
800
+ // ─── Event Handlers ──────────────────────────────────────
801
+ _onBackdropClick(e) {
802
+ if (e.target === e.currentTarget) {
803
+ this.controller.onOutsideClick();
804
+ }
805
+ }
806
+ // ─── Render Helpers ──────────────────────────────────────
807
+ _renderIcon(icon) {
808
+ if (!icon) return nothing;
809
+ if (icon === "success") {
810
+ return html`
811
+ <svg
812
+ class="icon-container"
813
+ viewBox="0 0 64 64"
814
+ style="width:64px;height:64px;background:none;"
815
+ >
816
+ <circle class="check-circle" cx="32" cy="32" r="30" />
817
+ <polyline class="check-mark" points="20,34 28,42 44,24" />
818
+ </svg>
819
+ `;
820
+ }
821
+ const paths = {
822
+ error: html`<svg
823
+ viewBox="0 0 24 24"
824
+ fill="none"
825
+ stroke="currentColor"
826
+ stroke-width="2"
827
+ stroke-linecap="round"
828
+ stroke-linejoin="round"
829
+ >
830
+ <circle cx="12" cy="12" r="10" />
831
+ <line x1="15" y1="9" x2="9" y2="15" />
832
+ <line x1="9" y1="9" x2="15" y2="15" />
833
+ </svg>`,
834
+ warning: html`<svg
835
+ viewBox="0 0 24 24"
836
+ fill="none"
837
+ stroke="currentColor"
838
+ stroke-width="2"
839
+ stroke-linecap="round"
840
+ stroke-linejoin="round"
841
+ >
842
+ <path
843
+ d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"
844
+ />
845
+ <line x1="12" y1="9" x2="12" y2="13" />
846
+ <line x1="12" y1="17" x2="12.01" y2="17" />
847
+ </svg>`,
848
+ info: html`<svg
849
+ viewBox="0 0 24 24"
850
+ fill="none"
851
+ stroke="currentColor"
852
+ stroke-width="2"
853
+ stroke-linecap="round"
854
+ stroke-linejoin="round"
855
+ >
856
+ <circle cx="12" cy="12" r="10" />
857
+ <line x1="12" y1="16" x2="12" y2="12" />
858
+ <line x1="12" y1="8" x2="12.01" y2="8" />
859
+ </svg>`
860
+ };
861
+ return html` <div class="icon-container icon-${icon}">${paths[icon] ?? nothing}</div> `;
862
+ }
863
+ _renderSpinner() {
864
+ return html`
865
+ <div class="spinner">
866
+ <div class="spinner-half">
867
+ <div class="spinner-inner"></div>
868
+ </div>
869
+ </div>
870
+ `;
871
+ }
872
+ _renderTaskIcon(status) {
873
+ switch (status) {
874
+ case "active":
875
+ return html`
876
+ <div class="mini-spinner">
877
+ <div class="mini-spinner-half">
878
+ <div class="mini-spinner-inner"></div>
879
+ </div>
880
+ </div>
881
+ `;
882
+ case "done":
883
+ return html`<svg
884
+ viewBox="0 0 24 24"
885
+ fill="none"
886
+ stroke="#22c55e"
887
+ stroke-width="2"
888
+ stroke-linecap="round"
889
+ stroke-linejoin="round"
890
+ >
891
+ <circle cx="12" cy="12" r="10" />
892
+ <polyline points="9,12 11,14 15,10" />
893
+ </svg>`;
894
+ case "error":
895
+ return html`<svg
896
+ viewBox="0 0 24 24"
897
+ fill="none"
898
+ stroke="#ef4444"
899
+ stroke-width="2"
900
+ stroke-linecap="round"
901
+ stroke-linejoin="round"
902
+ >
903
+ <circle cx="12" cy="12" r="10" />
904
+ <line x1="15" y1="9" x2="9" y2="15" />
905
+ <line x1="9" y1="9" x2="15" y2="15" />
906
+ </svg>`;
907
+ case "skipped":
908
+ return html`<svg
909
+ viewBox="0 0 24 24"
910
+ fill="none"
911
+ stroke="#9ca3af"
912
+ stroke-width="2"
913
+ stroke-linecap="round"
914
+ stroke-linejoin="round"
915
+ >
916
+ <circle cx="12" cy="12" r="10" />
917
+ <line x1="8" y1="12" x2="16" y2="12" />
918
+ </svg>`;
919
+ case "pending":
696
920
  default:
697
- this._iconElement.innerHTML = "";
698
- break;
921
+ return html`<svg
922
+ viewBox="0 0 24 24"
923
+ fill="none"
924
+ stroke="#d1d5db"
925
+ stroke-width="2"
926
+ stroke-linecap="round"
927
+ stroke-linejoin="round"
928
+ >
929
+ <circle cx="12" cy="12" r="10" />
930
+ </svg>`;
699
931
  }
700
- this._titleElement.textContent = this.#title;
701
- if (this.#html) {
702
- this._contentElement.innerHTML = this.#html;
703
- } else {
704
- if (this.#label instanceof Array) {
705
- this._contentElement.innerHTML = `<div>${this.#label.join("</div><div>")}</div>`;
706
- } else {
707
- this._contentElement.textContent = this.#label;
932
+ }
933
+ _getQueueWindow(items) {
934
+ const total = items.length;
935
+ if (total <= 4) return { start: 0, end: total - 1 };
936
+ let centerIdx = items.findIndex((i) => i.status === "active");
937
+ if (centerIdx < 0) {
938
+ const finishedStatuses = /* @__PURE__ */ new Set(["done", "skipped", "error"]);
939
+ const lastFinishedIdx = items.reduce(
940
+ (acc, item, i) => finishedStatuses.has(item.status) ? i : acc,
941
+ -1
942
+ );
943
+ centerIdx = lastFinishedIdx >= 0 ? Math.min(lastFinishedIdx + 1, total - 1) : 0;
944
+ }
945
+ const s3 = Math.max(0, Math.min(centerIdx - 1, total - 3));
946
+ const e3 = Math.min(total - 1, s3 + 2);
947
+ const hasTop = s3 > 0;
948
+ const hasBottom = e3 < total - 1;
949
+ if (hasTop && hasBottom) {
950
+ return { start: s3, end: e3 };
951
+ }
952
+ if (!hasTop) {
953
+ return { start: 0, end: 3 };
954
+ }
955
+ return { start: total - 4, end: total - 1 };
956
+ }
957
+ _renderQueueList(items) {
958
+ const total = items.length;
959
+ const { start, end } = this._getQueueWindow(items);
960
+ const visible = items.slice(start, end + 1);
961
+ return html`
962
+ <ul class="task-list">
963
+ ${start > 0 ? html`<li class="queue-ellipsis" aria-hidden="true">
964
+ <span></span><span></span><span></span>
965
+ </li>` : nothing}
966
+ ${visible.map(
967
+ (item) => html`
968
+ <li class="task-item">
969
+ <span class="task-icon">${this._renderTaskIcon(item.status)}</span>
970
+ <span class="task-label" data-status=${item.status}>${item.label}</span>
971
+ </li>
972
+ `
973
+ )}
974
+ ${end < total - 1 ? html`<li class="queue-ellipsis" aria-hidden="true">
975
+ <span></span><span></span><span></span>
976
+ </li>` : nothing}
977
+ </ul>
978
+ `;
979
+ }
980
+ _renderStepsHeader(items) {
981
+ const fragments = [];
982
+ items.forEach((item, i) => {
983
+ if (i > 0) {
984
+ fragments.push(html`<div class="step-connector"></div>`);
708
985
  }
986
+ fragments.push(html`<div class="step-dot" data-status=${item.status}></div>`);
987
+ });
988
+ return html`<div class="steps-header">${fragments}</div>`;
989
+ }
990
+ _renderStepsList(items) {
991
+ return html`
992
+ ${this._renderStepsHeader(items)}
993
+ <ul class="task-list">
994
+ ${items.map(
995
+ (item) => html`
996
+ <li class="task-item">
997
+ <span class="task-icon">${this._renderTaskIcon(item.status)}</span>
998
+ <span class="task-label" data-status=${item.status}>${item.label}</span>
999
+ </li>
1000
+ `
1001
+ )}
1002
+ </ul>
1003
+ `;
1004
+ }
1005
+ _renderButtons() {
1006
+ const s = this._state;
1007
+ if (!s.showConfirmButton && !s.showCancelButton) return nothing;
1008
+ return html`
1009
+ <div class="actions">
1010
+ ${s.showConfirmButton ? html`<button class="btn btn-confirm" @click=${() => this.controller.onConfirm()}>
1011
+ ${s.confirmButtonText}
1012
+ </button>` : nothing}
1013
+ ${s.showCancelButton ? html`<button class="btn btn-cancel" @click=${() => this.controller.onCancel()}>
1014
+ ${s.cancelButtonText}
1015
+ </button>` : nothing}
1016
+ </div>
1017
+ `;
1018
+ }
1019
+ _renderBody() {
1020
+ const s = this._state;
1021
+ switch (s.dialogType) {
1022
+ case "loading":
1023
+ return html`
1024
+ ${this._renderSpinner()} ${s.label ? html`<p class="label">${s.label}</p>` : nothing}
1025
+ ${s.description ? html`<p class="description">${s.description}</p>` : nothing}
1026
+ `;
1027
+ case "alert":
1028
+ case "confirm":
1029
+ return html`
1030
+ ${this._renderIcon(s.icon)} ${s.label ? html`<p class="label">${s.label}</p>` : nothing}
1031
+ ${s.description ? html`<p class="description">${s.description}</p>` : nothing}
1032
+ ${s.html ? html`<div class="html-content">${unsafeHTML(s.html)}</div>` : nothing}
1033
+ ${this._renderButtons()}
1034
+ `;
1035
+ case "queue":
1036
+ return html`
1037
+ ${s.label ? html`<p class="label">${s.label}</p>` : nothing}
1038
+ ${this._renderQueueList(s.queues)}
1039
+ `;
1040
+ case "steps":
1041
+ return html`
1042
+ ${s.label ? html`<p class="label">${s.label}</p>` : nothing}
1043
+ ${this._renderStepsList(s.steps)}
1044
+ `;
1045
+ default:
1046
+ return html`${nothing}`;
709
1047
  }
710
1048
  }
1049
+ render() {
1050
+ const s = this._state;
1051
+ return html`
1052
+ <div class="backdrop" ?data-open=${s.open} @click=${this._onBackdropClick}>
1053
+ <div class="card">
1054
+ ${s.title ? html`<p class="dialog-title">${s.title}</p>` : nothing} ${this._renderBody()}
1055
+ <div
1056
+ class="progress-bar"
1057
+ style="width:${s.progress ?? 0}%;opacity:${s.progress !== null ? 1 : 0}"
1058
+ ></div>
1059
+ </div>
1060
+ </div>
1061
+ `;
1062
+ }
711
1063
  };
1064
+ OverlayDialog.styles = overlayStyles;
1065
+ __decorateClass([
1066
+ property({ attribute: false })
1067
+ ], OverlayDialog.prototype, "controller", 2);
1068
+ __decorateClass([
1069
+ state()
1070
+ ], OverlayDialog.prototype, "_state", 2);
1071
+ OverlayDialog = __decorateClass([
1072
+ customElement("overlay-dialog")
1073
+ ], OverlayDialog);
712
1074
 
713
- // src/utilities.ts
714
- var withLoading = (fn, label = "Loading...") => {
715
- return async (...args) => {
716
- const overlay = new LoadingOverlay({ label });
717
- overlay.show();
718
- try {
719
- return await fn(...args);
720
- } finally {
721
- overlay.hide();
722
- }
723
- };
1075
+ // src/dialog.ts
1076
+ var _controller, _element, _DialogSingleton_instances, ensureElement_fn;
1077
+ var DialogSingleton = class {
1078
+ constructor() {
1079
+ __privateAdd(this, _DialogSingleton_instances);
1080
+ __privateAdd(this, _controller, new DialogController());
1081
+ __privateAdd(this, _element, null);
1082
+ }
1083
+ // ─── Core ────────────────────────────────────────────────
1084
+ show(options) {
1085
+ __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);
1086
+ __privateGet(this, _controller).show(options);
1087
+ }
1088
+ hide() {
1089
+ __privateGet(this, _controller).hide();
1090
+ }
1091
+ // ─── Alert / Confirm ─────────────────────────────────────
1092
+ alert(optionsOrLabel) {
1093
+ __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);
1094
+ return __privateGet(this, _controller).alert(optionsOrLabel);
1095
+ }
1096
+ confirm(optionsOrLabel) {
1097
+ __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);
1098
+ return __privateGet(this, _controller).confirm(optionsOrLabel);
1099
+ }
1100
+ // ─── Loading helpers ─────────────────────────────────────
1101
+ loading(label) {
1102
+ __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);
1103
+ __privateGet(this, _controller).loading(label);
1104
+ }
1105
+ progress(percent) {
1106
+ __privateGet(this, _controller).progress(percent);
1107
+ }
1108
+ label(label) {
1109
+ __privateGet(this, _controller).label(label);
1110
+ }
1111
+ description(description) {
1112
+ __privateGet(this, _controller).description(description);
1113
+ }
1114
+ // ─── Queue ───────────────────────────────────────────────
1115
+ setQueues(labels) {
1116
+ __privateGet(this, _controller).setQueues(labels);
1117
+ }
1118
+ queue(labels, title) {
1119
+ __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);
1120
+ __privateGet(this, _controller).queue(labels, title);
1121
+ }
1122
+ startQueue(label) {
1123
+ __privateGet(this, _controller).startQueue(label);
1124
+ }
1125
+ finishQueue(label) {
1126
+ __privateGet(this, _controller).finishQueue(label);
1127
+ }
1128
+ skipQueue(label) {
1129
+ __privateGet(this, _controller).skipQueue(label);
1130
+ }
1131
+ errorQueue(label) {
1132
+ __privateGet(this, _controller).errorQueue(label);
1133
+ }
1134
+ queueTitle(title) {
1135
+ __privateGet(this, _controller).queueTitle(title);
1136
+ }
1137
+ clearQueues() {
1138
+ __privateGet(this, _controller).clearQueues();
1139
+ }
1140
+ // ─── Steps ───────────────────────────────────────────────
1141
+ setSteps(labels) {
1142
+ __privateGet(this, _controller).setSteps(labels);
1143
+ }
1144
+ steps(labels) {
1145
+ __privateMethod(this, _DialogSingleton_instances, ensureElement_fn).call(this);
1146
+ __privateGet(this, _controller).steps(labels);
1147
+ }
1148
+ startStep(label) {
1149
+ __privateGet(this, _controller).startStep(label);
1150
+ }
1151
+ finishStep(label) {
1152
+ __privateGet(this, _controller).finishStep(label);
1153
+ }
1154
+ skipStep(label) {
1155
+ __privateGet(this, _controller).skipStep(label);
1156
+ }
1157
+ errorStep(label) {
1158
+ __privateGet(this, _controller).errorStep(label);
1159
+ }
1160
+ clearSteps() {
1161
+ __privateGet(this, _controller).clearSteps();
1162
+ }
1163
+ // ─── Advanced ────────────────────────────────────────────
1164
+ get controller() {
1165
+ return __privateGet(this, _controller);
1166
+ }
1167
+ };
1168
+ _controller = new WeakMap();
1169
+ _element = new WeakMap();
1170
+ _DialogSingleton_instances = new WeakSet();
1171
+ ensureElement_fn = function() {
1172
+ if (__privateGet(this, _element)) return;
1173
+ if (typeof document === "undefined") return;
1174
+ const el = document.createElement("overlay-dialog");
1175
+ el.controller = __privateGet(this, _controller);
1176
+ document.body.appendChild(el);
1177
+ __privateSet(this, _element, el);
724
1178
  };
1179
+ var dialog = new DialogSingleton();
725
1180
  export {
726
- ATTRIBUTE_ANIMATION,
727
- LoadingOverlay,
728
- Modal,
729
- TaskListOverlay,
730
- withLoading
1181
+ DialogController,
1182
+ OverlayDialog,
1183
+ dialog
731
1184
  };
732
1185
  //# sourceMappingURL=index.js.map