efficient-shell 4.134.0 → 4.137.1

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 (171) hide show
  1. package/LICENSE +6 -17
  2. package/README.md +84 -8
  3. package/dist/Browser-ZX5OTOKK.js +43 -0
  4. package/dist/Calculator-4SQGOQFQ.js +29 -0
  5. package/dist/ConfirmDialog-NPKW2XHM.js +1 -0
  6. package/dist/CurrencyConverter-2KOM7L6V.js +21 -0
  7. package/dist/Documents-IZTIQYPG.js +52 -0
  8. package/dist/Files-B63KKXGZ.js +89 -0
  9. package/dist/Notepad-WA77V45J.js +25 -0
  10. package/dist/PdfViewer-RBRGEKWF.js +1 -0
  11. package/dist/PomodoroTimer-SDUBKXUS.js +99 -0
  12. package/dist/Preview-D4KOA4OI.js +187 -0
  13. package/dist/Sidebar-Y57LYXXI.js +23 -0
  14. package/dist/Spreadsheet-JX5E6WH6.js +37 -0
  15. package/dist/Stock-XAQ5WMUZ.js +16 -0
  16. package/dist/Weather-HLAGJ5LV.js +60 -0
  17. package/dist/WorldClock-IRI4VCTA.js +32 -0
  18. package/dist/apps/index.js +1 -151
  19. package/dist/brand.css +1 -1
  20. package/dist/chunk-3A46NCXK.js +29 -0
  21. package/dist/chunk-44UO7FYU.js +11 -0
  22. package/dist/chunk-466CM2TJ.js +1 -0
  23. package/dist/chunk-57MIENLJ.js +1 -0
  24. package/dist/chunk-5CRAF2AD.js +2 -0
  25. package/dist/chunk-A2OQLLYK.js +6 -0
  26. package/dist/chunk-B43AGQET.js +4 -0
  27. package/dist/chunk-BR22X7E5.js +1 -0
  28. package/dist/chunk-BUTSU7FO.js +1 -0
  29. package/dist/chunk-D65T4H6J.js +1 -0
  30. package/dist/chunk-ESL5TDNA.js +1 -0
  31. package/dist/chunk-FBDZGO3V.js +1 -0
  32. package/dist/chunk-FHTLC7HH.js +1 -0
  33. package/dist/chunk-FQLMX3IE.js +948 -0
  34. package/dist/chunk-H562R4GX.js +34 -0
  35. package/dist/chunk-JN4WXTWJ.js +106 -0
  36. package/dist/chunk-LVDRBRYU.js +1 -0
  37. package/dist/chunk-LWLCNQY7.js +1 -0
  38. package/dist/chunk-MAIHBBWT.js +1 -0
  39. package/dist/chunk-NPJH5MD7.js +13 -0
  40. package/dist/chunk-OCSOMMY2.js +1 -0
  41. package/dist/chunk-OQDWLHZI.js +1 -0
  42. package/dist/chunk-OWSAMA3I.js +23 -0
  43. package/dist/chunk-PH3PRIFP.js +5 -0
  44. package/dist/chunk-TAWKWSPN.js +9 -0
  45. package/dist/chunk-U4GTZPXP.js +1 -0
  46. package/dist/chunk-UB4JIOLD.js +2 -0
  47. package/dist/chunk-UZAJ5BF6.js +9 -0
  48. package/dist/chunk-VEGMDXT4.js +1 -0
  49. package/dist/chunk-VTVSMRFX.js +1 -0
  50. package/dist/chunk-VVAGSKW7.js +1 -0
  51. package/dist/chunk-XO43FJRM.js +19 -0
  52. package/dist/chunk-Y7BOLDRM.js +69 -0
  53. package/dist/file-intake/index.js +1 -4
  54. package/dist/{index-DZ7E7bCl.d.ts → index-DVI9DMSu.d.ts} +189 -18
  55. package/dist/index.d.ts +2 -2
  56. package/dist/index.js +639 -7473
  57. package/dist/map/index.js +20 -540
  58. package/dist/markdown/index.js +6 -184
  59. package/dist/markup/index.js +1 -135
  60. package/dist/page-tabs/index.js +21 -709
  61. package/dist/sounds-OMHXVVQ4.js +1 -0
  62. package/dist/ui/index.d.ts +1 -1
  63. package/dist/ui/index.js +1 -19
  64. package/dist/ui.css +78 -10
  65. package/package.json +4 -3
  66. package/dist/Browser-O3QZGUF2.js +0 -482
  67. package/dist/Browser-O3QZGUF2.js.map +0 -1
  68. package/dist/Calculator-L6N5X3KU.js +0 -192
  69. package/dist/Calculator-L6N5X3KU.js.map +0 -1
  70. package/dist/ConfirmDialog-LYSSZSUD.js +0 -4
  71. package/dist/ConfirmDialog-LYSSZSUD.js.map +0 -1
  72. package/dist/CurrencyConverter-64MKXB3D.js +0 -236
  73. package/dist/CurrencyConverter-64MKXB3D.js.map +0 -1
  74. package/dist/Documents-5WXXYCLI.js +0 -441
  75. package/dist/Documents-5WXXYCLI.js.map +0 -1
  76. package/dist/Files-AUURUUDN.js +0 -805
  77. package/dist/Files-AUURUUDN.js.map +0 -1
  78. package/dist/Notepad-GPXJVPB7.js +0 -410
  79. package/dist/Notepad-GPXJVPB7.js.map +0 -1
  80. package/dist/PdfViewer-HKO6P4SR.js +0 -5
  81. package/dist/PdfViewer-HKO6P4SR.js.map +0 -1
  82. package/dist/PomodoroTimer-4TQBWLP2.js +0 -633
  83. package/dist/PomodoroTimer-4TQBWLP2.js.map +0 -1
  84. package/dist/Preview-FOZYPKM2.js +0 -2959
  85. package/dist/Preview-FOZYPKM2.js.map +0 -1
  86. package/dist/Sidebar-BPU7RCZP.js +0 -291
  87. package/dist/Sidebar-BPU7RCZP.js.map +0 -1
  88. package/dist/Spreadsheet-3C66BBBF.js +0 -519
  89. package/dist/Spreadsheet-3C66BBBF.js.map +0 -1
  90. package/dist/Stock-IF4IHYLU.js +0 -158
  91. package/dist/Stock-IF4IHYLU.js.map +0 -1
  92. package/dist/Weather-725PFR7T.js +0 -431
  93. package/dist/Weather-725PFR7T.js.map +0 -1
  94. package/dist/WorldClock-UU4NZIQ7.js +0 -202
  95. package/dist/WorldClock-UU4NZIQ7.js.map +0 -1
  96. package/dist/apps/index.js.map +0 -1
  97. package/dist/chunk-2TPTHAKI.js +0 -316
  98. package/dist/chunk-2TPTHAKI.js.map +0 -1
  99. package/dist/chunk-4P44JXDS.js +0 -195
  100. package/dist/chunk-4P44JXDS.js.map +0 -1
  101. package/dist/chunk-5O42JXQE.js +0 -26
  102. package/dist/chunk-5O42JXQE.js.map +0 -1
  103. package/dist/chunk-72CQWN7O.js +0 -547
  104. package/dist/chunk-72CQWN7O.js.map +0 -1
  105. package/dist/chunk-7CCHEEYC.js +0 -94
  106. package/dist/chunk-7CCHEEYC.js.map +0 -1
  107. package/dist/chunk-BE7FZOFU.js +0 -17
  108. package/dist/chunk-BE7FZOFU.js.map +0 -1
  109. package/dist/chunk-D276EMXS.js +0 -57
  110. package/dist/chunk-D276EMXS.js.map +0 -1
  111. package/dist/chunk-D7PYW2QS.js +0 -265
  112. package/dist/chunk-D7PYW2QS.js.map +0 -1
  113. package/dist/chunk-DVZWYWSD.js +0 -26
  114. package/dist/chunk-DVZWYWSD.js.map +0 -1
  115. package/dist/chunk-ET6ID4LP.js +0 -102
  116. package/dist/chunk-ET6ID4LP.js.map +0 -1
  117. package/dist/chunk-GA35NEND.js +0 -56
  118. package/dist/chunk-GA35NEND.js.map +0 -1
  119. package/dist/chunk-GIGV2ZRW.js +0 -70
  120. package/dist/chunk-GIGV2ZRW.js.map +0 -1
  121. package/dist/chunk-IPXN646P.js +0 -14
  122. package/dist/chunk-IPXN646P.js.map +0 -1
  123. package/dist/chunk-IRWSRJ4K.js +0 -48
  124. package/dist/chunk-IRWSRJ4K.js.map +0 -1
  125. package/dist/chunk-IULP5BCD.js +0 -79
  126. package/dist/chunk-IULP5BCD.js.map +0 -1
  127. package/dist/chunk-MGGY74RY.js +0 -14
  128. package/dist/chunk-MGGY74RY.js.map +0 -1
  129. package/dist/chunk-OLJDJYSU.js +0 -13678
  130. package/dist/chunk-OLJDJYSU.js.map +0 -1
  131. package/dist/chunk-PIGDNN5F.js +0 -46
  132. package/dist/chunk-PIGDNN5F.js.map +0 -1
  133. package/dist/chunk-Q2MJVSJT.js +0 -7
  134. package/dist/chunk-Q2MJVSJT.js.map +0 -1
  135. package/dist/chunk-Q3LT745N.js +0 -3710
  136. package/dist/chunk-Q3LT745N.js.map +0 -1
  137. package/dist/chunk-QAGQRNKI.js +0 -145
  138. package/dist/chunk-QAGQRNKI.js.map +0 -1
  139. package/dist/chunk-QRL64J5F.js +0 -503
  140. package/dist/chunk-QRL64J5F.js.map +0 -1
  141. package/dist/chunk-QSKCQUZE.js +0 -1103
  142. package/dist/chunk-QSKCQUZE.js.map +0 -1
  143. package/dist/chunk-R4OQIQYX.js +0 -153
  144. package/dist/chunk-R4OQIQYX.js.map +0 -1
  145. package/dist/chunk-RT6DSB5Y.js +0 -143
  146. package/dist/chunk-RT6DSB5Y.js.map +0 -1
  147. package/dist/chunk-SHQM5CK3.js +0 -549
  148. package/dist/chunk-SHQM5CK3.js.map +0 -1
  149. package/dist/chunk-T4VQHMG4.js +0 -822
  150. package/dist/chunk-T4VQHMG4.js.map +0 -1
  151. package/dist/chunk-TRCWINHF.js +0 -105
  152. package/dist/chunk-TRCWINHF.js.map +0 -1
  153. package/dist/chunk-TW7LPMVR.js +0 -16
  154. package/dist/chunk-TW7LPMVR.js.map +0 -1
  155. package/dist/chunk-W622KQTD.js +0 -465
  156. package/dist/chunk-W622KQTD.js.map +0 -1
  157. package/dist/chunk-X4Q6G253.js +0 -416
  158. package/dist/chunk-X4Q6G253.js.map +0 -1
  159. package/dist/chunk-YNKK7NE3.js +0 -171
  160. package/dist/chunk-YNKK7NE3.js.map +0 -1
  161. package/dist/chunk-YPO6RDMX.js +0 -20
  162. package/dist/chunk-YPO6RDMX.js.map +0 -1
  163. package/dist/file-intake/index.js.map +0 -1
  164. package/dist/index.js.map +0 -1
  165. package/dist/map/index.js.map +0 -1
  166. package/dist/markdown/index.js.map +0 -1
  167. package/dist/markup/index.js.map +0 -1
  168. package/dist/page-tabs/index.js.map +0 -1
  169. package/dist/sounds-NT4DEZGD.js +0 -3
  170. package/dist/sounds-NT4DEZGD.js.map +0 -1
  171. package/dist/ui/index.js.map +0 -1
