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