@bendyline/docblocks-react 2.6.2 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +3 -10
  2. package/THIRD_PARTY_NOTICES.txt +4284 -3651
  3. package/dist/AiDiagrams-XWOM3YZX.js +13 -0
  4. package/dist/AiEditorAssistant-M4LI2CRY.js +771 -0
  5. package/dist/AudioExportDialog-F23QXNPB.js +253 -0
  6. package/dist/DocumentConflictDialog-5LRVIGXS.js +111 -0
  7. package/dist/ExportDialog-L6FG6W3H.js +9 -0
  8. package/dist/{ExportToolbarControls-QYLBRWM7.js → ExportToolbarControls-BRCPZ7H3.js} +108 -42
  9. package/dist/{GitUI-L5T3XHZG.js → GitUI-LZW3IUXO.js} +5 -5
  10. package/dist/PresentationMaker-622NHMIN.js +9 -0
  11. package/dist/QuickOpenDialog-2KK4SEBU.js +356 -0
  12. package/dist/Settings-DuXk6baH.d.ts +144 -0
  13. package/dist/{ShareDialog-4OX3UPJ4.js → ShareDialog-N7GPM4SB.js} +10 -59
  14. package/dist/SpeechToolbarControl-UZQKGQWD.js +434 -0
  15. package/dist/TextFromNarration-XYRAB6O2.js +598 -0
  16. package/dist/WorkspaceSearchPanel-OUQQ5DOM.js +328 -0
  17. package/dist/chunk-2IYBBFQX.js +201 -0
  18. package/dist/{chunk-LG6HAWCK.js → chunk-56KO2LYN.js} +35 -4
  19. package/dist/chunk-5OZBGIIT.js +1826 -0
  20. package/dist/chunk-6B7G33D5.js +88 -0
  21. package/dist/chunk-ARATARVJ.js +40 -0
  22. package/dist/{chunk-ODYNOPC3.js → chunk-BCQZTWE5.js} +57 -17
  23. package/dist/chunk-EA4BO6E4.js +73 -0
  24. package/dist/chunk-G4MFB3IO.js +208 -0
  25. package/dist/chunk-I44HKDQX.js +314 -0
  26. package/dist/chunk-K4I3NJFL.js +49 -0
  27. package/dist/chunk-KBDZDFPU.js +61 -0
  28. package/dist/{chunk-FNSRCCBC.js → chunk-KWG6UP5O.js} +0 -10
  29. package/dist/chunk-MMOTEQK6.js +518 -0
  30. package/dist/chunk-OINKNX26.js +574 -0
  31. package/dist/chunk-Q5ZVP5LV.js +1711 -0
  32. package/dist/chunk-RP3DVNTI.js +62 -0
  33. package/dist/{chunk-FMDLZ4ZG.js → chunk-SGQKTKDX.js} +2 -2
  34. package/dist/chunk-TBLZBNQF.js +25 -0
  35. package/dist/{chunk-M5Y5WO7Z.js → chunk-UG4G7PJ4.js} +1 -1
  36. package/dist/chunk-UONFWKJ7.js +22 -0
  37. package/dist/chunk-VO2JYYQZ.js +40 -0
  38. package/dist/{chunk-EBWYGTN7.js → chunk-X437UAF7.js} +6 -0
  39. package/dist/chunk-YS4HVWIO.js +63 -0
  40. package/dist/editor.d.ts +36 -1
  41. package/dist/editor.js +16 -1
  42. package/dist/export/index.d.ts +27 -11
  43. package/dist/export/index.js +11 -7
  44. package/dist/index.d.ts +100 -13
  45. package/dist/index.js +3916 -1633
  46. package/dist/{run-export-I2XOVGG4.js → run-export-47X5FTYZ.js} +3 -3
  47. package/dist/settings/index.d.ts +32 -101
  48. package/dist/settings/index.js +14 -2
  49. package/package.json +9 -9
  50. package/src/styles/docblocks.css +2332 -656
  51. package/dist/ExportDialog-Q4VWI43H.js +0 -9
  52. package/dist/chunk-JDJVRDOP.js +0 -92
  53. package/dist/chunk-Q6XNIKDG.js +0 -669
  54. package/dist/{GitToolbarControl-O5AXBC6T.js → GitToolbarControl-J4UYZDV6.js} +3 -3