@@ -1,145 +0,0 @@
1
- import { DEFAULT_SHELL_STRINGS, useShellStrings } from './chunk-72CQWN7O.js';
2
- import { useRef, useState, useCallback } from 'react';
3
- import { jsx } from 'react/jsx-runtime';
4
-
5
- function acceptsFile(file, accept) {
6
- const rules = (accept ?? "").split(",").map((rule) => rule.trim().toLocaleLowerCase()).filter(Boolean);
7
- if (rules.length === 0 || rules.includes("*") || rules.includes("*/*")) return true;
8
- const fileName = file.name.toLocaleLowerCase();
9
- const fileType = file.type.toLocaleLowerCase();
10
- return rules.some((rule) => {
11
- if (rule.startsWith(".")) return fileName.endsWith(rule);
12
- if (rule.endsWith("/*")) return fileType.startsWith(rule.slice(0, -1));
13
- return fileType === rule;
14
- });
15
- }
16
- var humanSize = (bytes, strings = DEFAULT_SHELL_STRINGS.files) => {
17
- if (bytes < 1024) return strings.bytes(bytes);
18
- if (bytes < 1024 * 1024) return strings.kilobytes(Math.round(bytes / 1024));
19
- return strings.megabytes(bytes / (1024 * 1024));
20
- };
21
- var isFileDrag = (e) => Array.from(e.dataTransfer?.types ?? []).includes("Files");
22
- function pastedFiles(e) {
23
- const data = e.clipboardData;
24
- if (!data) return [];
25
- if (data.files && data.files.length > 0) return Array.from(data.files);
26
- const out = [];
27
- for (const item of Array.from(data.items ?? [])) {
28
- if (item.kind === "file") {
29
- const file = item.getAsFile();
30
- if (file) out.push(file);
31
- }
32
- }
33
- return out;
34
- }
35
- function useFileIntake({
36
- onAccept,
37
- accept,
38
- maxSizeBytes,
39
- maxFiles,
40
- multiple = true,
41
- currentCount = 0,
42
- acceptHint,
43
- disabled = false
44
- }) {
45
- const strings = useShellStrings().files;
46
- const inputRef = useRef(null);
47
- const [dragOver, setDragOver] = useState(false);
48
- const [rejections, setRejections] = useState([]);
49
- const depthRef = useRef(0);
50
- const hintText = typeof acceptHint === "string" ? acceptHint : "";
51
- const take = useCallback((incoming) => {
52
- if (disabled || !incoming) return;
53
- const candidates = Array.from(incoming);
54
- if (candidates.length === 0) return;
55
- const accepted = [];
56
- const rejected = [];
57
- for (const file of multiple ? candidates : candidates.slice(0, 1)) {
58
- if (!acceptsFile(file, accept)) {
59
- rejected.push({
60
- file,
61
- reason: "type",
62
- message: strings.notAccepted(file.name, hintText)
63
- });
64
- continue;
65
- }
66
- if (maxSizeBytes != null && file.size > maxSizeBytes) {
67
- rejected.push({
68
- file,
69
- reason: "size",
70
- message: strings.tooLarge(file.name, humanSize(file.size, strings), humanSize(maxSizeBytes, strings))
71
- });
72
- continue;
73
- }
74
- if (multiple && maxFiles != null && currentCount + accepted.length >= maxFiles) {
75
- rejected.push({
76
- file,
77
- reason: "count",
78
- message: strings.tooMany(file.name, maxFiles)
79
- });
80
- continue;
81
- }
82
- accepted.push(file);
83
- }
84
- setRejections(rejected);
85
- if (accepted.length) onAccept(accepted);
86
- if (inputRef.current) inputRef.current.value = "";
87
- }, [disabled, multiple, accept, hintText, maxSizeBytes, maxFiles, currentCount, onAccept, strings]);
88
- const open = useCallback(() => {
89
- if (!disabled) inputRef.current?.click();
90
- }, [disabled]);
91
- const clearRejections = useCallback(() => setRejections([]), []);
92
- const inputProps = {
93
- ref: inputRef,
94
- type: "file",
95
- accept: accept || void 0,
96
- multiple,
97
- disabled,
98
- tabIndex: -1,
99
- "aria-hidden": true,
100
- className: "hidden",
101
- onChange: (e) => take(e.target.files)
102
- };
103
- const zoneProps = disabled ? {} : {
104
- onDragEnter: (e) => {
105
- if (!isFileDrag(e)) return;
106
- e.preventDefault();
107
- depthRef.current += 1;
108
- setDragOver(true);
109
- },
110
- onDragOver: (e) => {
111
- if (!isFileDrag(e)) return;
112
- e.preventDefault();
113
- if (!dragOver) setDragOver(true);
114
- },
115
- onDragLeave: (e) => {
116
- if (!isFileDrag(e)) return;
117
- depthRef.current = Math.max(0, depthRef.current - 1);
118
- if (depthRef.current === 0) setDragOver(false);
119
- },
120
- onDrop: (e) => {
121
- if (!isFileDrag(e)) return;
122
- e.preventDefault();
123
- depthRef.current = 0;
124
- setDragOver(false);
125
- take(e.dataTransfer.files);
126
- }
127
- };
128
- const pasteProps = disabled ? {} : {
129
- onPaste: (e) => {
130
- const files = pastedFiles(e);
131
- if (files.length === 0) return;
132
- e.preventDefault();
133
- take(files);
134
- }
135
- };
136
- return { inputRef, inputProps, zoneProps, pasteProps, open, take, dragOver, rejections, clearRejections };
137
- }
138
- function FileIntakeAlert({ rejections, className = "mt-2" }) {
139
- if (rejections.length === 0) return null;
140
- return /* @__PURE__ */ jsx("ul", { role: "alert", className: `${className} flex flex-col gap-0.5`, children: rejections.map(({ file, message }, i) => /* @__PURE__ */ jsx("li", { className: "text-xs text-red-600", children: message }, `${file.name}-${i}`)) });
141
- }
142
-
143
- export { FileIntakeAlert, acceptsFile, humanSize, useFileIntake };
144
- //# sourceMappingURL=chunk-QAGQRNKI.js.map
145
- //# sourceMappingURL=chunk-QAGQRNKI.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/forms/useFileIntake.tsx"],"names":[],"mappings":";;;;AAsEO,SAAS,WAAA,CAAY,MAAY,MAAA,EAA0B;AAChE,EAAA,MAAM,KAAA,GAAA,CAAS,MAAA,IAAU,EAAA,EAAI,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,CAAI,CAAA,IAAA,KAAQ,IAAA,CAAK,MAAK,CAAE,iBAAA,EAAmB,CAAA,CAAE,OAAO,OAAO,CAAA;AACnG,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,IAAK,KAAA,CAAM,QAAA,CAAS,GAAG,CAAA,IAAK,KAAA,CAAM,QAAA,CAAS,KAAK,CAAA,EAAG,OAAO,IAAA;AAC/E,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,iBAAA,EAAkB;AAC7C,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,iBAAA,EAAkB;AAC7C,EAAA,OAAO,KAAA,CAAM,KAAK,CAAA,IAAA,KAAQ;AACxB,IAAA,IAAI,KAAK,UAAA,CAAW,GAAG,GAAG,OAAO,QAAA,CAAS,SAAS,IAAI,CAAA;AACvD,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA,EAAG,OAAO,QAAA,CAAS,UAAA,CAAW,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA;AACrE,IAAA,OAAO,QAAA,KAAa,IAAA;AAAA,EACtB,CAAC,CAAA;AACH;AAGO,IAAM,SAAA,GAAY,CAAC,KAAA,EAAe,OAAA,GAAiC,sBAAsB,KAAA,KAAkB;AAChH,EAAA,IAAI,KAAA,GAAQ,IAAA,EAAM,OAAO,OAAA,CAAQ,MAAM,KAAK,CAAA;AAC5C,EAAA,IAAI,KAAA,GAAQ,IAAA,GAAO,IAAA,EAAM,OAAO,OAAA,CAAQ,UAAU,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,IAAI,CAAC,CAAA;AAC1E,EAAA,OAAO,OAAA,CAAQ,SAAA,CAAU,KAAA,IAAS,IAAA,GAAO,IAAA,CAAK,CAAA;AAChD;AAGO,IAAM,UAAA,GAAa,CAAC,CAAA,KACzB,KAAA,CAAM,IAAA,CAAK,CAAA,CAAE,YAAA,EAAc,KAAA,IAAS,EAAE,CAAA,CAAE,QAAA,CAAS,OAAO,CAAA;AAGnD,SAAS,YAAY,CAAA,EAA2B;AACrD,EAAA,MAAM,OAAO,CAAA,CAAE,aAAA;AACf,EAAA,IAAI,CAAC,IAAA,EAAM,OAAO,EAAC;AACnB,EAAA,IAAI,IAAA,CAAK,KAAA,IAAS,IAAA,CAAK,KAAA,CAAM,MAAA,GAAS,GAAG,OAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,KAAK,CAAA;AACrE,EAAA,MAAM,MAAc,EAAC;AACrB,EAAA,KAAA,MAAW,QAAQ,KAAA,CAAM,IAAA,CAAK,KAAK,KAAA,IAAS,EAAE,CAAA,EAAG;AAC/C,IAAA,IAAI,IAAA,CAAK,SAAS,MAAA,EAAQ;AACxB,MAAA,MAAM,IAAA,GAAO,KAAK,SAAA,EAAU;AAC5B,MAAA,IAAI,IAAA,EAAM,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA;AAAA,IACzB;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,aAAA,CAAc;AAAA,EAC5B,QAAA;AAAA,EAAU,MAAA;AAAA,EAAQ,YAAA;AAAA,EAAc,QAAA;AAAA,EAAU,QAAA,GAAW,IAAA;AAAA,EAAM,YAAA,GAAe,CAAA;AAAA,EAC1E,UAAA;AAAA,EAAY,QAAA,GAAW;AACzB,CAAA,EAAsB;AACpB,EAAA,MAAM,OAAA,GAAU,iBAAgB,CAAE,KAAA;AAClC,EAAA,MAAM,QAAA,GAAW,OAAyB,IAAI,CAAA;AAC9C,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,KAAK,CAAA;AAC9C,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,QAAA,CAA0B,EAAE,CAAA;AAGhE,EAAA,MAAM,QAAA,GAAW,OAAO,CAAC,CAAA;AAEzB,EAAA,MAAM,QAAA,GAAW,OAAO,UAAA,KAAe,QAAA,GAAW,UAAA,GAAa,EAAA;AAG/D,EAAA,MAAM,IAAA,GAAO,WAAA,CAAY,CAAC,QAAA,KAAmD;AAC3E,IAAA,IAAI,QAAA,IAAY,CAAC,QAAA,EAAU;AAC3B,IAAA,MAAM,UAAA,GAAa,KAAA,CAAM,IAAA,CAAK,QAAQ,CAAA;AACtC,IAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC7B,IAAA,MAAM,WAAmB,EAAC;AAC1B,IAAA,MAAM,WAA4B,EAAC;AACnC,IAAA,KAAA,MAAW,QAAQ,QAAA,GAAW,UAAA,GAAa,WAAW,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,EAAG;AACjE,MAAA,IAAI,CAAC,WAAA,CAAY,IAAA,EAAM,MAAM,CAAA,EAAG;AAC9B,QAAA,QAAA,CAAS,IAAA,CAAK;AAAA,UACZ,IAAA;AAAA,UAAM,MAAA,EAAQ,MAAA;AAAA,UACd,OAAA,EAAS,OAAA,CAAQ,WAAA,CAAY,IAAA,CAAK,MAAM,QAAQ;AAAA,SACjD,CAAA;AACD,QAAA;AAAA,MACF;AACA,MAAA,IAAI,YAAA,IAAgB,IAAA,IAAQ,IAAA,CAAK,IAAA,GAAO,YAAA,EAAc;AACpD,QAAA,QAAA,CAAS,IAAA,CAAK;AAAA,UACZ,IAAA;AAAA,UAAM,MAAA,EAAQ,MAAA;AAAA,UACd,OAAA,EAAS,OAAA,CAAQ,QAAA,CAAS,IAAA,CAAK,IAAA,EAAM,SAAA,CAAU,IAAA,CAAK,IAAA,EAAM,OAAO,CAAA,EAAG,SAAA,CAAU,YAAA,EAAc,OAAO,CAAC;AAAA,SACrG,CAAA;AACD,QAAA;AAAA,MACF;AACA,MAAA,IAAI,YAAY,QAAA,IAAY,IAAA,IAAQ,YAAA,GAAe,QAAA,CAAS,UAAU,QAAA,EAAU;AAC9E,QAAA,QAAA,CAAS,IAAA,CAAK;AAAA,UACZ,IAAA;AAAA,UAAM,MAAA,EAAQ,OAAA;AAAA,UACd,OAAA,EAAS,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,MAAM,QAAQ;AAAA,SAC7C,CAAA;AACD,QAAA;AAAA,MACF;AACA,MAAA,QAAA,CAAS,KAAK,IAAI,CAAA;AAAA,IACpB;AACA,IAAA,aAAA,CAAc,QAAQ,CAAA;AACtB,IAAA,IAAI,QAAA,CAAS,MAAA,EAAQ,QAAA,CAAS,QAAQ,CAAA;AAGtC,IAAA,IAAI,QAAA,CAAS,OAAA,EAAS,QAAA,CAAS,OAAA,CAAQ,KAAA,GAAQ,EAAA;AAAA,EACjD,CAAA,EAAG,CAAC,QAAA,EAAU,QAAA,EAAU,MAAA,EAAQ,QAAA,EAAU,YAAA,EAAc,QAAA,EAAU,YAAA,EAAc,QAAA,EAAU,OAAO,CAAC,CAAA;AAElG,EAAA,MAAM,IAAA,GAAO,YAAY,MAAM;AAC7B,IAAA,IAAI,CAAC,QAAA,EAAU,QAAA,CAAS,OAAA,EAAS,KAAA,EAAM;AAAA,EACzC,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,MAAM,eAAA,GAAkB,YAAY,MAAM,aAAA,CAAc,EAAE,CAAA,EAAG,EAAE,CAAA;AAG/D,EAAA,MAAM,UAAA,GAAa;AAAA,IACjB,GAAA,EAAK,QAAA;AAAA,IACL,IAAA,EAAM,MAAA;AAAA,IACN,QAAQ,MAAA,IAAU,MAAA;AAAA,IAClB,QAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA,EAAU,EAAA;AAAA,IACV,aAAA,EAAe,IAAA;AAAA,IACf,SAAA,EAAW,QAAA;AAAA,IACX,UAAU,CAAC,CAAA,KAAqC,IAAA,CAAK,CAAA,CAAE,OAAO,KAAK;AAAA,GACrE;AAGA,EAAA,MAAM,SAAA,GAAY,QAAA,GACd,EAAC,GACD;AAAA,IACE,WAAA,EAAa,CAAC,CAAA,KAAiB;AAC7B,MAAA,IAAI,CAAC,UAAA,CAAW,CAAC,CAAA,EAAG;AACpB,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,QAAA,CAAS,OAAA,IAAW,CAAA;AACpB,MAAA,WAAA,CAAY,IAAI,CAAA;AAAA,IAClB,CAAA;AAAA,IACA,UAAA,EAAY,CAAC,CAAA,KAAiB;AAC5B,MAAA,IAAI,CAAC,UAAA,CAAW,CAAC,CAAA,EAAG;AACpB,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,IAAI,CAAC,QAAA,EAAU,WAAA,CAAY,IAAI,CAAA;AAAA,IACjC,CAAA;AAAA,IACA,WAAA,EAAa,CAAC,CAAA,KAAiB;AAC7B,MAAA,IAAI,CAAC,UAAA,CAAW,CAAC,CAAA,EAAG;AACpB,MAAA,QAAA,CAAS,UAAU,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,QAAA,CAAS,UAAU,CAAC,CAAA;AACnD,MAAA,IAAI,QAAA,CAAS,OAAA,KAAY,CAAA,EAAG,WAAA,CAAY,KAAK,CAAA;AAAA,IAC/C,CAAA;AAAA,IACA,MAAA,EAAQ,CAAC,CAAA,KAAiB;AACxB,MAAA,IAAI,CAAC,UAAA,CAAW,CAAC,CAAA,EAAG;AACpB,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,QAAA,CAAS,OAAA,GAAU,CAAA;AACnB,MAAA,WAAA,CAAY,KAAK,CAAA;AACjB,MAAA,IAAA,CAAK,CAAA,CAAE,aAAa,KAAK,CAAA;AAAA,IAC3B;AAAA,GACF;AAGJ,EAAA,MAAM,UAAA,GAAa,QAAA,GACf,EAAC,GACD;AAAA,IACE,OAAA,EAAS,CAAC,CAAA,KAAsB;AAC9B,MAAA,MAAM,KAAA,GAAQ,YAAY,CAAC,CAAA;AAC3B,MAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACxB,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,IAAA,CAAK,KAAK,CAAA;AAAA,IACZ;AAAA,GACF;AAEJ,EAAA,OAAO,EAAE,UAAU,UAAA,EAAY,SAAA,EAAW,YAAY,IAAA,EAAM,IAAA,EAAM,QAAA,EAAU,UAAA,EAAY,eAAA,EAAgB;AAC1G;AAOO,SAAS,eAAA,CAAgB,EAAE,UAAA,EAAY,SAAA,GAAY,QAAO,EAAwD;AACvH,EAAA,IAAI,UAAA,CAAW,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AACpC,EAAA,uBACE,GAAA,CAAC,IAAA,EAAA,EAAG,IAAA,EAAK,OAAA,EAAQ,SAAA,EAAW,CAAA,EAAG,SAAS,CAAA,sBAAA,CAAA,EACrC,QAAA,EAAA,UAAA,CAAW,GAAA,CAAI,CAAC,EAAE,IAAA,EAAM,OAAA,EAAQ,EAAG,CAAA,qBAClC,GAAA,CAAC,IAAA,EAAA,EAA6B,SAAA,EAAU,sBAAA,EAAwB,QAAA,EAAA,OAAA,EAAA,EAAvD,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,CAAA,EAAI,CAAC,CAAA,CAA8C,CACzE,CAAA,EACH,CAAA;AAEJ","file":"chunk-QAGQRNKI.js","sourcesContent":["/**\n * useFileIntake — the one way a file gets INTO a kit primitive.\n *\n * Every upload surface in the kit (`FilePicker`, `MediaUploadField`,\n * `MediaUploadGrid`, `BrandAssetEditor`, `BulkImportGrid`) takes files through\n * this hook, so the three gestures — the native file dialog, a drop onto the\n * zone, a paste into a composer — share ONE intake path and ONE set of checks.\n *\n * Before this each primitive owned its own hidden `<input type=file>`, its own\n * drag-over state and its own idea of what to check: `accept` reached the\n * native dialog only, so a file dropped onto the zone bypassed it entirely;\n * a multi-file drop onto a gallery kept the first file and lost the rest\n * without a word; and a rejected file was listed in plain red text that no\n * screen reader announced. The portals then copied the hidden-input block\n * nineteen more times. This hook is the fix at the narrowest layer that can\n * hold it (harness UI-15 / PAT-10).\n *\n * The hook validates and hands back `File[]`. It never uploads — transport\n * belongs to the consumer, for the reasons `FilePicker`'s header gives.\n *\n * Its rejection sentences and size units are the catalog's `files` section, so\n * a translated app says why a file was refused in the reader's language. The\n * catalog module imports nothing but React, which keeps this entry React-only.\n */\nimport {\n useCallback, useRef, useState,\n type ChangeEvent, type ClipboardEvent, type DragEvent, type ReactNode,\n} from 'react';\nimport { DEFAULT_SHELL_STRINGS, useShellStrings, type ShellStrings } from '../shell/strings';\n\nexport type FileRejectionReason = 'type' | 'size' | 'count';\n\nexport interface FileRejection {\n file: File;\n reason: FileRejectionReason;\n /** Human sentence naming the file and the rule it broke. */\n message: string;\n}\n\nexport interface FileIntakeLimits {\n /**\n * Native `accept` string — `image/*`, `.pdf,application/pdf`, … Enforced on\n * EVERY gesture, not only the dialog: a dropped or pasted file that fails it\n * is rejected with a reason. Empty or `*` accepts anything.\n */\n accept?: string;\n /** Rejected above this many bytes, with the reason shown. */\n maxSizeBytes?: number;\n /** Rejected once this many files are held in total (see `currentCount`). */\n maxFiles?: number;\n}\n\nexport interface FileIntakeOptions extends FileIntakeLimits {\n /** Receives the files that passed every check. Never called with an empty list. */\n onAccept: (files: File[]) => void;\n /** `false` takes the first candidate only — a single slot. Default `true`. */\n multiple?: boolean;\n /** Files the surface already holds, so `maxFiles` counts the total. Default 0. */\n currentCount?: number;\n /** Short kind hint for the type-rejection sentence, e.g. \"PNG · JPG\". */\n acceptHint?: ReactNode;\n disabled?: boolean;\n}\n\n/**\n * Does `file` satisfy a native `accept` string? Extension rules match the\n * name, `type/*` rules match the MIME prefix, anything else is an exact MIME.\n * A browser reports several media types for one `.ico` file and sometimes\n * none at all, which is why an extension rule exists at all.\n */\nexport function acceptsFile(file: File, accept?: string): boolean {\n const rules = (accept ?? '').split(',').map(rule => rule.trim().toLocaleLowerCase()).filter(Boolean);\n if (rules.length === 0 || rules.includes('*') || rules.includes('*/*')) return true;\n const fileName = file.name.toLocaleLowerCase();\n const fileType = file.type.toLocaleLowerCase();\n return rules.some(rule => {\n if (rule.startsWith('.')) return fileName.endsWith(rule);\n if (rule.endsWith('/*')) return fileType.startsWith(rule.slice(0, -1));\n return fileType === rule;\n });\n}\n\n/** `812 B`, `14 KB`, `6.2 MB` — the units, and their digits, from the catalog. */\nexport const humanSize = (bytes: number, strings: ShellStrings['files'] = DEFAULT_SHELL_STRINGS.files): string => {\n if (bytes < 1024) return strings.bytes(bytes);\n if (bytes < 1024 * 1024) return strings.kilobytes(Math.round(bytes / 1024));\n return strings.megabytes(bytes / (1024 * 1024));\n};\n\n/** Is this drag carrying files (as opposed to text, a URL, or an in-page reorder)? */\nexport const isFileDrag = (e: DragEvent): boolean =>\n Array.from(e.dataTransfer?.types ?? []).includes('Files');\n\n/** Files on a clipboard event, if the paste carried any. */\nexport function pastedFiles(e: ClipboardEvent): File[] {\n const data = e.clipboardData;\n if (!data) return [];\n if (data.files && data.files.length > 0) return Array.from(data.files);\n const out: File[] = [];\n for (const item of Array.from(data.items ?? [])) {\n if (item.kind === 'file') {\n const file = item.getAsFile();\n if (file) out.push(file);\n }\n }\n return out;\n}\n\nexport function useFileIntake({\n onAccept, accept, maxSizeBytes, maxFiles, multiple = true, currentCount = 0,\n acceptHint, disabled = false,\n}: FileIntakeOptions) {\n const strings = useShellStrings().files;\n const inputRef = useRef<HTMLInputElement>(null);\n const [dragOver, setDragOver] = useState(false);\n const [rejections, setRejections] = useState<FileRejection[]>([]);\n // A drag passes over every child of the zone, firing leave/enter pairs; a\n // depth counter is what keeps the highlight steady across them.\n const depthRef = useRef(0);\n\n const hintText = typeof acceptHint === 'string' ? acceptHint : '';\n\n /** Run every candidate through the checks; deliver the survivors. */\n const take = useCallback((incoming: FileList | File[] | null | undefined) => {\n if (disabled || !incoming) return;\n const candidates = Array.from(incoming);\n if (candidates.length === 0) return;\n const accepted: File[] = [];\n const rejected: FileRejection[] = [];\n for (const file of multiple ? candidates : candidates.slice(0, 1)) {\n if (!acceptsFile(file, accept)) {\n rejected.push({\n file, reason: 'type',\n message: strings.notAccepted(file.name, hintText),\n });\n continue;\n }\n if (maxSizeBytes != null && file.size > maxSizeBytes) {\n rejected.push({\n file, reason: 'size',\n message: strings.tooLarge(file.name, humanSize(file.size, strings), humanSize(maxSizeBytes, strings)),\n });\n continue;\n }\n if (multiple && maxFiles != null && currentCount + accepted.length >= maxFiles) {\n rejected.push({\n file, reason: 'count',\n message: strings.tooMany(file.name, maxFiles),\n });\n continue;\n }\n accepted.push(file);\n }\n setRejections(rejected);\n if (accepted.length) onAccept(accepted);\n // Reset the native input so re-picking the SAME file fires change again —\n // without this, removing a file and re-adding it appears to do nothing.\n if (inputRef.current) inputRef.current.value = '';\n }, [disabled, multiple, accept, hintText, maxSizeBytes, maxFiles, currentCount, onAccept, strings]);\n\n const open = useCallback(() => {\n if (!disabled) inputRef.current?.click();\n }, [disabled]);\n\n const clearRejections = useCallback(() => setRejections([]), []);\n\n /** Spread onto the hidden native input. It is never a tab stop and never the visible control. */\n const inputProps = {\n ref: inputRef,\n type: 'file' as const,\n accept: accept || undefined,\n multiple,\n disabled,\n tabIndex: -1,\n 'aria-hidden': true as const,\n className: 'hidden',\n onChange: (e: ChangeEvent<HTMLInputElement>) => take(e.target.files),\n };\n\n /** Spread onto the drop target. Only a drag carrying files lights it up. */\n const zoneProps = disabled\n ? {}\n : {\n onDragEnter: (e: DragEvent) => {\n if (!isFileDrag(e)) return;\n e.preventDefault();\n depthRef.current += 1;\n setDragOver(true);\n },\n onDragOver: (e: DragEvent) => {\n if (!isFileDrag(e)) return;\n e.preventDefault();\n if (!dragOver) setDragOver(true);\n },\n onDragLeave: (e: DragEvent) => {\n if (!isFileDrag(e)) return;\n depthRef.current = Math.max(0, depthRef.current - 1);\n if (depthRef.current === 0) setDragOver(false);\n },\n onDrop: (e: DragEvent) => {\n if (!isFileDrag(e)) return;\n e.preventDefault();\n depthRef.current = 0;\n setDragOver(false);\n take(e.dataTransfer.files);\n },\n };\n\n /** Spread onto a composer's text area: a paste that carries files is taken, one of text is left alone. */\n const pasteProps = disabled\n ? {}\n : {\n onPaste: (e: ClipboardEvent) => {\n const files = pastedFiles(e);\n if (files.length === 0) return;\n e.preventDefault();\n take(files);\n },\n };\n\n return { inputRef, inputProps, zoneProps, pasteProps, open, take, dragOver, rejections, clearRejections };\n}\n\n/**\n * The rejection list, announced. `role=\"alert\"` is what tells a screen-reader\n * user why the drop did nothing; the sighted user sees the same sentences.\n * Renders nothing while there is nothing to say.\n */\nexport function FileIntakeAlert({ rejections, className = 'mt-2' }: { rejections: FileRejection[]; className?: string }) {\n if (rejections.length === 0) return null;\n return (\n <ul role=\"alert\" className={`${className} flex flex-col gap-0.5`}>\n {rejections.map(({ file, message }, i) => (\n <li key={`${file.name}-${i}`} className=\"text-xs text-red-600\">{message}</li>\n ))}\n </ul>\n );\n}\n"]}
@@ -1,503 +0,0 @@
1
- import { onOverlayOpen, Z_LAYERS, registerModalEscapeInterceptor } from './chunk-W622KQTD.js';
2
- import { glassStyle, GLASS_DIVIDER } from './chunk-D276EMXS.js';
3
- import { useHydrated, useBrowserLayoutEffect } from './chunk-IRWSRJ4K.js';
4
- import { createContext, forwardRef, useContext, useRef, useId, useState, useEffect, useMemo, useCallback } from 'react';
5
- import { createPortal } from 'react-dom';
6
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
-
8
- // src/shell/contextMenuTarget.ts
9
- var TEXT_INPUT_TYPES = /* @__PURE__ */ new Set([
10
- "text",
11
- "search",
12
- "url",
13
- "tel",
14
- "email",
15
- "password",
16
- "number",
17
- "date",
18
- "datetime-local",
19
- "month",
20
- "week",
21
- "time"
22
- ]);
23
- var NATIVE_MEDIA = "img, picture, canvas, video, audio";
24
- var NATIVE_MENU_ATTR = "data-native-context-menu";
25
- function closest(el, selector) {
26
- if (!el || typeof el.closest !== "function") return null;
27
- return el.closest(selector);
28
- }
29
- function keepsNativeMenu(target) {
30
- const el = target;
31
- if (!el || typeof el.closest !== "function") return false;
32
- if (closest(el, `[${NATIVE_MENU_ATTR}]`)) return true;
33
- if (closest(el, "textarea")) return true;
34
- if (closest(el, NATIVE_MEDIA)) return true;
35
- const input = closest(el, "input");
36
- if (input) {
37
- const type = (input.type || "text").toLowerCase();
38
- if (TEXT_INPUT_TYPES.has(type)) return true;
39
- }
40
- const editable = closest(el, "[contenteditable]");
41
- if (editable) {
42
- const value = (editable.getAttribute("contenteditable") || "").toLowerCase();
43
- if (value === "" || value === "true" || value === "plaintext-only") return true;
44
- }
45
- return false;
46
- }
47
- var LINK_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:", "blob:"]);
48
- function linkAddress(href, base) {
49
- if (!href || href === "#") return void 0;
50
- try {
51
- const url = new URL(href, base ?? (typeof document !== "undefined" ? document.baseURI : void 0));
52
- return LINK_SCHEMES.has(url.protocol) ? url.href : void 0;
53
- } catch {
54
- return void 0;
55
- }
56
- }
57
- function describeContextTarget(target, selectionText, baseUrl) {
58
- const el = target;
59
- const link = closest(el, "a[href]");
60
- const text = (selectionText || "").trim();
61
- const linkUrl = linkAddress(link?.getAttribute("href"), baseUrl);
62
- const kind = text ? "selection" : linkUrl ? "link" : "default";
63
- return {
64
- kind,
65
- ...text ? { selectionText: text } : {},
66
- ...linkUrl ? { linkUrl } : {}
67
- };
68
- }
69
-
70
- // src/shell/menuPath.ts
71
- function openMenuLevel(path, depth, anchor) {
72
- if (path.length === depth + 1 && path[depth].key === anchor.key) return path;
73
- return [...path.slice(0, depth), anchor];
74
- }
75
- function closeMenuBelow(path, depth) {
76
- const keep = Math.max(0, depth);
77
- return path.length > keep ? path.slice(0, keep) : path;
78
- }
79
- function resolveMenuLevels(rootItems, path, childrenOf) {
80
- if (path.length === 0 || rootItems.length === 0) return [];
81
- const levels = [rootItems];
82
- for (let i = 1; i < path.length; i++) {
83
- const parent = levels[i - 1].find((item) => item.to === path[i].key);
84
- const kids = parent ? childrenOf(parent) : [];
85
- if (kids.length === 0) break;
86
- levels.push(kids);
87
- }
88
- return levels;
89
- }
90
- function clampMenuTop(anchorY, height, minTop, maxBottom) {
91
- const span = Math.max(0, maxBottom - minTop);
92
- const h = Math.min(height, span);
93
- const top = anchorY - h / 2;
94
- if (top < minTop) return minTop;
95
- if (top + h > maxBottom) return Math.max(minTop, maxBottom - h);
96
- return top;
97
- }
98
- function menuPanelLeft(ownerLeft, ownerRight, width, viewportW, gap, preferLeft = false, margin = 8) {
99
- const onRight = ownerRight + gap;
100
- const onLeft = ownerLeft - gap - width;
101
- const rightFits = onRight + width <= viewportW - margin;
102
- const leftFits = onLeft >= margin;
103
- if (preferLeft && leftFits) return { left: onLeft, flipped: true };
104
- if (rightFits) return { left: onRight, flipped: false };
105
- if (leftFits) return { left: onLeft, flipped: true };
106
- return preferLeft ? { left: margin, flipped: true } : { left: Math.max(margin, viewportW - margin - width), flipped: false };
107
- }
108
- function getDensity() {
109
- try {
110
- return getComputedStyle(document.documentElement).getPropertyValue("--menu-density")?.trim() || "normal";
111
- } catch {
112
- return "normal";
113
- }
114
- }
115
- var SUBMENU_CLOSE_DELAY = 200;
116
- var SUBMENU_OPEN_DELAY = 120;
117
- var SUBMENU_GAP = 4;
118
- var MenuTreeContext = createContext(null);
119
- function PopupMenu({ children, style, className = "", onClose, minWidth = 180, portal = false }) {
120
- const ref = useRef(null);
121
- const treeId = useId();
122
- const [path, setPath] = useState([]);
123
- const closeTimer = useRef();
124
- useEffect(() => () => clearTimeout(closeTimer.current), []);
125
- const hydrated = useHydrated();
126
- const tree = useMemo(() => {
127
- const cancelClose = () => clearTimeout(closeTimer.current);
128
- return {
129
- id: treeId,
130
- depth: 0,
131
- path,
132
- open: (depth, anchor) => {
133
- cancelClose();
134
- setPath((prev) => openMenuLevel(prev, depth, anchor));
135
- },
136
- scheduleClose: (depth) => {
137
- cancelClose();
138
- closeTimer.current = setTimeout(() => setPath((prev) => closeMenuBelow(prev, depth)), SUBMENU_CLOSE_DELAY);
139
- },
140
- closeBelow: (depth) => {
141
- cancelClose();
142
- setPath((prev) => closeMenuBelow(prev, depth));
143
- },
144
- cancelClose,
145
- closeTree: () => {
146
- cancelClose();
147
- setPath([]);
148
- onClose?.();
149
- },
150
- root: ref
151
- };
152
- }, [treeId, path, onClose]);
153
- useEffect(() => {
154
- if (!onClose) return;
155
- const handleClick = (e) => {
156
- if (e.target.closest("[data-menu-toggle]")) return;
157
- if (e.target.closest?.(`[data-popup-menu-tree="${treeId}"]`)) return;
158
- if (ref.current && !ref.current.contains(e.target)) onClose();
159
- };
160
- const handleKey = (e) => {
161
- if (e.key === "Escape") onClose();
162
- };
163
- window.addEventListener("pointerdown", handleClick);
164
- window.addEventListener("keydown", handleKey);
165
- const offOverlay = onOverlayOpen(onClose);
166
- return () => {
167
- window.removeEventListener("pointerdown", handleClick);
168
- window.removeEventListener("keydown", handleKey);
169
- offOverlay();
170
- };
171
- }, [onClose, treeId]);
172
- useEffect(() => {
173
- const el = ref.current;
174
- if (!el) return;
175
- requestAnimationFrame(() => {
176
- const rect = el.getBoundingClientRect();
177
- const margin = 8;
178
- if (rect.right > window.innerWidth - margin) {
179
- const overflow = rect.right - window.innerWidth + margin;
180
- if (el.style.left) {
181
- el.style.left = `${parseFloat(el.style.left) - overflow}px`;
182
- } else if (!el.style.right) {
183
- el.style.left = `${rect.left - overflow}px`;
184
- }
185
- }
186
- if (rect.left < margin) {
187
- if (el.style.left) {
188
- el.style.left = `${margin}px`;
189
- } else if (el.style.right) {
190
- el.style.right = `${window.innerWidth - rect.width - margin}px`;
191
- }
192
- }
193
- if (rect.bottom > window.innerHeight - margin) {
194
- const overflow = rect.bottom - window.innerHeight + margin;
195
- if (el.style.top) {
196
- el.style.top = `${parseFloat(el.style.top) - overflow}px`;
197
- } else if (el.style.bottom) {
198
- el.style.bottom = `${margin}px`;
199
- } else {
200
- el.style.top = `${rect.top - overflow}px`;
201
- }
202
- }
203
- if (rect.top < margin) {
204
- if (el.style.top) {
205
- el.style.top = `${margin}px`;
206
- } else if (el.style.bottom) {
207
- el.style.bottom = `${window.innerHeight - rect.height - margin}px`;
208
- }
209
- }
210
- });
211
- });
212
- const density = getDensity();
213
- const ownsZ = /(^|[\s:])!?z-/.test(className);
214
- const menu = /* @__PURE__ */ jsx(
215
- "div",
216
- {
217
- ref,
218
- "data-popup-menu-tree": treeId,
219
- className: `fixed rounded-2xl ${density === "tight" ? "py-1" : density === "large" ? "py-2" : "py-1.5"} ${className}`,
220
- onContextMenu: (e) => {
221
- if (!keepsNativeMenu(e.target)) e.preventDefault();
222
- },
223
- style: { ...ownsZ ? {} : { zIndex: Z_LAYERS.menu }, minWidth, animation: "popup-in 0.12s ease-out", ...glassStyle(), ...style },
224
- children: /* @__PURE__ */ jsx(MenuTreeContext.Provider, { value: tree, children })
225
- }
226
- );
227
- if (!portal) return menu;
228
- return hydrated ? createPortal(menu, document.body) : null;
229
- }
230
- function itemLook(danger, disabled, className, highlighted = false) {
231
- const density = getDensity();
232
- const itemPadY = density === "tight" ? "0.25rem" : density === "large" ? "0.6rem" : "max(0.25rem, calc(var(--menu-padding-y, 0.5rem) - 0.1rem))";
233
- const tone = danger ? "text-red-600 hover:bg-red-50" : highlighted ? "bg-blue-50 text-blue-700" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700";
234
- return {
235
- className: `w-full flex items-center gap-2 text-left transition-colors rounded-lg mx-auto
236
- ${tone}
237
- ${disabled ? "opacity-40 cursor-not-allowed" : ""}
238
- ${className}`,
239
- style: {
240
- width: "calc(100% - 8px)",
241
- marginLeft: 4,
242
- marginRight: 4,
243
- fontSize: "var(--menu-font-size, 14px)",
244
- paddingLeft: "var(--menu-padding-x, 1rem)",
245
- paddingRight: "var(--menu-padding-x, 1rem)",
246
- paddingTop: itemPadY,
247
- paddingBottom: itemPadY
248
- }
249
- };
250
- }
251
- var PopupMenuItem = forwardRef(function PopupMenuItem2({
252
- onClick,
253
- children,
254
- className = "",
255
- danger,
256
- disabled,
257
- role,
258
- tabIndex,
259
- id,
260
- onMouseEnter
261
- }, ref) {
262
- const tree = useContext(MenuTreeContext);
263
- const look = itemLook(danger, disabled, className);
264
- return /* @__PURE__ */ jsx(
265
- "button",
266
- {
267
- ref,
268
- id,
269
- role,
270
- tabIndex,
271
- onClick: () => {
272
- onClick?.();
273
- if (tree && tree.depth > 0) tree.closeTree();
274
- },
275
- onMouseEnter: () => {
276
- tree?.scheduleClose(tree.depth);
277
- onMouseEnter?.();
278
- },
279
- disabled,
280
- className: look.className,
281
- style: look.style,
282
- children
283
- }
284
- );
285
- });
286
- var chevron = /* @__PURE__ */ jsx("svg", { "aria-hidden": true, className: "h-3.5 w-3.5 shrink-0 ml-auto text-gray-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.25 4.5l7.5 7.5-7.5 7.5" }) });
287
- function layerOf(el) {
288
- let z = Z_LAYERS.menu;
289
- for (let node = el; node && node !== document.body; node = node.parentElement) {
290
- const value = parseInt(getComputedStyle(node).zIndex, 10);
291
- if (!Number.isNaN(value)) z = value;
292
- }
293
- return z;
294
- }
295
- function focusables(panel) {
296
- if (!panel) return [];
297
- return [...panel.querySelectorAll("button:not([disabled])")].filter((b) => b.closest("[data-popup-submenu-panel]") === panel);
298
- }
299
- function PopupSubmenu({ label, icon, children, disabled, className = "", minWidth = 180, maxHeight }) {
300
- const tree = useContext(MenuTreeContext);
301
- const key = useId();
302
- const rowRef = useRef(null);
303
- const panelRef = useRef(null);
304
- const openTimer = useRef();
305
- const focusOnOpen = useRef(false);
306
- const panelId = `${key}-menu`;
307
- useEffect(() => () => clearTimeout(openTimer.current), []);
308
- const depth = tree?.depth ?? 0;
309
- const anchor = tree?.path[depth];
310
- const isOpen = !!anchor && anchor.key === key && !disabled;
311
- const openNow = useCallback((withFocus) => {
312
- if (!tree || disabled || !rowRef.current) return;
313
- clearTimeout(openTimer.current);
314
- focusOnOpen.current = withFocus;
315
- const row = rowRef.current.getBoundingClientRect();
316
- const owner = rowRef.current.closest("[data-popup-submenu-panel]") ?? rowRef.current.closest("[data-popup-menu-tree]");
317
- const ownerRect = owner?.getBoundingClientRect() ?? row;
318
- const preferLeft = depth > 0 && (tree.path[depth - 1]?.flipped ?? false);
319
- const place = menuPanelLeft(ownerRect.left, ownerRect.right, minWidth, window.innerWidth, SUBMENU_GAP, preferLeft);
320
- tree.open(depth, { key, y: row.top, ...place });
321
- }, [tree, disabled, depth, key, minWidth]);
322
- const closeSelf = (restoreFocus) => {
323
- tree?.closeBelow(depth);
324
- rowRef.current?.focus();
325
- };
326
- useEffect(() => {
327
- if (!isOpen) return;
328
- return registerModalEscapeInterceptor((e) => {
329
- if (e.key !== "Escape") return false;
330
- if (tree && tree.path.length > depth + 1) return false;
331
- closeSelf();
332
- return true;
333
- });
334
- });
335
- useBrowserLayoutEffect(() => {
336
- if (!isOpen || !anchor) return;
337
- const place = () => {
338
- const panel = panelRef.current;
339
- if (!panel || !rowRef.current) return;
340
- const margin = 8;
341
- const owner = rowRef.current.closest("[data-popup-submenu-panel]") ?? rowRef.current.closest("[data-popup-menu-tree]");
342
- const ownerRect = owner?.getBoundingClientRect() ?? rowRef.current.getBoundingClientRect();
343
- const preferLeft = depth > 0 && (tree?.path[depth - 1]?.flipped ?? false);
344
- const width = panel.offsetWidth || minWidth;
345
- const spot = menuPanelLeft(ownerRect.left, ownerRect.right, width, window.innerWidth, SUBMENU_GAP, preferLeft, margin);
346
- const height = panel.offsetHeight;
347
- const rowTop = rowRef.current.getBoundingClientRect().top;
348
- const top = clampMenuTop(rowTop + height / 2, height, margin, window.innerHeight - margin);
349
- panel.style.left = `${spot.left}px`;
350
- panel.style.top = `${top}px`;
351
- panel.dataset.flipped = String(spot.flipped);
352
- };
353
- place();
354
- let second = 0;
355
- const first = requestAnimationFrame(() => {
356
- second = requestAnimationFrame(place);
357
- });
358
- return () => {
359
- cancelAnimationFrame(first);
360
- cancelAnimationFrame(second);
361
- };
362
- });
363
- useEffect(() => {
364
- if (!isOpen || !focusOnOpen.current) return;
365
- focusOnOpen.current = false;
366
- focusables(panelRef.current)[0]?.focus();
367
- }, [isOpen]);
368
- const childTree = useMemo(() => tree && { ...tree, depth: depth + 1 }, [tree, depth]);
369
- if (!tree) throw new Error("PopupSubmenu must be rendered inside a PopupMenu");
370
- const onRowKeyDown = (e) => {
371
- if (e.key === "ArrowRight") {
372
- e.preventDefault();
373
- e.stopPropagation();
374
- openNow(true);
375
- }
376
- };
377
- const onPanelKeyDown = (e) => {
378
- if (e.target.closest("[data-popup-submenu-panel]") !== e.currentTarget) return;
379
- const items = focusables(panelRef.current);
380
- const at = items.indexOf(document.activeElement);
381
- const move = (to) => {
382
- e.preventDefault();
383
- e.stopPropagation();
384
- items[(to + items.length) % items.length]?.focus();
385
- };
386
- if (e.key === "ArrowDown") move(at + 1);
387
- else if (e.key === "ArrowUp") move(at < 0 ? items.length - 1 : at - 1);
388
- else if (e.key === "Home") move(0);
389
- else if (e.key === "End") move(items.length - 1);
390
- else if (e.key === "ArrowLeft") {
391
- e.preventDefault();
392
- e.stopPropagation();
393
- closeSelf();
394
- }
395
- };
396
- const look = itemLook(false, disabled, className, isOpen);
397
- const density = getDensity();
398
- return /* @__PURE__ */ jsxs(Fragment, { children: [
399
- /* @__PURE__ */ jsxs(
400
- "button",
401
- {
402
- ref: rowRef,
403
- type: "button",
404
- role: "menuitem",
405
- "aria-haspopup": "menu",
406
- "aria-expanded": isOpen,
407
- "aria-controls": isOpen ? panelId : void 0,
408
- disabled,
409
- "data-popup-submenu-row": true,
410
- className: look.className,
411
- style: look.style,
412
- onClick: (e) => {
413
- const byKey = e.detail === 0;
414
- if (!isOpen) openNow(byKey);
415
- else if (byKey) focusables(panelRef.current)[0]?.focus();
416
- },
417
- onKeyDown: onRowKeyDown,
418
- onMouseEnter: () => {
419
- if (disabled) return;
420
- tree.cancelClose();
421
- if (isOpen) {
422
- tree.closeBelow(depth + 1);
423
- return;
424
- }
425
- clearTimeout(openTimer.current);
426
- openTimer.current = setTimeout(() => openNow(false), SUBMENU_OPEN_DELAY);
427
- },
428
- onMouseLeave: () => {
429
- clearTimeout(openTimer.current);
430
- tree.scheduleClose(depth);
431
- },
432
- children: [
433
- icon,
434
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: label }),
435
- chevron
436
- ]
437
- }
438
- ),
439
- isOpen && createPortal(
440
- /* @__PURE__ */ jsx(
441
- "div",
442
- {
443
- ref: panelRef,
444
- id: panelId,
445
- role: "menu",
446
- "data-popup-menu-tree": tree.id,
447
- "data-popup-submenu-panel": true,
448
- className: `fixed rounded-2xl overflow-x-hidden overflow-y-auto overscroll-contain ${density === "tight" ? "py-1" : density === "large" ? "py-2" : "py-1.5"}`,
449
- style: {
450
- left: anchor.left,
451
- top: anchor.y,
452
- minWidth,
453
- // The viewport cap always applies, so a long list scrolls rather
454
- // than running off the screen even when no maxHeight is passed.
455
- maxHeight: maxHeight === void 0 ? "calc(100vh - 16px)" : `min(${typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight}, calc(100vh - 16px))`,
456
- zIndex: layerOf(tree.root.current) + depth + 1,
457
- animation: `${anchor.flipped ? "popup-submenu-in-left" : "popup-submenu-in"} 0.1s ease-out`,
458
- ...glassStyle()
459
- },
460
- onKeyDown: onPanelKeyDown,
461
- onMouseEnter: () => {
462
- tree.cancelClose();
463
- clearTimeout(openTimer.current);
464
- },
465
- onMouseLeave: () => tree.scheduleClose(depth + 1),
466
- onContextMenu: (e) => {
467
- if (!keepsNativeMenu(e.target)) e.preventDefault();
468
- },
469
- children: /* @__PURE__ */ jsx(MenuTreeContext.Provider, { value: childTree, children })
470
- }
471
- ),
472
- document.body
473
- )
474
- ] });
475
- }
476
- function PopupMenuDivider() {
477
- const density = getDensity();
478
- return /* @__PURE__ */ jsx("div", { className: `border-t ${GLASS_DIVIDER} ${density === "tight" ? "my-0.5" : density === "large" ? "my-1.5" : "my-1"} mx-3` });
479
- }
480
- function PopupMenuLabel({ children }) {
481
- const density = getDensity();
482
- const labelPadY = density === "tight" ? "0.125rem" : density === "large" ? "0.375rem" : "0.25rem";
483
- return /* @__PURE__ */ jsx(
484
- "div",
485
- {
486
- className: "text-[10px] font-medium text-gray-400 uppercase tracking-wider",
487
- style: {
488
- paddingLeft: "var(--menu-padding-x, 1rem)",
489
- paddingRight: "var(--menu-padding-x, 1rem)",
490
- paddingTop: labelPadY,
491
- paddingBottom: labelPadY
492
- },
493
- children
494
- }
495
- );
496
- }
497
- function MenuIcon({ d, className = "h-4 w-4 text-gray-400" }) {
498
- return /* @__PURE__ */ jsx("svg", { className, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d }) });
499
- }
500
-
501
- export { MenuIcon, NATIVE_MENU_ATTR, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, PopupSubmenu, clampMenuTop, closeMenuBelow, describeContextTarget, keepsNativeMenu, menuPanelLeft, openMenuLevel, resolveMenuLevels };
502
- //# sourceMappingURL=chunk-QRL64J5F.js.map
503
- //# sourceMappingURL=chunk-QRL64J5F.js.map