@@ -0,0 +1,328 @@
1
+ import {
2
+ HighlightedText
3
+ } from "./chunk-TBLZBNQF.js";
4
+ import {
5
+ FilesIcon
6
+ } from "./chunk-EA4BO6E4.js";
7
+
8
+ // src/WorkspaceSearch/WorkspaceSearchPanel.tsx
9
+ import { useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
10
+
11
+ // src/WorkspaceSearch/useWorkspaceSearch.ts
12
+ import { useEffect, useRef, useState } from "react";
13
+ var EMPTY = { result: null, searching: false };
14
+ var QUERY_DEBOUNCE_MS = 200;
15
+ var INDEX_REFRESH_MS = 750;
16
+ function useWorkspaceSearch(index, generation, query) {
17
+ const [state, setState] = useState(EMPTY);
18
+ const controllerRef = useRef(null);
19
+ const runRef = useRef(null);
20
+ const refreshTimerRef = useRef(null);
21
+ const { text } = query;
22
+ const matchCase = query.matchCase ?? false;
23
+ const wholeWord = query.wholeWord ?? false;
24
+ useEffect(() => {
25
+ controllerRef.current?.abort();
26
+ controllerRef.current = null;
27
+ if (!index || !text.trim()) {
28
+ runRef.current = null;
29
+ setState(EMPTY);
30
+ return;
31
+ }
32
+ const run = () => {
33
+ controllerRef.current?.abort();
34
+ const controller = new AbortController();
35
+ controllerRef.current = controller;
36
+ index.search({ text, matchCase, wholeWord }, { signal: controller.signal }).then(
37
+ (result) => {
38
+ if (controllerRef.current === controller) setState({ result, searching: false });
39
+ },
40
+ () => {
41
+ if (controllerRef.current === controller) {
42
+ setState((current) => ({ ...current, searching: false }));
43
+ }
44
+ }
45
+ );
46
+ };
47
+ runRef.current = run;
48
+ setState((current) => ({ ...current, searching: true }));
49
+ const timer = setTimeout(run, QUERY_DEBOUNCE_MS);
50
+ return () => clearTimeout(timer);
51
+ }, [index, text, matchCase, wholeWord]);
52
+ useEffect(() => {
53
+ if (!runRef.current || refreshTimerRef.current !== null) return;
54
+ refreshTimerRef.current = setTimeout(() => {
55
+ refreshTimerRef.current = null;
56
+ runRef.current?.();
57
+ }, INDEX_REFRESH_MS);
58
+ }, [generation]);
59
+ useEffect(
60
+ () => () => {
61
+ controllerRef.current?.abort();
62
+ if (refreshTimerRef.current !== null) clearTimeout(refreshTimerRef.current);
63
+ },
64
+ []
65
+ );
66
+ return state;
67
+ }
68
+
69
+ // src/WorkspaceSearch/WorkspaceSearchPanel.tsx
70
+ import { jsx, jsxs } from "react/jsx-runtime";
71
+ function plural(count, noun) {
72
+ return `${count} ${noun}${count === 1 ? "" : "s"}`;
73
+ }
74
+ function statusText(index, status, query, result, searching) {
75
+ if (!index) return "Search is not available for this workspace.";
76
+ if (status.error) return status.error;
77
+ const indexing = status.pendingCount > 0 ? ` Indexing ${status.documentCount - status.pendingCount} of ${status.documentCount}\u2026` : "";
78
+ if (!query.trim()) {
79
+ return status.listed ? `Search ${plural(status.documentCount, "document")}.${indexing}` : "Listing workspace\u2026";
80
+ }
81
+ if (!result) return searching ? "Searching\u2026" : "";
82
+ if (result.totalDocuments === 0) return `No results.${indexing}`;
83
+ const found = `${plural(result.totalMatches, "result")} in ${plural(result.totalDocuments, "document")}`;
84
+ const shown = result.truncated && result.documents.length < result.totalDocuments ? ` (showing the first ${result.documents.length})` : result.truncated ? " (more not counted)" : "";
85
+ return `${found}${shown}.${indexing}`;
86
+ }
87
+ function folderOf(path) {
88
+ const slash = path.lastIndexOf("/");
89
+ return slash < 0 ? "" : path.slice(0, slash);
90
+ }
91
+ function WorkspaceSearchPanel({
92
+ index,
93
+ status,
94
+ hidden,
95
+ focusRequest,
96
+ onOpen,
97
+ onShowFiles
98
+ }) {
99
+ const inputRef = useRef2(null);
100
+ const rowRefs = useRef2(/* @__PURE__ */ new Map());
101
+ const [text, setText] = useState2("");
102
+ const [matchCase, setMatchCase] = useState2(false);
103
+ const [wholeWord, setWholeWord] = useState2(false);
104
+ const [collapsed, setCollapsed] = useState2(/* @__PURE__ */ new Set());
105
+ const [focusedKey, setFocusedKey] = useState2(null);
106
+ const query = useMemo(() => ({ text, matchCase, wholeWord }), [text, matchCase, wholeWord]);
107
+ const { result, searching } = useWorkspaceSearch(index, status.generation, query);
108
+ useEffect2(() => {
109
+ if (focusRequest === 0) return;
110
+ inputRef.current?.focus();
111
+ inputRef.current?.select();
112
+ }, [focusRequest]);
113
+ useEffect2(() => {
114
+ setCollapsed(/* @__PURE__ */ new Set());
115
+ }, [text, matchCase, wholeWord]);
116
+ const rows = useMemo(() => {
117
+ const flat = [];
118
+ for (const [position, document] of (result?.documents ?? []).entries()) {
119
+ flat.push({ kind: "document", key: `d:${document.path}`, document, position });
120
+ if (collapsed.has(document.path)) continue;
121
+ document.matches.forEach(
122
+ (match, matchPosition) => flat.push({
123
+ kind: "line",
124
+ key: `l:${document.path}:${matchPosition}`,
125
+ document,
126
+ match,
127
+ position: matchPosition
128
+ })
129
+ );
130
+ }
131
+ return flat;
132
+ }, [result, collapsed]);
133
+ const tabStop = rows.some((row) => row.key === focusedKey) ? focusedKey : rows[0]?.key ?? null;
134
+ const focusRow = (key) => {
135
+ setFocusedKey(key);
136
+ rowRefs.current.get(key)?.focus();
137
+ };
138
+ const open = (document, match) => onOpen({ path: document.path, match, query: text, matchCase, wholeWord });
139
+ const toggle = (path, expand) => setCollapsed((current) => {
140
+ const next = new Set(current);
141
+ const shouldExpand = expand ?? next.has(path);
142
+ if (shouldExpand) next.delete(path);
143
+ else next.add(path);
144
+ return next;
145
+ });
146
+ const onTreeKeyDown = (event) => {
147
+ const position = rows.findIndex((row2) => row2.key === tabStop);
148
+ const row = rows[position];
149
+ if (!row) return;
150
+ const isExpandable = row.kind === "document" && row.document.matches.length > 0;
151
+ const expanded = row.kind === "document" && !collapsed.has(row.document.path);
152
+ switch (event.key) {
153
+ case "ArrowDown":
154
+ if (position < rows.length - 1) focusRow(rows[position + 1].key);
155
+ break;
156
+ case "ArrowUp":
157
+ if (position > 0) focusRow(rows[position - 1].key);
158
+ break;
159
+ case "Home":
160
+ focusRow(rows[0].key);
161
+ break;
162
+ case "End":
163
+ focusRow(rows[rows.length - 1].key);
164
+ break;
165
+ case "ArrowRight":
166
+ if (isExpandable && !expanded) toggle(row.document.path, true);
167
+ else if (isExpandable && rows[position + 1]) focusRow(rows[position + 1].key);
168
+ break;
169
+ case "ArrowLeft":
170
+ if (row.kind === "line") focusRow(`d:${row.document.path}`);
171
+ else if (isExpandable && expanded) toggle(row.document.path, false);
172
+ break;
173
+ case "Enter":
174
+ case " ":
175
+ open(row.document, row.kind === "line" ? row.match : null);
176
+ break;
177
+ default:
178
+ return;
179
+ }
180
+ event.preventDefault();
181
+ };
182
+ const documentCount = result?.documents.length ?? 0;
183
+ return /* @__PURE__ */ jsxs("section", { className: "db-search-panel", hidden, "aria-label": "Search workspace", children: [
184
+ /* @__PURE__ */ jsxs("div", { className: "db-explorer-toolbar", children: [
185
+ /* @__PURE__ */ jsx("span", { className: "db-explorer-title", children: "Search" }),
186
+ /* @__PURE__ */ jsx("div", { className: "db-explorer-actions", children: /* @__PURE__ */ jsx(
187
+ "button",
188
+ {
189
+ type: "button",
190
+ className: "db-explorer-btn",
191
+ onClick: onShowFiles,
192
+ title: "Show files",
193
+ "aria-label": "Show files",
194
+ children: /* @__PURE__ */ jsx(FilesIcon, {})
195
+ }
196
+ ) })
197
+ ] }),
198
+ /* @__PURE__ */ jsxs("div", { className: "db-search-form", role: "search", children: [
199
+ /* @__PURE__ */ jsx(
200
+ "input",
201
+ {
202
+ ref: inputRef,
203
+ className: "db-search-input",
204
+ type: "search",
205
+ "aria-label": "Search workspace",
206
+ placeholder: "Search",
207
+ spellCheck: false,
208
+ value: text,
209
+ onChange: (event) => setText(event.target.value),
210
+ onKeyDown: (event) => {
211
+ if (event.key === "ArrowDown" && rows[0]) {
212
+ event.preventDefault();
213
+ focusRow(rows[0].key);
214
+ }
215
+ }
216
+ }
217
+ ),
218
+ /* @__PURE__ */ jsx(
219
+ "button",
220
+ {
221
+ type: "button",
222
+ className: "db-search-toggle",
223
+ "aria-pressed": matchCase,
224
+ "aria-label": "Match case",
225
+ title: "Match case",
226
+ onClick: () => setMatchCase((value) => !value),
227
+ children: "Aa"
228
+ }
229
+ ),
230
+ /* @__PURE__ */ jsx(
231
+ "button",
232
+ {
233
+ type: "button",
234
+ className: "db-search-toggle db-search-toggle--word",
235
+ "aria-pressed": wholeWord,
236
+ "aria-label": "Match whole word",
237
+ title: "Match whole word",
238
+ onClick: () => setWholeWord((value) => !value),
239
+ children: "ab"
240
+ }
241
+ )
242
+ ] }),
243
+ /* @__PURE__ */ jsx("p", { className: "db-search-status", role: "status", children: statusText(index, status, text, result, searching) }),
244
+ /* @__PURE__ */ jsx(
245
+ "div",
246
+ {
247
+ className: "db-search-results",
248
+ role: "tree",
249
+ "aria-label": "Search results",
250
+ onKeyDown: onTreeKeyDown,
251
+ children: rows.map((row) => {
252
+ const common = {
253
+ ref: (element) => {
254
+ if (element) rowRefs.current.set(row.key, element);
255
+ else rowRefs.current.delete(row.key);
256
+ },
257
+ role: "treeitem",
258
+ tabIndex: row.key === tabStop ? 0 : -1,
259
+ onFocus: () => setFocusedKey(row.key)
260
+ };
261
+ if (row.kind === "document") {
262
+ const { document: document2 } = row;
263
+ const expandable = document2.matches.length > 0;
264
+ const expanded = expandable && !collapsed.has(document2.path);
265
+ return /* @__PURE__ */ jsxs(
266
+ "div",
267
+ {
268
+ ...common,
269
+ className: "db-search-document",
270
+ "aria-level": 1,
271
+ "aria-setsize": documentCount,
272
+ "aria-posinset": row.position + 1,
273
+ "aria-expanded": expandable ? expanded : void 0,
274
+ onClick: () => open(document2, null),
275
+ children: [
276
+ /* @__PURE__ */ jsx(
277
+ "span",
278
+ {
279
+ className: "db-search-chevron",
280
+ "aria-hidden": "true",
281
+ onClick: (event) => {
282
+ event.stopPropagation();
283
+ if (expandable) toggle(document2.path);
284
+ },
285
+ children: expandable ? expanded ? "\u25BE" : "\u25B8" : ""
286
+ }
287
+ ),
288
+ /* @__PURE__ */ jsx("span", { className: "db-search-document-title", children: /* @__PURE__ */ jsx(HighlightedText, { text: document2.title, ranges: document2.titleRanges }) }),
289
+ /* @__PURE__ */ jsx("span", { className: "db-search-document-path", children: folderOf(document2.path) }),
290
+ document2.matchCount > 0 && /* @__PURE__ */ jsx(
291
+ "span",
292
+ {
293
+ className: "db-search-count",
294
+ "aria-label": plural(document2.matchCount, "match"),
295
+ children: document2.matchCount
296
+ }
297
+ )
298
+ ]
299
+ },
300
+ row.key
301
+ );
302
+ }
303
+ const { match, document } = row;
304
+ return /* @__PURE__ */ jsxs(
305
+ "div",
306
+ {
307
+ ...common,
308
+ className: "db-search-line",
309
+ "aria-level": 2,
310
+ "aria-setsize": document.matches.length,
311
+ "aria-posinset": row.position + 1,
312
+ onClick: () => open(document, match),
313
+ children: [
314
+ /* @__PURE__ */ jsx("span", { className: "db-search-line-number", children: match.line }),
315
+ /* @__PURE__ */ jsx("span", { className: "db-search-snippet", children: /* @__PURE__ */ jsx(HighlightedText, { text: match.snippet, ranges: match.ranges }) })
316
+ ]
317
+ },
318
+ row.key
319
+ );
320
+ })
321
+ }
322
+ ),
323
+ (result?.documents ?? []).some((document) => document.moreMatches) && /* @__PURE__ */ jsx("p", { className: "db-search-more", children: "Some documents have more matches; open them to see all." })
324
+ ] });
325
+ }
326
+ export {
327
+ WorkspaceSearchPanel
328
+ };
@@ -0,0 +1,201 @@
1
+ // src/Speech/progressive-player.ts
2
+ var ProgressivePlayer = class _ProgressivePlayer {
3
+ constructor(context, onChange) {
4
+ this.context = context;
5
+ this.onChange = onChange;
6
+ this.chunks = [];
7
+ this.pending = /* @__PURE__ */ new Map();
8
+ this.sources = /* @__PURE__ */ new Set();
9
+ this.bufferedDuration = 0;
10
+ this.playbackOffset = 0;
11
+ this.playbackStartedAt = 0;
12
+ this.playing = false;
13
+ this.complete = false;
14
+ this.disposed = false;
15
+ this.endTimer = null;
16
+ }
17
+ static create(onChange) {
18
+ const Context = globalThis.AudioContext;
19
+ if (!Context) return null;
20
+ try {
21
+ return new _ProgressivePlayer(new Context(), onChange);
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+ /** Call synchronously from the click that starts playback, to keep audio permission. */
27
+ prime() {
28
+ void this.context.resume().catch(() => void 0);
29
+ }
30
+ append(chunk) {
31
+ if (this.disposed || chunk.index < this.chunks.length || this.pending.has(chunk.index)) return;
32
+ const samples = new Float32Array(chunk.pcm);
33
+ if (samples.length === 0) return;
34
+ const buffer = this.context.createBuffer(1, samples.length, chunk.sampleRate);
35
+ buffer.copyToChannel(samples, 0);
36
+ this.pending.set(chunk.index, buffer);
37
+ while (this.pending.has(this.chunks.length)) {
38
+ const index = this.chunks.length;
39
+ const next = this.pending.get(index);
40
+ this.pending.delete(index);
41
+ const scheduled = { index, start: this.bufferedDuration, buffer: next };
42
+ this.chunks.push(scheduled);
43
+ this.bufferedDuration += next.duration;
44
+ if (this.playing) this.schedule(scheduled);
45
+ }
46
+ this.refreshEndTimer();
47
+ this.emit();
48
+ }
49
+ async play() {
50
+ if (this.disposed || this.bufferedDuration <= 0) return false;
51
+ if (this.playing) return true;
52
+ if (this.playbackOffset >= this.bufferedDuration - 0.02) {
53
+ if (!this.complete) {
54
+ this.emit();
55
+ return false;
56
+ }
57
+ this.playbackOffset = 0;
58
+ }
59
+ await this.context.resume();
60
+ this.stopSources();
61
+ this.playing = true;
62
+ this.playbackStartedAt = this.context.currentTime + 0.025;
63
+ for (const chunk of this.chunks) this.schedule(chunk);
64
+ this.refreshEndTimer();
65
+ this.emit();
66
+ return true;
67
+ }
68
+ pause() {
69
+ if (!this.playing) return;
70
+ this.playbackOffset = this.currentTime();
71
+ this.playing = false;
72
+ this.stopSources();
73
+ this.clearEndTimer();
74
+ this.emit();
75
+ }
76
+ seek(seconds) {
77
+ const wasPlaying = this.playing;
78
+ this.playbackOffset = Math.max(0, Math.min(this.bufferedDuration, seconds));
79
+ this.playing = false;
80
+ this.stopSources();
81
+ this.clearEndTimer();
82
+ if (wasPlaying) void this.play();
83
+ else this.emit();
84
+ }
85
+ /** No more chunks will arrive. */
86
+ finish() {
87
+ this.complete = true;
88
+ this.refreshEndTimer();
89
+ this.emit();
90
+ }
91
+ snapshot() {
92
+ const currentTime = this.currentTime();
93
+ return {
94
+ currentTime,
95
+ bufferedDuration: this.bufferedDuration,
96
+ isPlaying: this.playing,
97
+ waitingForAudio: !this.complete && !this.playing && this.bufferedDuration > 0 && currentTime >= this.bufferedDuration - 0.05,
98
+ complete: this.complete,
99
+ currentChunk: this.chunkAt(currentTime)
100
+ };
101
+ }
102
+ dispose() {
103
+ if (this.disposed) return;
104
+ this.disposed = true;
105
+ this.playing = false;
106
+ this.stopSources();
107
+ this.clearEndTimer();
108
+ void this.context.close().catch(() => void 0);
109
+ }
110
+ chunkAt(time) {
111
+ let found = null;
112
+ for (const chunk of this.chunks) {
113
+ if (chunk.start <= time + 1e-3) found = chunk.index;
114
+ else break;
115
+ }
116
+ return found;
117
+ }
118
+ currentTime() {
119
+ if (!this.playing) return this.playbackOffset;
120
+ return Math.min(
121
+ this.bufferedDuration,
122
+ this.playbackOffset + Math.max(0, this.context.currentTime - this.playbackStartedAt)
123
+ );
124
+ }
125
+ schedule(chunk) {
126
+ const chunkEnd = chunk.start + chunk.buffer.duration;
127
+ if (!this.playing || chunkEnd <= this.playbackOffset + 5e-3) return;
128
+ let sourceOffset = Math.max(0, this.playbackOffset - chunk.start);
129
+ let when = this.playbackStartedAt + Math.max(0, chunk.start - this.playbackOffset);
130
+ if (when < this.context.currentTime) {
131
+ sourceOffset += this.context.currentTime - when;
132
+ when = this.context.currentTime;
133
+ }
134
+ if (sourceOffset >= chunk.buffer.duration - 5e-3) return;
135
+ const source = this.context.createBufferSource();
136
+ source.buffer = chunk.buffer;
137
+ source.connect(this.context.destination);
138
+ source.addEventListener("ended", () => this.sources.delete(source), { once: true });
139
+ this.sources.add(source);
140
+ source.start(when, sourceOffset);
141
+ }
142
+ refreshEndTimer() {
143
+ this.clearEndTimer();
144
+ if (!this.playing) return;
145
+ const remainingMs = Math.max(20, (this.bufferedDuration - this.currentTime()) * 1e3 + 40);
146
+ this.endTimer = setTimeout(() => {
147
+ if (!this.playing) return;
148
+ if (this.currentTime() < this.bufferedDuration - 0.05) {
149
+ this.refreshEndTimer();
150
+ return;
151
+ }
152
+ this.playbackOffset = this.bufferedDuration;
153
+ this.playing = false;
154
+ this.stopSources();
155
+ this.clearEndTimer();
156
+ this.emit();
157
+ }, remainingMs);
158
+ }
159
+ stopSources() {
160
+ for (const source of this.sources) {
161
+ try {
162
+ source.stop();
163
+ } catch {
164
+ }
165
+ }
166
+ this.sources.clear();
167
+ }
168
+ clearEndTimer() {
169
+ if (this.endTimer) clearTimeout(this.endTimer);
170
+ this.endTimer = null;
171
+ }
172
+ emit() {
173
+ if (!this.disposed) this.onChange(this.snapshot());
174
+ }
175
+ };
176
+ var AutoPlayGate = class {
177
+ constructor() {
178
+ this.firstAt = null;
179
+ this.firstDuration = 0;
180
+ this.firstCharacters = 0;
181
+ }
182
+ /** Call after each appended chunk; true when it is safe to start playing. */
183
+ ready(snapshot, progress, nowMs) {
184
+ if (this.firstAt === null) {
185
+ this.firstAt = nowMs;
186
+ this.firstDuration = snapshot.bufferedDuration;
187
+ this.firstCharacters = progress?.completedCharacters ?? 0;
188
+ }
189
+ const elapsed = (nowMs - this.firstAt) / 1e3;
190
+ const audioRate = elapsed > 0.25 ? (snapshot.bufferedDuration - this.firstDuration) / elapsed : 0;
191
+ const characterRate = elapsed > 0.25 ? ((progress?.completedCharacters ?? 0) - this.firstCharacters) / elapsed : 0;
192
+ const remaining = progress && characterRate > 0 ? (progress.totalCharacters - progress.completedCharacters) / characterRate : Number.POSITIVE_INFINITY;
193
+ const ahead = snapshot.bufferedDuration - snapshot.currentTime;
194
+ return ahead >= 4 && (audioRate >= 1.15 || ahead >= remaining + 3);
195
+ }
196
+ };
197
+
198
+ export {
199
+ ProgressivePlayer,
200
+ AutoPlayGate
201
+ };
@@ -1,3 +1,22 @@
1
+ // src/components/dialog-stack.ts
2
+ var closers = [];
3
+ function registerDialogCloser(close) {
4
+ closers.push(close);
5
+ return () => {
6
+ const index = closers.indexOf(close);
7
+ if (index >= 0) closers.splice(index, 1);
8
+ };
9
+ }
10
+ function isTopmostDialog(close) {
11
+ return closers[closers.length - 1] === close;
12
+ }
13
+ function dismissTopmostDialog() {
14
+ const close = closers[closers.length - 1];
15
+ if (!close) return false;
16
+ close();
17
+ return true;
18
+ }
19
+
1
20
  // src/components/Dialog.tsx
2
21
  import { useCallback, useEffect, useId, useRef } from "react";
3
22
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -11,13 +30,23 @@ function Dialog({
11
30
  closeOnBackdrop = true,
12
31
  className,
13
32
  bodyClassName,
14
- footerClassName
33
+ footerClassName,
34
+ placement = "center",
35
+ titleHidden = false
15
36
  }) {
16
37
  const closeButtonRef = useRef(null);
17
38
  const dialogRef = useRef(null);
18
39
  const titleId = useId();
40
+ const closeRef = useRef(onClose);
41
+ closeRef.current = onClose;
42
+ const registeredClose = useRef(() => closeRef.current());
43
+ useEffect(() => {
44
+ const close = registeredClose.current;
45
+ return registerDialogCloser(close);
46
+ }, []);
19
47
  useEffect(() => {
20
48
  const onKey = (e) => {
49
+ if (!isTopmostDialog(registeredClose.current)) return;
21
50
  if (e.key === "Escape") {
22
51
  e.preventDefault();
23
52
  onClose();
@@ -49,7 +78,7 @@ function Dialog({
49
78
  }, [onClose]);
50
79
  useEffect(() => {
51
80
  const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
52
- const target = initialFocusRef?.current ?? closeButtonRef.current;
81
+ const target = initialFocusRef?.current ?? closeButtonRef.current ?? dialogRef.current;
53
82
  target?.focus();
54
83
  return () => {
55
84
  if (previousFocus?.isConnected) previousFocus.focus();
@@ -65,7 +94,8 @@ function Dialog({
65
94
  const panelClassName = ["db-dialog", sizeClass, className].filter(Boolean).join(" ");
66
95
  const bodyClasses = ["db-dialog-body", bodyClassName].filter(Boolean).join(" ");
67
96
  const footerClasses = ["db-dialog-footer", footerClassName].filter(Boolean).join(" ");
68
- return /* @__PURE__ */ jsx("div", { className: "db-dialog-overlay", onClick: handleBackdropClick, children: /* @__PURE__ */ jsxs(
97
+ const overlayClassName = placement === "top" ? "db-dialog-overlay db-dialog-overlay--top" : "db-dialog-overlay";
98
+ return /* @__PURE__ */ jsx("div", { className: overlayClassName, onClick: handleBackdropClick, children: /* @__PURE__ */ jsxs(
69
99
  "div",
70
100
  {
71
101
  ref: dialogRef,
@@ -75,7 +105,7 @@ function Dialog({
75
105
  "aria-labelledby": titleId,
76
106
  tabIndex: -1,
77
107
  children: [
78
- /* @__PURE__ */ jsxs("div", { className: "db-dialog-header", children: [
108
+ titleHidden ? /* @__PURE__ */ jsx("h2", { id: titleId, className: "db-visually-hidden", children: title }) : /* @__PURE__ */ jsxs("div", { className: "db-dialog-header", children: [
79
109
  /* @__PURE__ */ jsx("h2", { id: titleId, className: "db-dialog-title", children: title }),
80
110
  /* @__PURE__ */ jsx(
81
111
  "button",
@@ -105,5 +135,6 @@ var FOCUSABLE_SELECTOR = [
105
135
  ].join(",");
106
136
 
107
137
  export {
138
+ dismissTopmostDialog,
108
139
  Dialog
109
140
  };