@jbpark/live-editor 4.6.1 → 6.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/README.ko.md +37 -35
- package/README.md +37 -35
- package/dist/{boundary-BLB80tyw.js → boundary-CySnDUs2.js} +5 -4
- package/dist/boundary-CySnDUs2.js.map +1 -0
- package/dist/{compile-Dxas-D05.js → compile-my0AjsCw.js} +8 -7
- package/dist/compile-my0AjsCw.js.map +1 -0
- package/dist/constants-st9gnJ5g.js +55 -0
- package/dist/constants-st9gnJ5g.js.map +1 -0
- package/dist/{context-B8MzP7_p.js → context-Bjq0CvBE.js} +3 -3
- package/dist/{context-B8MzP7_p.js.map → context-Bjq0CvBE.js.map} +1 -1
- package/dist/{core-Bgfek364.js → core-BNVuH8-l.js} +51 -14
- package/dist/core-BNVuH8-l.js.map +1 -0
- package/dist/{detect-typescript-Dr_oLniM.js → detect-typescript-CMF0FVKD.js} +2 -2
- package/dist/{detect-typescript-Dr_oLniM.js.map → detect-typescript-CMF0FVKD.js.map} +1 -1
- package/dist/dnd/index.d.ts +3 -2
- package/dist/dnd/index.js +3 -2
- package/dist/dnd-BvjEDo-x.js +4197 -0
- package/dist/dnd-BvjEDo-x.js.map +1 -0
- package/dist/document-BQhb7YR8.d.ts +17 -0
- package/dist/{document-CHO215iP.js → document-uHqw0mvr.js} +194 -109
- package/dist/document-uHqw0mvr.js.map +1 -0
- package/dist/edit-options-C_r9aCz4.d.ts +100 -0
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.js +2 -2
- package/dist/{editor-CMElfHer.js → editor-CvnzrM9J.js} +2 -2
- package/dist/{editor-CMElfHer.js.map → editor-CvnzrM9J.js.map} +1 -1
- package/dist/error/index.d.ts +1 -1
- package/dist/error/index.js +2 -2
- package/dist/{error-CN_GCnXB.js → error-CFzvnLCf.js} +18 -32
- package/dist/error-CFzvnLCf.js.map +1 -0
- package/dist/{index-DpDU1IAw.d.ts → index-92L7F440.d.ts} +2 -2
- package/dist/{index-DrWtSKXu.d.ts → index-BZpZwrxT.d.ts} +2 -2
- package/dist/index-C8h8TcIl.d.ts +15 -0
- package/dist/{index-13BDSrQc.d.ts → index-C93npmTU.d.ts} +4 -6
- package/dist/{index-DQkL37eL.d.ts → index-CqsS207F.d.ts} +6 -12
- package/dist/{index-CiWB0zFb.d.ts → index-DQcdHg_V.d.ts} +18 -136
- package/dist/{index-CRwEhJ7w.d.ts → index-DfIvkht8.d.ts} +3 -3
- package/dist/index.d.ts +12 -11
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/{messages-IBPs4TkZ.d.ts → messages-BpN3QIhj.d.ts} +8 -2
- package/dist/{messages-CSrMI4Rm.js → messages-DrINcRdG.js} +8 -2
- package/dist/messages-DrINcRdG.js.map +1 -0
- package/dist/preview/index.d.ts +2 -2
- package/dist/preview/index.js +2 -3
- package/dist/{preview-CtyG5UbH.js → preview-B05MkVIQ.js} +16 -13
- package/dist/preview-B05MkVIQ.js.map +1 -0
- package/dist/provider/index.d.ts +2 -2
- package/dist/provider/index.js +2 -2
- package/dist/{sections-jYuqS4KB.js → sections-DhH_aVf5.js} +4 -7
- package/dist/sections-DhH_aVf5.js.map +1 -0
- package/dist/style.css +809 -4950
- package/dist/{validate-DB59Z_Nx.d.ts → types-Czps1Txy.d.ts} +12 -28
- package/dist/ui-kit/index.d.ts +22 -0
- package/dist/{constants-DU17Tdwo.js → ui-kit/index.js} +147 -54
- package/dist/ui-kit/index.js.map +1 -0
- package/dist/ui-kit/style.css +5039 -0
- package/dist/update-2PJbwoS8.d.ts +68 -0
- package/dist/{items-BXXtMUNI.js → update-BuYQJqjq.js} +143 -392
- package/dist/update-BuYQJqjq.js.map +1 -0
- package/dist/{use-dynamic-tailwind-BP1toJkW.js → use-dynamic-tailwind-B3l1qZRW.js} +318 -287
- package/dist/use-dynamic-tailwind-B3l1qZRW.js.map +1 -0
- package/dist/use-field-state-iQGW0vc6.js +131 -0
- package/dist/use-field-state-iQGW0vc6.js.map +1 -0
- package/dist/utils/ast/index.d.ts +47 -4
- package/dist/utils/ast/index.js +4 -64
- package/dist/utils/ast/index.js.map +1 -1
- package/dist/utils/index.d.ts +13 -32
- package/dist/utils/index.js +4 -26
- package/dist/validate-CZz30Pnb.js +122 -0
- package/dist/validate-CZz30Pnb.js.map +1 -0
- package/dist/validate-CiA1LF6S.d.ts +33 -0
- package/package.json +20 -10
- package/dist/boundary-BLB80tyw.js.map +0 -1
- package/dist/compile-Dxas-D05.js.map +0 -1
- package/dist/constants-DU17Tdwo.js.map +0 -1
- package/dist/core-Bgfek364.js.map +0 -1
- package/dist/dnd-V8NAxd_R.js +0 -2755
- package/dist/dnd-V8NAxd_R.js.map +0 -1
- package/dist/document-CHO215iP.js.map +0 -1
- package/dist/document-DqRSCtsx.d.ts +0 -1614
- package/dist/error-CN_GCnXB.js.map +0 -1
- package/dist/index-C_PfR4X9.d.ts +0 -734
- package/dist/items-BXXtMUNI.js.map +0 -1
- package/dist/messages-CSrMI4Rm.js.map +0 -1
- package/dist/preview-CtyG5UbH.js.map +0 -1
- package/dist/sections-jYuqS4KB.js.map +0 -1
- package/dist/use-dynamic-tailwind-BP1toJkW.js.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/validate-iLo8IONY.js +0 -64
- package/dist/validate-iLo8IONY.js.map +0 -1
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
declare const extractSections: (code: string, options?: SectionOptions) => Section[];
|
|
4
|
-
declare const replaceSections: (code: string, sections: string[], options?: DocumentOptions) => string;
|
|
5
|
-
declare const createDocument: ({ containerId }?: DocumentOptions) => string;
|
|
6
|
-
type DocumentCheck = {
|
|
7
|
-
ok: true;
|
|
8
|
-
} | ({
|
|
9
|
-
ok: false;
|
|
10
|
-
} & DocumentProblem);
|
|
11
|
-
declare const checkDocument: (code: string, options?: DocumentOptions) => DocumentCheck;
|
|
12
|
-
//#endregion
|
|
1
|
+
import { n as Section, t as Module } from "../index-C8h8TcIl.js";
|
|
2
|
+
import { n as DocumentProblem, r as SectionOptions, t as DocumentOptions } from "../document-BQhb7YR8.js";
|
|
13
3
|
//#region node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/clsx.d.mts
|
|
14
4
|
type ClassValue = ClassArray | ClassDictionary | string | number | bigint | null | boolean | undefined;
|
|
15
5
|
type ClassDictionary = Record<string, any>;
|
|
@@ -28,25 +18,16 @@ declare const clearEditorCaches: () => void;
|
|
|
28
18
|
//#region src/utils/scripts.d.ts
|
|
29
19
|
declare const preloadScripts: (scripts: string[]) => void;
|
|
30
20
|
//#endregion
|
|
31
|
-
//#region src/utils/
|
|
32
|
-
|
|
33
|
-
declare const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
declare const clearScriptCache: () => void;
|
|
42
|
-
/** @deprecated Internal. `Live.Dnd` renders section previews itself. Removed in the next major. */
|
|
43
|
-
declare const generateSection: (code: string, fullCode: string, options?: DocumentOptions) => string;
|
|
44
|
-
/** @deprecated Internal. `Live.Dnd` renders section previews itself. Removed in the next major. */
|
|
45
|
-
declare const generateSections: (codes: string[], fullCode: string, options?: DocumentOptions) => string[];
|
|
46
|
-
/** @deprecated Internal. `Live.Dnd` renders section previews itself. Removed in the next major. */
|
|
47
|
-
declare const createSectionPreviewCache: () => SectionPreviewCache$1;
|
|
48
|
-
/** @deprecated Internal. Removed in the next major. */
|
|
49
|
-
type SectionPreviewCache = SectionPreviewCache$1;
|
|
21
|
+
//#region src/utils/sections.d.ts
|
|
22
|
+
declare const extractSections: (code: string, options?: SectionOptions) => Section[];
|
|
23
|
+
declare const replaceSections: (code: string, sections: string[], options?: DocumentOptions) => string;
|
|
24
|
+
declare const createDocument: ({ containerId }?: DocumentOptions) => string;
|
|
25
|
+
type DocumentCheck = {
|
|
26
|
+
ok: true;
|
|
27
|
+
} | ({
|
|
28
|
+
ok: false;
|
|
29
|
+
} & DocumentProblem);
|
|
30
|
+
declare const checkDocument: (code: string, options?: DocumentOptions) => DocumentCheck;
|
|
50
31
|
//#endregion
|
|
51
|
-
export { type DocumentCheck, type DocumentOptions, type DocumentProblem, type SectionOptions,
|
|
32
|
+
export { type DocumentCheck, type DocumentOptions, type DocumentProblem, type SectionOptions, checkDocument, clearCompilationCache, clearEditorCaches, cn, compile, createDocument, extractSections, preloadScripts, replaceSections };
|
|
52
33
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/utils/index.js
CHANGED
|
@@ -1,27 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as registerEditorSession$1, t as clearEditorCaches } from "../editor-caches-ClMvdcQF.js";
|
|
1
|
+
import { t as clearEditorCaches } from "../editor-caches-ClMvdcQF.js";
|
|
3
2
|
import { t as cn } from "../cn-CAnad2vL.js";
|
|
4
|
-
import {
|
|
5
|
-
import { a as
|
|
6
|
-
|
|
7
|
-
//#region src/utils/index.ts
|
|
8
|
-
/** @deprecated Internal. Use `compile`. Removed in the next major. */
|
|
9
|
-
const transformCode = transformCode$1;
|
|
10
|
-
/** @deprecated Internal. Removed in the next major. */
|
|
11
|
-
const detectTypeScript = detectTypeScript$1;
|
|
12
|
-
/** @deprecated Internal. `Live` registers its own session. Removed in the next major. */
|
|
13
|
-
const registerEditorSession = registerEditorSession$1;
|
|
14
|
-
/** @deprecated Internal. Use `preloadScripts`. Removed in the next major. */
|
|
15
|
-
const getCachedScriptBlob = getCachedScriptBlob$1;
|
|
16
|
-
/** @deprecated Internal. Use `clearEditorCaches`. Removed in the next major. */
|
|
17
|
-
const clearScriptCache = clearScriptCache$1;
|
|
18
|
-
/** @deprecated Internal. `Live.Dnd` renders section previews itself. Removed in the next major. */
|
|
19
|
-
const generateSection = generateSection$1;
|
|
20
|
-
/** @deprecated Internal. `Live.Dnd` renders section previews itself. Removed in the next major. */
|
|
21
|
-
const generateSections = generateSections$1;
|
|
22
|
-
/** @deprecated Internal. `Live.Dnd` renders section previews itself. Removed in the next major. */
|
|
23
|
-
const createSectionPreviewCache = createSectionPreviewCache$1;
|
|
24
|
-
//#endregion
|
|
25
|
-
export { checkDocument, clearCompilationCache, clearEditorCaches, clearScriptCache, cn, compile, createDocument, createSectionPreviewCache, detectTypeScript, extractSections, generateSection, generateSections, getCachedScriptBlob, preloadScripts, registerEditorSession, replaceSections, transformCode };
|
|
26
|
-
|
|
27
|
-
//# sourceMappingURL=index.js.map
|
|
3
|
+
import { n as compile, r as preloadScripts, t as clearCompilationCache } from "../compile-my0AjsCw.js";
|
|
4
|
+
import { a as replaceSections, n as createDocument, r as extractSections, t as checkDocument } from "../sections-DhH_aVf5.js";
|
|
5
|
+
export { checkDocument, clearCompilationCache, clearEditorCaches, cn, compile, createDocument, extractSections, preloadScripts, replaceSections };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
//#region src/utils/ast/validate.ts
|
|
2
|
+
const defaultValidationMessages = {
|
|
3
|
+
required: "This field is required.",
|
|
4
|
+
min: (min) => `Must be at least ${min}.`,
|
|
5
|
+
max: (max) => `Must be at most ${max}.`,
|
|
6
|
+
pattern: "Value does not match the required format.",
|
|
7
|
+
url: "Must be a valid URL.",
|
|
8
|
+
date: "Must be a valid date.",
|
|
9
|
+
time: "Must be a valid time.",
|
|
10
|
+
datetime: "Must be a valid date and time.",
|
|
11
|
+
email: "Must be a valid email address.",
|
|
12
|
+
minLength: (minLength) => `Must be at least ${minLength} character${minLength === 1 ? "" : "s"}.`,
|
|
13
|
+
maxLength: (maxLength) => `Must be at most ${maxLength} character${maxLength === 1 ? "" : "s"}.`,
|
|
14
|
+
step: (step) => `Must be in steps of ${step}.`,
|
|
15
|
+
minItems: (minItems) => `Must have at least ${minItems} item${minItems === 1 ? "" : "s"}.`,
|
|
16
|
+
maxItems: (maxItems) => `Must have at most ${maxItems} item${maxItems === 1 ? "" : "s"}.`,
|
|
17
|
+
minDate: (minDate) => `Must be on or after ${minDate}.`,
|
|
18
|
+
maxDate: (maxDate) => `Must be on or before ${maxDate}.`,
|
|
19
|
+
protocol: (protocols) => `Must use ${protocols.join(", ")}.`
|
|
20
|
+
};
|
|
21
|
+
const VALID = { valid: true };
|
|
22
|
+
const STEP_TOLERANCE = 1e-9;
|
|
23
|
+
const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
24
|
+
const TIME = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
25
|
+
const isDateTime = (value) => {
|
|
26
|
+
const [date, time, ...rest] = value.split("T");
|
|
27
|
+
return rest.length === 0 && date !== void 0 && time !== void 0 && isCalendarDate(date) && TIME.test(time);
|
|
28
|
+
};
|
|
29
|
+
const compareToBound = (value, bound) => {
|
|
30
|
+
const length = Math.min(value.length, bound.length);
|
|
31
|
+
const a = value.slice(0, length);
|
|
32
|
+
const b = bound.slice(0, length);
|
|
33
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
34
|
+
};
|
|
35
|
+
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
36
|
+
const isCalendarDate = (value) => {
|
|
37
|
+
const match = ISO_DATE.exec(value);
|
|
38
|
+
if (!match) return false;
|
|
39
|
+
const [year, month, day] = match.slice(1).map(Number);
|
|
40
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
41
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
42
|
+
};
|
|
43
|
+
const normalizeProtocol = (protocol) => `${protocol.toLowerCase().replace(/:$/, "")}:`;
|
|
44
|
+
const invalid = (message) => ({
|
|
45
|
+
valid: false,
|
|
46
|
+
message
|
|
47
|
+
});
|
|
48
|
+
const validateBindingValue = (binding, value, options = {}) => {
|
|
49
|
+
const messages = {
|
|
50
|
+
...defaultValidationMessages,
|
|
51
|
+
...options.messages
|
|
52
|
+
};
|
|
53
|
+
if (value === "" || value === null || value === void 0) {
|
|
54
|
+
if (binding.required) return {
|
|
55
|
+
valid: false,
|
|
56
|
+
message: messages.required
|
|
57
|
+
};
|
|
58
|
+
return VALID;
|
|
59
|
+
}
|
|
60
|
+
if (typeof value === "number") {
|
|
61
|
+
if (binding.min !== void 0 && value < binding.min) return {
|
|
62
|
+
valid: false,
|
|
63
|
+
message: messages.min(binding.min)
|
|
64
|
+
};
|
|
65
|
+
if (binding.max !== void 0 && value > binding.max) return {
|
|
66
|
+
valid: false,
|
|
67
|
+
message: messages.max(binding.max)
|
|
68
|
+
};
|
|
69
|
+
if (binding.step !== void 0 && binding.step > 0) {
|
|
70
|
+
const steps = (value - (binding.min ?? 0)) / binding.step;
|
|
71
|
+
if (Math.abs(steps - Math.round(steps)) > STEP_TOLERANCE) return invalid(messages.step(binding.step));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (typeof value === "string") {
|
|
75
|
+
const length = [...value].length;
|
|
76
|
+
if (binding.minLength !== void 0 && length < binding.minLength) return invalid(messages.minLength(binding.minLength));
|
|
77
|
+
if (binding.maxLength !== void 0 && length > binding.maxLength) return invalid(messages.maxLength(binding.maxLength));
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(value)) {
|
|
80
|
+
if (binding.minItems !== void 0 && value.length < binding.minItems) return invalid(messages.minItems(binding.minItems));
|
|
81
|
+
if (binding.maxItems !== void 0 && value.length > binding.maxItems) return invalid(messages.maxItems(binding.maxItems));
|
|
82
|
+
}
|
|
83
|
+
if (typeof value === "string" && binding.pattern) {
|
|
84
|
+
let regex;
|
|
85
|
+
try {
|
|
86
|
+
regex = new RegExp(binding.pattern);
|
|
87
|
+
} catch {
|
|
88
|
+
return VALID;
|
|
89
|
+
}
|
|
90
|
+
if (!regex.test(value)) return invalid(binding.patternMessage ?? messages.pattern);
|
|
91
|
+
}
|
|
92
|
+
if (typeof value === "string" && binding.type === "url") {
|
|
93
|
+
let url;
|
|
94
|
+
try {
|
|
95
|
+
url = new URL(value);
|
|
96
|
+
} catch {
|
|
97
|
+
return {
|
|
98
|
+
valid: false,
|
|
99
|
+
message: messages.url
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (binding.protocols?.length) {
|
|
103
|
+
const allowed = binding.protocols.map(normalizeProtocol);
|
|
104
|
+
if (!allowed.includes(url.protocol)) return invalid(messages.protocol(allowed));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (typeof value === "string" && binding.type === "time") {
|
|
108
|
+
if (!TIME.test(value)) return invalid(messages.time);
|
|
109
|
+
}
|
|
110
|
+
if (typeof value === "string" && (binding.type === "date" || binding.type === "datetime")) {
|
|
111
|
+
if (binding.type === "date" && !isCalendarDate(value)) return invalid(messages.date);
|
|
112
|
+
if (binding.type === "datetime" && !isDateTime(value)) return invalid(messages.datetime);
|
|
113
|
+
if (binding.minDate !== void 0 && compareToBound(value, binding.minDate) < 0) return invalid(messages.minDate(binding.minDate));
|
|
114
|
+
if (binding.maxDate !== void 0 && compareToBound(value, binding.maxDate) > 0) return invalid(messages.maxDate(binding.maxDate));
|
|
115
|
+
}
|
|
116
|
+
if (typeof value === "string" && binding.type === "email" && !EMAIL.test(value)) return invalid(messages.email);
|
|
117
|
+
return VALID;
|
|
118
|
+
};
|
|
119
|
+
//#endregion
|
|
120
|
+
export { validateBindingValue as n, defaultValidationMessages as t };
|
|
121
|
+
|
|
122
|
+
//# sourceMappingURL=validate-CZz30Pnb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-CZz30Pnb.js","names":[],"sources":["../src/utils/ast/validate.ts"],"sourcesContent":["import type { BindingItem } from './types';\n\nexport interface ValidationResult {\n valid: boolean;\n message?: string;\n}\n\n// The messages a failed check returns. Pass your own through `options` to\n// translate them; the built-in panel passes the provider's `validation`\n// messages (#524).\nexport interface ValidationMessages {\n required: string;\n min: (min: number) => string;\n max: (max: number) => string;\n pattern: string;\n url: string;\n date: string;\n time: string;\n datetime: string;\n email: string;\n minLength: (minLength: number) => string;\n maxLength: (maxLength: number) => string;\n step: (step: number) => string;\n minItems: (minItems: number) => string;\n maxItems: (maxItems: number) => string;\n minDate: (minDate: string) => string;\n maxDate: (maxDate: string) => string;\n // The allowed schemes, normalized to end with `:`.\n protocol: (protocols: string[]) => string;\n}\n\nexport const defaultValidationMessages: ValidationMessages = {\n required: 'This field is required.',\n min: min => `Must be at least ${min}.`,\n max: max => `Must be at most ${max}.`,\n pattern: 'Value does not match the required format.',\n url: 'Must be a valid URL.',\n date: 'Must be a valid date.',\n time: 'Must be a valid time.',\n datetime: 'Must be a valid date and time.',\n email: 'Must be a valid email address.',\n minLength: minLength =>\n `Must be at least ${minLength} character${minLength === 1 ? '' : 's'}.`,\n maxLength: maxLength =>\n `Must be at most ${maxLength} character${maxLength === 1 ? '' : 's'}.`,\n step: step => `Must be in steps of ${step}.`,\n minItems: minItems =>\n `Must have at least ${minItems} item${minItems === 1 ? '' : 's'}.`,\n maxItems: maxItems =>\n `Must have at most ${maxItems} item${maxItems === 1 ? '' : 's'}.`,\n minDate: minDate => `Must be on or after ${minDate}.`,\n maxDate: maxDate => `Must be on or before ${maxDate}.`,\n protocol: protocols => `Must use ${protocols.join(', ')}.`,\n};\n\nexport interface ValidationOptions {\n messages?: Partial<ValidationMessages>;\n}\n\nconst VALID: ValidationResult = { valid: true };\n\n// How close a number has to be to a whole number of steps, so `0.3` passes\n// `step: 0.1` despite floating-point error.\nconst STEP_TOLERANCE = 1e-9;\n\nconst ISO_DATE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\n// A 24-hour `HH:mm`, the format the built-in time picker writes.\nconst TIME = /^([01]\\d|2[0-3]):[0-5]\\d$/;\n\n// `YYYY-MM-DDTHH:mm` in local time with no zone, as `datetime-local` writes\n// it, naming a day that exists.\nconst isDateTime = (value: string) => {\n const [date, time, ...rest] = value.split('T');\n\n return (\n rest.length === 0 &&\n date !== undefined &&\n time !== undefined &&\n isCalendarDate(date) &&\n TIME.test(time)\n );\n};\n\n// Compares a date or date-time with a `minDate`/`maxDate` bound at the\n// coarser of the two, so a date-only bound covers that whole day. The\n// formats sort by time, so they compare as text.\nconst compareToBound = (value: string, bound: string) => {\n const length = Math.min(value.length, bound.length);\n const a = value.slice(0, length);\n const b = bound.slice(0, length);\n\n return a < b ? -1 : a > b ? 1 : 0;\n};\n\n// A basic `local@domain.tld` check: no spaces, one `@`, a dot in the domain.\n// Anything stricter rejects real addresses.\nconst EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n// `YYYY-MM-DD` naming a day that exists, so `2026-02-30` fails. This is the\n// format the built-in date picker writes.\nconst isCalendarDate = (value: string) => {\n const match = ISO_DATE.exec(value);\n\n if (!match) {\n return false;\n }\n\n const [year, month, day] = match.slice(1).map(Number) as [\n number,\n number,\n number,\n ];\n const date = new Date(Date.UTC(year, month - 1, day));\n\n return (\n date.getUTCFullYear() === year &&\n date.getUTCMonth() === month - 1 &&\n date.getUTCDate() === day\n );\n};\n\n// `'https'` and `'https:'` both name the scheme `URL.protocol` reports as\n// `'https:'`.\nconst normalizeProtocol = (protocol: string) =>\n `${protocol.toLowerCase().replace(/:$/, '')}:`;\n\nconst invalid = (message: string): ValidationResult => ({\n valid: false,\n message,\n});\n\nexport const validateBindingValue = (\n binding: BindingItem,\n value: unknown,\n options: ValidationOptions = {},\n): ValidationResult => {\n const messages = { ...defaultValidationMessages, ...options.messages };\n const isEmpty = value === '' || value === null || value === undefined;\n\n if (isEmpty) {\n if (binding.required) {\n return { valid: false, message: messages.required };\n }\n return VALID;\n }\n\n // `min` and `max` apply to numbers only; a `type: 'number'` field passes\n // a number (#238). Other values aren't range-checked.\n if (typeof value === 'number') {\n if (binding.min !== undefined && value < binding.min) {\n return { valid: false, message: messages.min(binding.min) };\n }\n if (binding.max !== undefined && value > binding.max) {\n return { valid: false, message: messages.max(binding.max) };\n }\n\n if (binding.step !== undefined && binding.step > 0) {\n const steps = (value - (binding.min ?? 0)) / binding.step;\n\n if (Math.abs(steps - Math.round(steps)) > STEP_TOLERANCE) {\n return invalid(messages.step(binding.step));\n }\n }\n }\n\n if (typeof value === 'string') {\n const length = [...value].length;\n\n if (binding.minLength !== undefined && length < binding.minLength) {\n return invalid(messages.minLength(binding.minLength));\n }\n\n if (binding.maxLength !== undefined && length > binding.maxLength) {\n return invalid(messages.maxLength(binding.maxLength));\n }\n }\n\n if (Array.isArray(value)) {\n if (binding.minItems !== undefined && value.length < binding.minItems) {\n return invalid(messages.minItems(binding.minItems));\n }\n\n if (binding.maxItems !== undefined && value.length > binding.maxItems) {\n return invalid(messages.maxItems(binding.maxItems));\n }\n }\n\n if (typeof value === 'string' && binding.pattern) {\n let regex: RegExp;\n\n try {\n regex = new RegExp(binding.pattern);\n } catch {\n // An invalid `pattern` is the binding's mistake, so don't block the\n // input.\n return VALID;\n }\n\n if (!regex.test(value)) {\n return invalid(binding.patternMessage ?? messages.pattern);\n }\n }\n\n if (typeof value === 'string' && binding.type === 'url') {\n let url: URL;\n\n try {\n url = new URL(value);\n } catch {\n return { valid: false, message: messages.url };\n }\n\n if (binding.protocols?.length) {\n const allowed = binding.protocols.map(normalizeProtocol);\n\n if (!allowed.includes(url.protocol)) {\n return invalid(messages.protocol(allowed));\n }\n }\n }\n\n if (typeof value === 'string' && binding.type === 'time') {\n if (!TIME.test(value)) {\n return invalid(messages.time);\n }\n }\n\n if (\n typeof value === 'string' &&\n (binding.type === 'date' || binding.type === 'datetime')\n ) {\n if (binding.type === 'date' && !isCalendarDate(value)) {\n return invalid(messages.date);\n }\n\n if (binding.type === 'datetime' && !isDateTime(value)) {\n return invalid(messages.datetime);\n }\n\n if (\n binding.minDate !== undefined &&\n compareToBound(value, binding.minDate) < 0\n ) {\n return invalid(messages.minDate(binding.minDate));\n }\n\n if (\n binding.maxDate !== undefined &&\n compareToBound(value, binding.maxDate) > 0\n ) {\n return invalid(messages.maxDate(binding.maxDate));\n }\n }\n\n if (\n typeof value === 'string' &&\n binding.type === 'email' &&\n !EMAIL.test(value)\n ) {\n return invalid(messages.email);\n }\n\n return VALID;\n};\n"],"mappings":";AA+BA,MAAa,4BAAgD;CAC3D,UAAU;CACV,MAAK,QAAO,oBAAoB,IAAI;CACpC,MAAK,QAAO,mBAAmB,IAAI;CACnC,SAAS;CACT,KAAK;CACL,MAAM;CACN,MAAM;CACN,UAAU;CACV,OAAO;CACP,YAAW,cACT,oBAAoB,UAAU,YAAY,cAAc,IAAI,KAAK,IAAI;CACvE,YAAW,cACT,mBAAmB,UAAU,YAAY,cAAc,IAAI,KAAK,IAAI;CACtE,OAAM,SAAQ,uBAAuB,KAAK;CAC1C,WAAU,aACR,sBAAsB,SAAS,OAAO,aAAa,IAAI,KAAK,IAAI;CAClE,WAAU,aACR,qBAAqB,SAAS,OAAO,aAAa,IAAI,KAAK,IAAI;CACjE,UAAS,YAAW,uBAAuB,QAAQ;CACnD,UAAS,YAAW,wBAAwB,QAAQ;CACpD,WAAU,cAAa,YAAY,UAAU,KAAK,IAAI,EAAE;AAC1D;AAMA,MAAM,QAA0B,EAAE,OAAO,KAAK;AAI9C,MAAM,iBAAiB;AAEvB,MAAM,WAAW;AAGjB,MAAM,OAAO;AAIb,MAAM,cAAc,UAAkB;CACpC,MAAM,CAAC,MAAM,MAAM,GAAG,QAAQ,MAAM,MAAM,GAAG;CAE7C,OACE,KAAK,WAAW,KAChB,SAAS,KAAA,KACT,SAAS,KAAA,KACT,eAAe,IAAI,KACnB,KAAK,KAAK,IAAI;AAElB;AAKA,MAAM,kBAAkB,OAAe,UAAkB;CACvD,MAAM,SAAS,KAAK,IAAI,MAAM,QAAQ,MAAM,MAAM;CAClD,MAAM,IAAI,MAAM,MAAM,GAAG,MAAM;CAC/B,MAAM,IAAI,MAAM,MAAM,GAAG,MAAM;CAE/B,OAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI;AAClC;AAIA,MAAM,QAAQ;AAId,MAAM,kBAAkB,UAAkB;CACxC,MAAM,QAAQ,SAAS,KAAK,KAAK;CAEjC,IAAI,CAAC,OACH,OAAO;CAGT,MAAM,CAAC,MAAM,OAAO,OAAO,MAAM,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM;CAKpD,MAAM,OAAO,IAAI,KAAK,KAAK,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC;CAEpD,OACE,KAAK,eAAe,MAAM,QAC1B,KAAK,YAAY,MAAM,QAAQ,KAC/B,KAAK,WAAW,MAAM;AAE1B;AAIA,MAAM,qBAAqB,aACzB,GAAG,SAAS,YAAY,CAAC,CAAC,QAAQ,MAAM,EAAE,EAAE;AAE9C,MAAM,WAAW,aAAuC;CACtD,OAAO;CACP;AACF;AAEA,MAAa,wBACX,SACA,OACA,UAA6B,CAAC,MACT;CACrB,MAAM,WAAW;EAAE,GAAG;EAA2B,GAAG,QAAQ;CAAS;CAGrE,IAFgB,UAAU,MAAM,UAAU,QAAQ,UAAU,KAAA,GAE/C;EACX,IAAI,QAAQ,UACV,OAAO;GAAE,OAAO;GAAO,SAAS,SAAS;EAAS;EAEpD,OAAO;CACT;CAIA,IAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,QAAQ,QAAQ,KAAA,KAAa,QAAQ,QAAQ,KAC/C,OAAO;GAAE,OAAO;GAAO,SAAS,SAAS,IAAI,QAAQ,GAAG;EAAE;EAE5D,IAAI,QAAQ,QAAQ,KAAA,KAAa,QAAQ,QAAQ,KAC/C,OAAO;GAAE,OAAO;GAAO,SAAS,SAAS,IAAI,QAAQ,GAAG;EAAE;EAG5D,IAAI,QAAQ,SAAS,KAAA,KAAa,QAAQ,OAAO,GAAG;GAClD,MAAM,SAAS,SAAS,QAAQ,OAAO,MAAM,QAAQ;GAErD,IAAI,KAAK,IAAI,QAAQ,KAAK,MAAM,KAAK,CAAC,IAAI,gBACxC,OAAO,QAAQ,SAAS,KAAK,QAAQ,IAAI,CAAC;EAE9C;CACF;CAEA,IAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,SAAS,CAAC,GAAG,KAAK,CAAC,CAAC;EAE1B,IAAI,QAAQ,cAAc,KAAA,KAAa,SAAS,QAAQ,WACtD,OAAO,QAAQ,SAAS,UAAU,QAAQ,SAAS,CAAC;EAGtD,IAAI,QAAQ,cAAc,KAAA,KAAa,SAAS,QAAQ,WACtD,OAAO,QAAQ,SAAS,UAAU,QAAQ,SAAS,CAAC;CAExD;CAEA,IAAI,MAAM,QAAQ,KAAK,GAAG;EACxB,IAAI,QAAQ,aAAa,KAAA,KAAa,MAAM,SAAS,QAAQ,UAC3D,OAAO,QAAQ,SAAS,SAAS,QAAQ,QAAQ,CAAC;EAGpD,IAAI,QAAQ,aAAa,KAAA,KAAa,MAAM,SAAS,QAAQ,UAC3D,OAAO,QAAQ,SAAS,SAAS,QAAQ,QAAQ,CAAC;CAEtD;CAEA,IAAI,OAAO,UAAU,YAAY,QAAQ,SAAS;EAChD,IAAI;EAEJ,IAAI;GACF,QAAQ,IAAI,OAAO,QAAQ,OAAO;EACpC,QAAQ;GAGN,OAAO;EACT;EAEA,IAAI,CAAC,MAAM,KAAK,KAAK,GACnB,OAAO,QAAQ,QAAQ,kBAAkB,SAAS,OAAO;CAE7D;CAEA,IAAI,OAAO,UAAU,YAAY,QAAQ,SAAS,OAAO;EACvD,IAAI;EAEJ,IAAI;GACF,MAAM,IAAI,IAAI,KAAK;EACrB,QAAQ;GACN,OAAO;IAAE,OAAO;IAAO,SAAS,SAAS;GAAI;EAC/C;EAEA,IAAI,QAAQ,WAAW,QAAQ;GAC7B,MAAM,UAAU,QAAQ,UAAU,IAAI,iBAAiB;GAEvD,IAAI,CAAC,QAAQ,SAAS,IAAI,QAAQ,GAChC,OAAO,QAAQ,SAAS,SAAS,OAAO,CAAC;EAE7C;CACF;CAEA,IAAI,OAAO,UAAU,YAAY,QAAQ,SAAS,QAC5C;MAAA,CAAC,KAAK,KAAK,KAAK,GAClB,OAAO,QAAQ,SAAS,IAAI;CAAA;CAIhC,IACE,OAAO,UAAU,aAChB,QAAQ,SAAS,UAAU,QAAQ,SAAS,aAC7C;EACA,IAAI,QAAQ,SAAS,UAAU,CAAC,eAAe,KAAK,GAClD,OAAO,QAAQ,SAAS,IAAI;EAG9B,IAAI,QAAQ,SAAS,cAAc,CAAC,WAAW,KAAK,GAClD,OAAO,QAAQ,SAAS,QAAQ;EAGlC,IACE,QAAQ,YAAY,KAAA,KACpB,eAAe,OAAO,QAAQ,OAAO,IAAI,GAEzC,OAAO,QAAQ,SAAS,QAAQ,QAAQ,OAAO,CAAC;EAGlD,IACE,QAAQ,YAAY,KAAA,KACpB,eAAe,OAAO,QAAQ,OAAO,IAAI,GAEzC,OAAO,QAAQ,SAAS,QAAQ,QAAQ,OAAO,CAAC;CAEpD;CAEA,IACE,OAAO,UAAU,YACjB,QAAQ,SAAS,WACjB,CAAC,MAAM,KAAK,KAAK,GAEjB,OAAO,QAAQ,SAAS,KAAK;CAG/B,OAAO;AACT"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { r as BindingItem } from "./types-Czps1Txy.js";
|
|
2
|
+
//#region src/utils/ast/validate.d.ts
|
|
3
|
+
interface ValidationResult {
|
|
4
|
+
valid: boolean;
|
|
5
|
+
message?: string;
|
|
6
|
+
}
|
|
7
|
+
interface ValidationMessages {
|
|
8
|
+
required: string;
|
|
9
|
+
min: (min: number) => string;
|
|
10
|
+
max: (max: number) => string;
|
|
11
|
+
pattern: string;
|
|
12
|
+
url: string;
|
|
13
|
+
date: string;
|
|
14
|
+
time: string;
|
|
15
|
+
datetime: string;
|
|
16
|
+
email: string;
|
|
17
|
+
minLength: (minLength: number) => string;
|
|
18
|
+
maxLength: (maxLength: number) => string;
|
|
19
|
+
step: (step: number) => string;
|
|
20
|
+
minItems: (minItems: number) => string;
|
|
21
|
+
maxItems: (maxItems: number) => string;
|
|
22
|
+
minDate: (minDate: string) => string;
|
|
23
|
+
maxDate: (maxDate: string) => string;
|
|
24
|
+
protocol: (protocols: string[]) => string;
|
|
25
|
+
}
|
|
26
|
+
declare const defaultValidationMessages: ValidationMessages;
|
|
27
|
+
interface ValidationOptions {
|
|
28
|
+
messages?: Partial<ValidationMessages>;
|
|
29
|
+
}
|
|
30
|
+
declare const validateBindingValue: (binding: BindingItem, value: unknown, options?: ValidationOptions) => ValidationResult;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { validateBindingValue as a, defaultValidationMessages as i, ValidationOptions as n, ValidationResult as r, ValidationMessages as t };
|
|
33
|
+
//# sourceMappingURL=validate-CiA1LF6S.d.ts.map
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "
|
|
7
|
+
"version": "6.0.0",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"module": "./dist/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"url": "https://github.com/pjb0811/live-editor/issues"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
|
41
|
-
"node": ">=
|
|
41
|
+
"node": "^22.18.0 || >=24.11.0"
|
|
42
42
|
},
|
|
43
43
|
"packageManager": "pnpm@12.8.1",
|
|
44
44
|
"exports": {
|
|
@@ -78,6 +78,11 @@
|
|
|
78
78
|
"types": "./dist/utils/tailwind/index.d.ts",
|
|
79
79
|
"import": "./dist/utils/tailwind/index.js"
|
|
80
80
|
},
|
|
81
|
+
"./ui-kit": {
|
|
82
|
+
"types": "./dist/ui-kit/index.d.ts",
|
|
83
|
+
"import": "./dist/ui-kit/index.js"
|
|
84
|
+
},
|
|
85
|
+
"./ui-kit/style.css": "./dist/ui-kit/style.css",
|
|
81
86
|
"./style.css": "./dist/style.css"
|
|
82
87
|
},
|
|
83
88
|
"files": [
|
|
@@ -92,9 +97,10 @@
|
|
|
92
97
|
"check-api-surface": "node .github/scripts/check-api-surface.mjs",
|
|
93
98
|
"check-package-types": "pnpm build && node .github/scripts/check-package-types.mjs",
|
|
94
99
|
"lint": "eslint .",
|
|
95
|
-
"
|
|
100
|
+
"knip": "knip",
|
|
96
101
|
"test": "vitest run",
|
|
97
102
|
"test:browser": "playwright test",
|
|
103
|
+
"test:coverage": "vitest run --coverage",
|
|
98
104
|
"test:watch": "vitest",
|
|
99
105
|
"bench": "vitest bench",
|
|
100
106
|
"preview": "vite preview",
|
|
@@ -102,15 +108,13 @@
|
|
|
102
108
|
"prepare": "husky"
|
|
103
109
|
},
|
|
104
110
|
"dependencies": {
|
|
105
|
-
"@babel/standalone": "^
|
|
111
|
+
"@babel/standalone": "^8.0.6",
|
|
106
112
|
"@codemirror/lang-javascript": "^6.2.5",
|
|
107
|
-
"@codemirror/merge": "^6.12.2",
|
|
108
113
|
"@dnd-kit/core": "^6.3.1",
|
|
109
114
|
"@dnd-kit/modifiers": "^9.0.0",
|
|
110
115
|
"@dnd-kit/sortable": "^10.0.0",
|
|
111
116
|
"@dnd-kit/utilities": "^3.2.2",
|
|
112
|
-
"@jbpark/
|
|
113
|
-
"@jbpark/use-hooks": "^4.1.0",
|
|
117
|
+
"@jbpark/use-hooks": "^4.6.0",
|
|
114
118
|
"@uiw/codemirror-theme-vscode": "^4.25.11",
|
|
115
119
|
"@uiw/react-codemirror": "^4.25.11",
|
|
116
120
|
"codemirror": "^6.0.0",
|
|
@@ -119,11 +123,15 @@
|
|
|
119
123
|
"zod": "^4.4.3"
|
|
120
124
|
},
|
|
121
125
|
"peerDependencies": {
|
|
126
|
+
"@jbpark/ui-kit": "^10.5.0",
|
|
122
127
|
"prettier": "^3.0.0",
|
|
123
128
|
"react": ">=19.0.0",
|
|
124
129
|
"react-dom": ">=19.0.0"
|
|
125
130
|
},
|
|
126
131
|
"peerDependenciesMeta": {
|
|
132
|
+
"@jbpark/ui-kit": {
|
|
133
|
+
"optional": true
|
|
134
|
+
},
|
|
127
135
|
"prettier": {
|
|
128
136
|
"optional": true
|
|
129
137
|
}
|
|
@@ -134,27 +142,30 @@
|
|
|
134
142
|
"@babel/traverse": "^8.0.6",
|
|
135
143
|
"@babel/types": "^8.0.6",
|
|
136
144
|
"@changesets/cli": "^3.0.0",
|
|
145
|
+
"@codemirror/merge": "^6.12.2",
|
|
137
146
|
"@eslint/js": "^10.0.0",
|
|
147
|
+
"@jbpark/ui-kit": "^10.5.0",
|
|
138
148
|
"@playwright/test": "^1.63.0",
|
|
139
149
|
"@tailwindcss/vite": "^4.3.3",
|
|
140
150
|
"@testing-library/dom": "^10.4.1",
|
|
141
151
|
"@testing-library/react": "^16.3.3",
|
|
142
152
|
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
143
153
|
"@tsdown/css": "^0.22.14",
|
|
144
|
-
"@types/babel__standalone": "^7.1.9",
|
|
145
154
|
"@types/node": "^26.2.0",
|
|
146
155
|
"@types/react": "^19.2.18",
|
|
147
156
|
"@types/react-dom": "^19.2.4",
|
|
148
157
|
"@vitejs/plugin-react": "^6.0.5",
|
|
158
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
149
159
|
"baseline-browser-mapping": "^2.11.14",
|
|
150
160
|
"clsx": "^2.1.1",
|
|
151
|
-
"depcheck": "^1.4.7",
|
|
152
161
|
"eslint": "^10.0.0",
|
|
153
162
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
154
163
|
"eslint-plugin-react-refresh": "^0.5.4",
|
|
155
164
|
"globals": "^17.11.0",
|
|
156
165
|
"husky": "^9.1.7",
|
|
157
166
|
"jsdom": "^30.0.1",
|
|
167
|
+
"knip": "^5.88.1",
|
|
168
|
+
"lightningcss": "^1.33.0",
|
|
158
169
|
"lint-staged": "^17.3.0",
|
|
159
170
|
"nanoid": "^6.0.0",
|
|
160
171
|
"prettier": "^3.9.6",
|
|
@@ -168,7 +179,6 @@
|
|
|
168
179
|
"typescript": "~6.0.3",
|
|
169
180
|
"typescript-eslint": "^8.67.0",
|
|
170
181
|
"vite": "^8.2.1",
|
|
171
|
-
"vite-plugin-dts": "^5.0.3",
|
|
172
182
|
"vitest": "^5.0.0"
|
|
173
183
|
},
|
|
174
184
|
"lint-staged": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"boundary-BLB80tyw.js","names":[],"sources":["../src/components/error/error.tsx","../src/components/error/boundary.tsx"],"sourcesContent":["import { useLiveMessages } from '~/components/context/messages';\nimport { cn } from '~/utils/cn';\n\nexport interface Props extends React.ComponentPropsWithRef<'div'> {\n message?: string | null;\n title?: string;\n onReset?: () => void;\n}\n\nconst Error = ({ message, title, className, onReset }: Props) => {\n const messages = useLiveMessages();\n\n if (!message) {\n return null;\n }\n\n return (\n <div\n className={cn(className)}\n style={{\n padding: '16px',\n backgroundColor: '#fee2e2',\n border: '1px solid #fca5a5',\n borderRadius: '6px',\n color: '#7f1d1d',\n }}\n >\n <h3 style={{ fontSize: '14px', fontWeight: 'bold', margin: 0 }}>\n {title ?? messages.errors.default}\n </h3>\n <pre\n style={{\n marginTop: '8px',\n fontSize: '12px',\n whiteSpace: 'pre-wrap',\n margin: '8px 0 0 0',\n }}\n >\n {message}\n </pre>\n {onReset && (\n <button\n style={{\n marginTop: '8px',\n padding: '4px 12px',\n fontSize: '12px',\n color: 'white',\n backgroundColor: '#dc2626',\n border: 'none',\n borderRadius: '4px',\n cursor: 'pointer',\n }}\n onClick={onReset}\n >\n {messages.errors.tryAgain}\n </button>\n )}\n </div>\n );\n};\n\nexport default Error;\n","import React from 'react';\n\nimport {\n type LiveMessages,\n MessagesContext,\n} from '~/components/context/messages';\n\nimport Error from './error';\n\ninterface Props {\n children: React.ReactNode;\n // `reset` clears the caught error and renders `children` again, the same\n // thing the built-in error's reset button does.\n fallback?: (message?: string, reset?: () => void) => React.ReactNode;\n onError?: (e: Error, info: React.ErrorInfo) => void;\n // When any of these values changes (compared one by one, by identity), a\n // caught error resets and `children` render again, such as when the code\n // that failed is edited.\n resetKeys?: readonly unknown[];\n}\n\ninterface State {\n hasError: boolean;\n error?: Error;\n // The resetKeys the last render saw, so getDerivedStateFromProps can tell\n // when they change.\n resetKeys?: readonly unknown[];\n}\n\nconst keysChanged = (\n prev: readonly unknown[] = [],\n next: readonly unknown[] = [],\n) => prev.length !== next.length || next.some((key, i) => key !== prev[i]);\n\nclass ErrorBoundary extends React.Component<Props, State> {\n static contextType = MessagesContext;\n declare context: LiveMessages;\n\n constructor(props: Props) {\n super(props);\n this.state = { hasError: false, resetKeys: props.resetKeys };\n }\n\n static getDerivedStateFromError(error: Error): Partial<State> {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n console.error('🚨 [Boundary] Rendering error:', error, errorInfo);\n this.props.onError?.(error, errorInfo);\n }\n\n // Clears a caught error in the same render that sees new resetKeys, so\n // `children` get their next chance right away and the fallback doesn't\n // render (and run its effects) once more first (#442). If `children` throw\n // again with the new keys, getDerivedStateFromError catches that in this\n // same render pass.\n static getDerivedStateFromProps(\n props: Props,\n state: State,\n ): Partial<State> | null {\n if (!keysChanged(state.resetKeys, props.resetKeys)) {\n return null;\n }\n\n return state.hasError\n ? { hasError: false, error: undefined, resetKeys: props.resetKeys }\n : { resetKeys: props.resetKeys };\n }\n\n reset = () => this.setState({ hasError: false, error: undefined });\n\n render() {\n if (this.state.hasError) {\n return this.props.fallback ? (\n <>{this.props.fallback(this.state.error?.message, this.reset)}</>\n ) : (\n <Error\n message={this.state.error?.message}\n onReset={this.reset}\n title={this.context.errors.rendering}\n />\n );\n }\n\n return this.props.children;\n }\n}\n\nexport default ErrorBoundary;\n"],"mappings":";;;;;AASA,MAAM,SAAS,EAAE,SAAS,OAAO,WAAW,cAAqB;CAC/D,MAAM,WAAW,gBAAgB;CAEjC,IAAI,CAAC,SACH,OAAO;CAGT,OACE,qBAAC,OAAD;EACE,WAAW,GAAG,SAAS;EACvB,OAAO;GACL,SAAS;GACT,iBAAiB;GACjB,QAAQ;GACR,cAAc;GACd,OAAO;EACT;EARF,UAAA;GAUE,oBAAC,MAAD;IAAI,OAAO;KAAE,UAAU;KAAQ,YAAY;KAAQ,QAAQ;IAAE;IAC1D,UAAA,SAAS,SAAS,OAAO;GACxB,CAAA;GACJ,oBAAC,OAAD;IACE,OAAO;KACL,WAAW;KACX,UAAU;KACV,YAAY;KACZ,QAAQ;IACV;IAEC,UAAA;GACE,CAAA;GACJ,WACC,oBAAC,UAAD;IACE,OAAO;KACL,WAAW;KACX,SAAS;KACT,UAAU;KACV,OAAO;KACP,iBAAiB;KACjB,QAAQ;KACR,cAAc;KACd,QAAQ;IACV;IACA,SAAS;IAER,UAAA,SAAS,OAAO;GACX,CAAA;EAEP;;AAET;;;AC9BA,MAAM,eACJ,OAA2B,CAAC,GAC5B,OAA2B,CAAC,MACzB,KAAK,WAAW,KAAK,UAAU,KAAK,MAAM,KAAK,MAAM,QAAQ,KAAK,EAAE;AAEzE,IAAM,gBAAN,cAA4B,MAAM,UAAwB;CACxD,OAAO,cAAc;CAGrB,YAAY,OAAc;EACxB,MAAM,KAAK;EACX,KAAK,QAAQ;GAAE,UAAU;GAAO,WAAW,MAAM;EAAU;CAC7D;CAEA,OAAO,yBAAyB,OAA8B;EAC5D,OAAO;GAAE,UAAU;GAAM;EAAM;CACjC;CAEA,kBAAkB,OAAc,WAA4B;EAC1D,QAAQ,MAAM,kCAAkC,OAAO,SAAS;EAChE,KAAK,MAAM,UAAU,OAAO,SAAS;CACvC;CAOA,OAAO,yBACL,OACA,OACuB;EACvB,IAAI,CAAC,YAAY,MAAM,WAAW,MAAM,SAAS,GAC/C,OAAO;EAGT,OAAO,MAAM,WACT;GAAE,UAAU;GAAO,OAAO,KAAA;GAAW,WAAW,MAAM;EAAU,IAChE,EAAE,WAAW,MAAM,UAAU;CACnC;CAEA,cAAc,KAAK,SAAS;EAAE,UAAU;EAAO,OAAO,KAAA;CAAU,CAAC;CAEjE,SAAS;EACP,IAAI,KAAK,MAAM,UACb,OAAO,KAAK,MAAM,WAChB,oBAAA,UAAA,EAAA,UAAG,KAAK,MAAM,SAAS,KAAK,MAAM,OAAO,SAAS,KAAK,KAAK,EAAI,CAAA,IAEhE,oBAAC,OAAD;GACE,SAAS,KAAK,MAAM,OAAO;GAC3B,SAAS,KAAK;GACd,OAAO,KAAK,QAAQ,OAAO;EAC5B,CAAA;EAIL,OAAO,KAAK,MAAM;CACpB;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"compile-Dxas-D05.js","names":[],"sources":["../src/utils/scripts.ts","../src/utils/compile.ts"],"sourcesContent":["import { CONFIG } from '../constants';\nimport { createBoundedCache } from './cache';\nimport { registerEditorCache } from './editor-caches';\n\n// How many callers have requested each script and not yet injected it. A\n// blob URL evicted in that time isn't revoked until the last of them is done\n// (`deferredRevocations`), or its `<script>` would fail to load without an\n// error (#443).\nconst pins = new Map<string, number>();\nconst deferredRevocations = new Map<string, string[]>();\n\nconst revoke = (src: string, blobUrl: string) => {\n if (!pins.has(src)) {\n URL.revokeObjectURL(blobUrl);\n return;\n }\n\n deferredRevocations.set(src, [\n ...(deferredRevocations.get(src) ?? []),\n blobUrl,\n ]);\n};\n\nconst pin = (src: string) => {\n pins.set(src, (pins.get(src) ?? 0) + 1);\n};\n\nconst unpin = (src: string) => {\n const count = (pins.get(src) ?? 0) - 1;\n\n if (count > 0) {\n pins.set(src, count);\n return;\n }\n\n pins.delete(src);\n deferredRevocations.get(src)?.forEach(blobUrl => {\n URL.revokeObjectURL(blobUrl);\n });\n deferredRevocations.delete(src);\n};\n\nconst scriptCache = createBoundedCache<string, string>(\n CONFIG.SCRIPT_CACHE_LIMIT,\n revoke,\n);\nconst loadingScriptCache = new Map<string, Promise<string>>();\n\nexport const getCachedScriptBlob = async (src: string): Promise<string> => {\n const cached = scriptCache.get(src);\n\n if (cached) {\n return cached;\n }\n\n const existing = loadingScriptCache.get(src);\n\n if (existing) {\n return existing;\n }\n\n const promise = fetch(src)\n .then(res => res.text())\n .then(text => {\n const blob = new Blob([text], { type: 'application/javascript' });\n const blobUrl = URL.createObjectURL(blob);\n\n scriptCache.set(src, blobUrl);\n\n return blobUrl;\n })\n // Clean up after a failure too: a rejected promise left here would be\n // reused by every later caller, so the script could never load again.\n .finally(() => {\n loadingScriptCache.delete(src);\n });\n\n loadingScriptCache.set(src, promise);\n\n return promise;\n};\n\n// Resolves every script to a blob URL and hands them to `inject` while none\n// of them can be revoked. `inject` must use them at once, as appending\n// `<script>` elements does: the browser resolves a blob URL on insertion.\n// Several scripts, or several frames, can push more scripts through the cache\n// than it holds before the last one arrives (#443).\nexport const withScriptBlobs = async (\n scripts: string[],\n inject: (blobUrls: string[]) => void,\n): Promise<void> => {\n scripts.forEach(pin);\n\n try {\n inject(await Promise.all(scripts.map(getCachedScriptBlob)));\n } finally {\n scripts.forEach(unpin);\n }\n};\n\nexport const preloadScripts = (scripts: string[]): void => {\n scripts.forEach(src => {\n // Not awaited, so the rejection is handled here. The real load reports a\n // failure to whoever awaits it.\n getCachedScriptBlob(src).catch(() => {});\n });\n};\n\n// Revokes every blob URL, through the same `onEvict` the LRU uses, and drops\n// the pending loads, so the next session doesn't get a URL created before\n// the revocation.\nexport const clearScriptCache = () => {\n scriptCache.clear();\n loadingScriptCache.clear();\n};\n\nregisterEditorCache(clearScriptCache);\n","import React from 'react';\n\nimport * as Babel from '@babel/standalone';\n\nimport type { Module } from '~/types';\n\nimport { CONFIG } from '../constants';\nimport { createBoundedCache } from './cache';\nimport { detectTypeScript } from './detect-typescript';\nimport { registerEditorCache } from './editor-caches';\n\ninterface CompilationKey {\n code: string;\n modules: [string, unknown][];\n}\n\n// The keys still in the LRU. Comparing at most `CACHE_LIMIT` of them avoids\n// keeping every module object ever seen, or serializing modules, which can be\n// cyclic or closures.\nconst compilationKeys = new Set<CompilationKey>();\nconst compilationCache = createBoundedCache<CompilationKey, Module>(\n CONFIG.CACHE_LIMIT,\n key => compilationKeys.delete(key),\n);\n\nconst createCacheKey = (\n code: string,\n modules: Record<string, unknown>,\n): CompilationKey => ({\n code,\n modules: Object.keys(modules)\n .sort()\n .map(name => [name, modules[name]]),\n});\n\n// Objects/functions compare by reference; primitives compare by value.\n// Replace a module object when its implementation changes. In-place edits\n// inside an existing module are intentionally not observed by this cache.\nexport const compile = (\n code: string,\n modules: Record<string, unknown>,\n): Module => {\n const candidate = createCacheKey(code, modules);\n\n for (const key of compilationKeys) {\n if (\n key.code === candidate.code &&\n key.modules.length === candidate.modules.length &&\n key.modules.every(\n ([name, value], index) =>\n name === candidate.modules[index]![0] &&\n Object.is(value, candidate.modules[index]![1]),\n )\n ) {\n return compilationCache.get(key)!;\n }\n }\n\n const result = compileModule(code, Object.fromEntries(candidate.modules));\n\n compilationCache.set(candidate, result);\n compilationKeys.add(candidate);\n\n return result;\n};\n\n// `compilationKeys` needs no separate pass: it is drained by the `onEvict`\n// the cache was built with, which `clear()` runs for every entry.\nexport const clearCompilationCache = () => {\n compilationCache.clear();\n};\n\n// Transforms JSX and TypeScript with `@babel/standalone`'s presets, without\n// the `typescript` package (#192). Babel works per file without types, so a\n// `const enum` becomes a real enum and legacy decorators aren't supported.\n// On an error, the input comes back unchanged.\nexport const transformCode = (code: string, isTypeScript = false): string => {\n try {\n const result = Babel.transform(code, {\n filename: isTypeScript ? 'preview.tsx' : 'preview.jsx',\n presets: isTypeScript ? ['typescript', 'env', 'react'] : ['env', 'react'],\n sourceType: 'module',\n plugins: [Babel.availablePlugins['transform-modules-commonjs']],\n }).code;\n\n return result || '';\n } catch (e) {\n console.error('❌ Babel transformation error:', e);\n return code;\n }\n};\n\nconst compileModule = (\n code: string,\n modules: Record<string, unknown>,\n): Module => {\n const isTypeScript = detectTypeScript(code);\n\n type RenderFunction = (\n exports: Module['exports'],\n require: (name: string) => unknown,\n module: Module,\n React: typeof import('react'),\n ) => void;\n\n let render: RenderFunction;\n\n // Runs in this window's own JS realm, not inside the preview iframe — the iframe only\n // renders the resulting React elements via portal. See README \"Security Notes\".\n try {\n render = new Function(\n 'exports',\n 'require',\n 'module',\n 'React',\n transformCode(code, isTypeScript),\n ) as RenderFunction;\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : 'Syntax error';\n return { exports: {}, error: errorMessage };\n }\n\n const module: Module = { exports: {} };\n\n const customRequire = (name: string) => {\n if (name === 'react') {\n return React;\n }\n\n // Check presence, not truthiness: a module can be a falsy value (`0`, `''`,\n // `false`, `null`).\n if (name in modules) {\n return modules[name];\n }\n\n throw new Error(`Module not found: ${name}`);\n };\n\n try {\n render(module.exports, customRequire, module, React);\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : 'Runtime error';\n return { exports: {}, error: errorMessage };\n }\n\n return module;\n};\n\nregisterEditorCache(clearCompilationCache);\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants-DU17Tdwo.js","names":[],"sources":["../src/constants/index.ts"],"sourcesContent":["import { type Section } from '../types';\n\nexport const STORAGE_KEY = 'live-editor-code';\n\nexport const CONFIG = {\n CACHE_LIMIT: 50,\n // The document parse cache. Its entries are whole Babel ASTs (about\n // 0.66 MB per document), and only the current and previous version get hits,\n // so it keeps few (#106).\n DOCUMENT_CACHE_LIMIT: 4,\n // The script cache (`utils/scripts.ts`). Its entries are live blob URLs,\n // revoked on eviction, and a preview loads only a few scripts (#104).\n SCRIPT_CACHE_LIMIT: 10,\n} as const;\n\nexport const DATA_ATTR = {\n ID: 'data-id',\n BINDING: 'data-binding',\n // Names an entry of `Live.Dnd`'s `bindingKeys` instead of declaring the\n // bindings inline (#513).\n BINDING_KEY: 'data-binding-key',\n ITEM: 'data-item',\n} as const;\n\n// Attributes the editor owns. `data-id` links the canvas to the source,\n// `data-name` is a section's name, and `data-binding` and `data-binding-key`\n// declare what each edit is checked against. A binding can't target them,\n// since rewriting one would break that link (#429).\nexport const RESERVED_BINDING_PROPERTIES: readonly string[] = [\n DATA_ATTR.ID,\n 'data-name',\n DATA_ATTR.BINDING,\n DATA_ATTR.BINDING_KEY,\n];\n\nexport const REGEX = {\n NUMBER: /^\\d+(\\.\\d+)?$/,\n BOOLEAN_OR_NULL: /^(true|false|null|undefined)$/,\n MODULE_IMPORT_EXPORT_LINE:\n /^\\s*(?:import|export)\\b[^\\n]*\\bfrom\\s+['\"][^'\"]*['\"];?\\s*$/gm,\n} as const;\n\nexport const TS_PATTERNS = [\n /interface\\s+\\w+/,\n /type\\s+\\w+\\s*=/,\n /:\\s*\\w+(\\[\\])?(\\s*\\||\\s*&|\\s*=|\\s*;|\\s*,|\\s*\\))/,\n /as\\s+\\w+/,\n /<[A-Z]\\w*>/,\n /enum\\s+\\w+/,\n /public\\s+|private\\s+|protected\\s+/,\n /readonly\\s+/,\n /\\?\\s*:/,\n] as const;\n\nexport const BINDING_PROP = {\n CHILDREN: 'children',\n INNER_TEXT: 'innerText',\n INNER_HTML: 'innerHTML',\n ITEMS: 'items',\n} as const;\n\n// The element whose `<section>` children are a document's sections, unless\n// `Live.Dnd`'s `containerId` says otherwise (#449).\nexport const DEFAULT_CONTAINER_ID = 'app-container';\n\n// A new, empty document built around `containerId`. `createDocument()` in\n// `@jbpark/live-editor/utils` is the public way in; this is the template\n// behind it and behind `DEFAULT_TEMPLATE`.\nexport const documentTemplate = (containerId: string) => `\nimport * as ui from 'ui-kit';\nimport { cn } from 'ui-kit/utils';\n\nconst App = () => {\n return (\n <main id=\"${containerId}\"></main>\n )\n}\n\nexport default App;\n`;\n\nexport const DEFAULT_TEMPLATE = documentTemplate(DEFAULT_CONTAINER_ID);\n\nexport const PALETTE_SECTIONS: Section[] = [\n {\n id: 'hero',\n name: 'Hero',\n code: `\n <section\n data-name=\"Hero\"\n className={cn(\n 'text-center text-white',\n //\n )}\n >\n <ui.Space\n data-id=\"\"\n data-binding={[\n {\n label: 'Background Style',\n property: 'style',\n type: 'object',\n // Types the keys inside the object value; a leaf takes the same\n // fields as a top-level binding.\n render: {\n backgroundColor: { type: 'color', label: 'Background Color' },\n },\n // Not a library field, so it arrives as meta.group.\n group: 'layout',\n },\n {\n label: 'Content Spacing',\n property: 'size',\n type: 'number',\n // Constraints the library validates with or without a widget.\n min: 0,\n max: 40,\n // Presentation only: the built-in panel ignores it and shows a\n // number input; a custom panel can switch on widget.type.\n widget: { type: 'slider', step: 4, unit: 'px' },\n group: 'layout',\n },\n ]}\n style={{\n backgroundColor: '#6366f1',\n }}\n orientation=\"vertical\"\n size={16}\n align=\"center\"\n className={cn(\n 'px-5 py-15',\n //\n )}\n >\n <h1\n data-id=\"\"\n data-binding={[\n {\n label: 'Title',\n property: 'innerText',\n type: 'string',\n required: true,\n },\n ]}\n className={cn(\n 'text-5xl font-bold',\n //\n )}\n >\n Welcome to Live Editor\n </h1>\n <p\n data-id=\"\"\n data-binding={[\n {\n label: 'Description',\n type: 'richtext',\n },\n ]}\n className={cn(\n 'text-xl',\n //\n )}\n >\n Build <strong>dynamic components</strong> with drag & drop\n </p>\n <ui.Button\n data-id=\"\"\n data-binding={[\n {\n label: 'Button Text',\n property: 'innerText',\n },\n {\n label: 'Button Icon',\n property: 'icon',\n type: 'jsx',\n },\n {\n label: 'Button Size',\n property: 'size',\n options: [\n { label: 'Small', value: 'small' },\n { label: 'Middle', value: 'middle' },\n { label: 'Large', value: 'large' },\n ],\n },\n {\n label: 'Button Variant',\n property: 'variant',\n options: [\n { label: 'Solid', value: 'solid' },\n { label: 'Outlined', value: 'outlined' },\n { label: 'Filled', value: 'filled' },\n ],\n },\n ]}\n icon={<span>🚀</span>}\n size=\"large\"\n variant=\"solid\"\n className={cn(\n 'bg-white text-indigo-500',\n 'hover:bg-gray-50',\n //\n )}\n >\n Get Started\n </ui.Button>\n </ui.Space>\n </section>\n `,\n },\n {\n id: 'features',\n name: 'Features',\n code: `\n <section\n data-name=\"Features\"\n className={cn(\n 'px-5 py-10',\n //\n )}\n >\n <ui.Space\n data-id=\"\"\n data-binding={[\n {\n label: 'Feature Cards',\n property: 'children',\n },\n ]}\n className={cn(\n 'mx-auto max-w-7xl',\n //\n )}\n size=\"large\"\n wrap\n >\n <ui.Card\n className={cn(\n 'min-w-70 flex-1 text-center',\n //\n )}\n >\n <ui.Space orientation=\"vertical\" size=\"middle\" align=\"center\">\n <div\n data-id=\"\"\n data-binding={[\n {\n label: 'Icon',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'text-5xl',\n //\n )}\n >\n 🚀\n </div>\n <h3\n data-id=\"\"\n data-binding={[\n {\n label: 'Title',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'text-2xl text-gray-800',\n //\n )}\n >\n Feature 1\n </h3>\n <p\n data-id=\"\"\n data-binding={[\n {\n label: 'Description',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'text-gray-600',\n //\n )}\n >\n Amazing feature description\n </p>\n </ui.Space>\n </ui.Card>\n <ui.Card\n className={cn(\n 'min-w-70 flex-1 text-center',\n //\n )}\n >\n <ui.Space orientation=\"vertical\" size=\"middle\" align=\"center\">\n <div\n data-id=\"\"\n data-binding={[\n {\n label: 'Icon',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'text-5xl',\n //\n )}\n >\n ⚡\n </div>\n <h3\n data-id=\"\"\n data-binding={[\n {\n label: 'Title',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'text-2xl text-gray-800',\n //\n )}\n >\n Feature 2\n </h3>\n <p\n data-id=\"\"\n data-binding={[\n {\n label: 'Description',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'text-gray-600',\n //\n )}\n >\n Another great feature\n </p>\n </ui.Space>\n </ui.Card>\n <ui.Card\n className={cn(\n 'min-w-70 flex-1 text-center',\n //\n )}\n >\n <ui.Space orientation=\"vertical\" size=\"middle\" align=\"center\">\n <div\n data-id=\"\"\n data-binding={[\n {\n label: 'Icon',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'text-5xl',\n //\n )}\n >\n ✨\n </div>\n <h3\n data-id=\"\"\n data-binding={[\n {\n label: 'Title',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'text-2xl text-gray-800',\n //\n )}\n >\n Feature 3\n </h3>\n <p\n data-id=\"\"\n data-binding={[\n {\n label: 'Description',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'text-gray-600',\n //\n )}\n >\n One more awesome thing\n </p>\n </ui.Space>\n </ui.Card>\n </ui.Space>\n </section>\n `,\n },\n {\n id: 'about',\n name: 'About',\n code: `\n <section\n data-name=\"About\"\n className={cn(\n 'mx-auto max-w-3xl px-5 py-10',\n //\n )}\n >\n <ui.Typography.Title\n data-id=\"\"\n data-binding={[{ label: 'Title', property: 'innerText' }]}\n level={2}\n className={cn(\n 'mb-5 text-4xl text-gray-800',\n //\n )}\n >\n About Live Editor\n </ui.Typography.Title>\n <ui.Typography.Paragraph\n data-id=\"\"\n data-binding={[\n {\n label: 'Description 1',\n type: 'richtext',\n },\n ]}\n className={cn(\n 'mb-4 text-lg leading-relaxed text-gray-600',\n //\n )}\n >\n Live Editor is a powerful visual development tool that enables you to\n create stunning web interfaces through an intuitive drag-and-drop\n experience. Built with modern web technologies, it streamlines your\n workflow and accelerates development cycles.\n </ui.Typography.Paragraph>\n <ui.Typography.Paragraph\n data-id=\"\"\n data-binding={[\n {\n label: 'Description 2',\n type: 'richtext',\n },\n ]}\n className={cn(\n 'text-lg leading-relaxed text-gray-600',\n //\n )}\n >\n Whether you're a seasoned developer or just starting out, Live\n Editor provides the flexibility and control you need to bring your\n creative vision to life. Focus on what matters most - building great\n products.\n </ui.Typography.Paragraph>\n </section>\n `,\n },\n {\n id: 'cta',\n name: 'CTA',\n code: `\n <section\n data-name=\"CTA\"\n className={cn(\n 'bg-gray-50 px-5 py-12 text-center',\n //\n )}\n >\n <div\n className={cn(\n 'mx-auto max-w-xl',\n //\n )}\n >\n <ui.Typography.Title\n data-id=\"\"\n data-binding={[{ label: 'Title', property: 'innerText' }]}\n level={2}\n className={cn(\n 'mb-4 text-3xl text-gray-800',\n //\n )}\n >\n Ready to get started?\n </ui.Typography.Title>\n <ui.Typography.Paragraph\n data-id=\"\"\n data-binding={[\n {\n label: 'Description',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'mb-6 text-lg text-gray-600',\n //\n )}\n >\n Start exploring what Live Editor can do for your projects\n </ui.Typography.Paragraph>\n <ui.Space\n data-id=\"\"\n data-binding={[{ label: 'Buttons', property: 'children' }]}\n className={cn(\n 'flex flex-wrap justify-center gap-4',\n //\n )}\n >\n <ui.Button\n data-id=\"\"\n data-binding={[\n {\n label: 'Button 1 Text',\n property: 'innerText',\n },\n ]}\n size=\"large\"\n className={cn(\n 'bg-indigo-500 px-8 text-base text-white',\n 'hover:bg-indigo-600',\n //\n )}\n >\n View Documentation\n </ui.Button>\n <ui.Button\n data-id=\"\"\n data-binding={[\n {\n label: 'Button 2 Text',\n property: 'innerText',\n },\n ]}\n variant=\"outlined\"\n size=\"large\"\n className={cn(\n 'border-2 border-indigo-500 px-8 text-base text-indigo-500',\n 'hover:bg-indigo-50',\n //\n )}\n >\n View Examples\n </ui.Button>\n </ui.Space>\n </div>\n </section>\n `,\n },\n {\n id: 'getting-started',\n name: 'Getting Started',\n code: `\n <section\n data-name=\"Getting Started\"\n className={cn(\n 'px-5 py-16',\n //\n )}\n >\n <div\n className={cn(\n 'mx-auto max-w-5xl',\n //\n )}\n >\n <ui.Typography.Title\n data-id=\"\"\n data-binding={[{ label: 'Title', property: 'innerText' }]}\n level={2}\n className={cn(\n 'mb-12 text-center text-4xl font-bold text-gray-800',\n //\n )}\n >\n How to Get Started\n </ui.Typography.Title>\n <div\n className={cn(\n 'grid gap-8 md:grid-cols-3',\n //\n )}\n >\n <ui.Card\n data-id=\"\"\n data-binding={[\n {\n label: 'Card 1 Content',\n property: 'children',\n },\n ]}\n className={cn(\n 'rounded-lg border-2 border-gray-200 bg-white p-8',\n //\n )}\n >\n <ui.Typography.Title\n data-id=\"\"\n data-binding={[\n {\n label: 'Card 1 Title',\n property: 'innerText',\n },\n ]}\n level={3}\n className={cn(\n 'mb-2 text-2xl font-bold text-gray-800',\n //\n )}\n >\n Explore\n </ui.Typography.Title>\n <ui.Typography.Paragraph\n data-id=\"\"\n data-binding={[\n {\n label: 'Card 1 Icon',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'mb-6 text-4xl font-bold text-indigo-600',\n //\n )}\n >\n 📚\n </ui.Typography.Paragraph>\n <ul\n className={cn(\n 'mb-8 space-y-3 text-gray-600',\n //\n )}\n >\n <li>✓ Read documentation</li>\n <li>✓ Browse examples</li>\n <li>✓ Try the demo</li>\n </ul>\n <ui.Button\n data-id=\"\"\n data-binding={[\n {\n label: 'Card 1 Button',\n property: 'innerText',\n },\n ]}\n variant=\"outlined\"\n size=\"large\"\n block\n className={cn(\n 'border-2 border-indigo-500 text-indigo-500',\n 'hover:bg-indigo-50',\n //\n )}\n >\n View Docs\n </ui.Button>\n </ui.Card>\n <ui.Card\n data-id=\"\"\n data-binding={[\n {\n label: 'Card 2 Content',\n property: 'children',\n },\n ]}\n className={cn(\n 'relative rounded-lg border-2 border-indigo-500 bg-white p-8',\n //\n )}\n >\n <div\n className={cn(\n 'absolute -top-4 left-1/2 -translate-x-1/2',\n 'rounded-full bg-indigo-500 px-4 py-1 text-sm text-white',\n //\n )}\n >\n Recommended\n </div>\n <ui.Typography.Title\n data-id=\"\"\n data-binding={[\n {\n label: 'Card 2 Title',\n property: 'innerText',\n },\n ]}\n level={3}\n className={cn(\n 'mb-2 text-2xl font-bold text-gray-800',\n //\n )}\n >\n Practice\n </ui.Typography.Title>\n <ui.Typography.Paragraph\n data-id=\"\"\n data-binding={[\n {\n label: 'Card 2 Icon',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'mb-6 text-4xl font-bold text-indigo-600',\n //\n )}\n >\n 💻\n </ui.Typography.Paragraph>\n <ul\n className={cn(\n 'mb-8 space-y-3 text-gray-600',\n //\n )}\n >\n <li>✓ Interactive playground</li>\n <li>✓ Experiment freely</li>\n <li>✓ Build your own</li>\n </ul>\n <ui.Button\n data-id=\"\"\n data-binding={[\n {\n label: 'Card 2 Button',\n property: 'innerText',\n },\n ]}\n size=\"large\"\n block\n className={cn(\n 'bg-indigo-500 text-white',\n 'hover:bg-indigo-600',\n //\n )}\n >\n Start Building\n </ui.Button>\n </ui.Card>\n <ui.Card\n data-id=\"\"\n data-binding={[\n {\n label: 'Card 3 Content',\n property: 'children',\n },\n ]}\n className={cn(\n 'rounded-lg border-2 border-gray-200 bg-white p-8',\n //\n )}\n >\n <ui.Typography.Title\n data-id=\"\"\n data-binding={[\n {\n label: 'Card 3 Title',\n property: 'innerText',\n },\n ]}\n level={3}\n className={cn(\n 'mb-2 text-2xl font-bold text-gray-800',\n //\n )}\n >\n Contribute\n </ui.Typography.Title>\n <ui.Typography.Paragraph\n data-id=\"\"\n data-binding={[\n {\n label: 'Card 3 Icon',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'mb-6 text-4xl font-bold text-indigo-600',\n //\n )}\n >\n 🤝\n </ui.Typography.Paragraph>\n <ul\n className={cn(\n 'mb-8 space-y-3 text-gray-600',\n //\n )}\n >\n <li>✓ Report issues</li>\n <li>✓ Suggest features</li>\n <li>✓ Submit pull requests</li>\n </ul>\n <ui.Button\n data-id=\"\"\n data-binding={[\n {\n label: 'Card 3 Button',\n property: 'innerText',\n },\n ]}\n variant=\"outlined\"\n size=\"large\"\n block\n className={cn(\n 'border-2 border-indigo-500 text-indigo-500',\n 'hover:bg-indigo-50',\n //\n )}\n >\n View on GitHub\n </ui.Button>\n </ui.Card>\n </div>\n </div>\n </section>\n `,\n },\n {\n id: 'key-features',\n name: 'Key Features',\n code: `\n <section\n data-name=\"Key Features\"\n className={cn(\n 'bg-indigo-50 px-5 py-16',\n //\n )}\n >\n <div\n className={cn(\n 'mx-auto max-w-6xl',\n //\n )}\n >\n <h2\n data-id=\"\"\n data-binding={[{ label: 'Title', property: 'innerText' }]}\n className={cn(\n 'mb-12 text-center text-4xl font-bold text-gray-800',\n //\n )}\n >\n Key Features\n </h2>\n <div\n className={cn(\n 'grid gap-8 md:grid-cols-2',\n //\n )}\n >\n <div\n className={cn(\n 'rounded-lg bg-white p-8 shadow-md',\n //\n )}\n >\n <div\n className={cn(\n 'mb-4 text-4xl',\n //\n )}\n >\n 🎨\n </div>\n <h3\n className={cn(\n 'mb-4 text-2xl font-bold text-gray-800',\n //\n )}\n >\n Visual Editing\n </h3>\n <p\n className={cn(\n 'text-gray-600',\n //\n )}\n >\n See your changes in real-time as you build. Live Editor provides\n instant visual feedback, making it easier to understand how code\n affects your interface.\n </p>\n </div>\n <div\n className={cn(\n 'rounded-lg bg-white p-8 shadow-md',\n //\n )}\n >\n <div\n className={cn(\n 'mb-4 text-4xl',\n //\n )}\n >\n 🧩\n </div>\n <h3\n className={cn(\n 'mb-4 text-2xl font-bold text-gray-800',\n //\n )}\n >\n Component-Based\n </h3>\n <p\n className={cn(\n 'text-gray-600',\n //\n )}\n >\n Build with reusable components that you can drag, drop, and\n customize. Learn modern development patterns while creating your\n interface.\n </p>\n </div>\n </div>\n </div>\n </section>\n `,\n },\n {\n id: 'stats',\n name: 'Stats',\n code: `\n <section\n data-name=\"Stats\"\n className={cn(\n 'bg-indigo-600 text-white',\n //\n )}\n >\n <ui.Marquees\n data-id=\"\"\n data-binding={[\n { label: 'Stats Items', property: 'items' },\n {\n label: 'Scroll Speed',\n property: 'speed',\n type: 'number',\n min: 20,\n max: 300,\n // The bare-string form; it arrives as { type: 'slider' }.\n widget: 'slider',\n },\n { label: 'Pause On Hover', property: 'pauseOnHover', type: 'boolean' },\n ]}\n speed={100}\n pauseOnHover={true}\n autoFill={true}\n className=\"px-5 py-16\"\n items={[\n {\n key: 'stats-row',\n children: (\n <div\n data-id=\"\"\n data-binding={[\n {\n label: 'Stats Cards',\n property: 'children',\n },\n ]}\n className={cn('flex gap-20')}\n >\n <div\n className={cn(\n 'min-w-60 text-center',\n //\n )}\n >\n <p\n data-id=\"\"\n data-binding={[\n {\n label: 'Title',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'mb-2 text-5xl font-bold',\n //\n )}\n >\n Open\n </p>\n <p\n data-id=\"\"\n data-binding={[\n {\n label: 'Description',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'text-indigo-200',\n //\n )}\n >\n Source Project\n </p>\n </div>\n <div\n className={cn(\n 'min-w-60 text-center',\n //\n )}\n >\n <p\n data-id=\"\"\n data-binding={[\n {\n label: 'Title',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'mb-2 text-5xl font-bold',\n //\n )}\n >\n Free\n </p>\n <p\n data-id=\"\"\n data-binding={[\n {\n label: 'Description',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'text-indigo-200',\n //\n )}\n >\n For Everyone\n </p>\n </div>\n <div\n className={cn(\n 'min-w-60 text-center',\n //\n )}\n >\n <p\n data-id=\"\"\n data-binding={[\n {\n label: 'Title',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'mb-2 text-5xl font-bold',\n //\n )}\n >\n Active\n </p>\n <p\n data-id=\"\"\n data-binding={[\n {\n label: 'Description',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'text-indigo-200',\n //\n )}\n >\n Community\n </p>\n </div>\n <div\n className={cn(\n 'min-w-60 text-center',\n //\n )}\n >\n <p\n data-id=\"\"\n data-binding={[\n {\n label: 'Title',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'mb-2 text-5xl font-bold',\n //\n )}\n >\n Learn\n </p>\n <p\n data-id=\"\"\n data-binding={[\n {\n label: 'Description',\n property: 'innerText',\n },\n ]}\n className={cn(\n 'text-indigo-200',\n //\n )}\n >\n By Doing\n </p>\n </div>\n </div>\n ),\n },\n ]}\n />\n </section>\n `,\n },\n {\n id: 'roadmap',\n name: 'Roadmap',\n code: `\n <section\n data-name=\"Roadmap\"\n className={cn(\n 'px-5 py-16',\n //\n )}\n >\n <div\n className={cn(\n 'mx-auto max-w-3xl',\n //\n )}\n >\n <h2\n data-id=\"\"\n data-binding={[\n { label: 'Title', property: 'innerText', required: true },\n ]}\n className={cn(\n 'mb-8 text-center text-4xl font-bold text-gray-800',\n //\n )}\n >\n Roadmap\n </h2>\n <ui.List\n data-id=\"\"\n data-binding={[\n {\n label: 'Milestones',\n property: 'data',\n type: 'array',\n // One leaf per item key. Each leaf takes the same fields as a\n // top-level binding, and label falls back to the key.\n render: {\n title: {\n type: 'string',\n label: 'Title',\n required: true,\n },\n date: { type: 'date', label: 'Release Date', required: true },\n status: {\n type: 'string',\n label: 'Status',\n options: [\n { label: 'Planned', value: 'planned' },\n { label: 'In Progress', value: 'in-progress' },\n { label: 'Shipped', value: 'shipped' },\n ],\n },\n progress: {\n type: 'number',\n label: 'Progress',\n min: 0,\n max: 100,\n widget: { type: 'slider', step: 10, unit: '%' },\n },\n color: { type: 'color', label: 'Accent Color' },\n highlight: { type: 'boolean', label: 'Highlight' },\n // An object-valued key: its own render map types the keys\n // inside it, one level further down.\n link: {\n type: 'object',\n label: 'Link',\n render: {\n text: { type: 'string', label: 'Link Text' },\n href: {\n type: 'url',\n label: 'Link URL',\n pattern: '^https://',\n // Not a library field, so it arrives as meta.hint.\n hint: 'Must use https',\n },\n },\n },\n },\n },\n ]}\n data={[\n {\n title: 'Visual editing',\n date: '2026-03-02',\n status: 'shipped',\n progress: 100,\n color: '#22c55e',\n highlight: false,\n link: { text: 'Release notes', href: 'https://github.com' },\n },\n {\n title: 'Custom panels',\n date: '2026-09-26',\n status: 'in-progress',\n progress: 60,\n color: '#6366f1',\n highlight: true,\n link: { text: 'Guide', href: 'https://github.com' },\n },\n {\n title: 'Collaboration',\n date: '2027-01-15',\n status: 'planned',\n progress: 0,\n color: '#f59e0b',\n highlight: false,\n link: { text: 'Discussion', href: 'https://github.com' },\n },\n ]}\n itemKey={item => item.title}\n renderItem={item => (\n <div\n className={cn(\n 'flex items-center gap-4 border-b border-gray-100 py-4',\n item.highlight && 'rounded-lg bg-indigo-50 px-4',\n )}\n >\n <span\n className=\"size-3 shrink-0 rounded-full\"\n style={{ backgroundColor: item.color }}\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold text-gray-800\">{item.title}</p>\n <p className=\"text-sm text-gray-500\">\n {item.date} · {item.status}\n </p>\n <ui.Progress value={item.progress} className=\"mt-2\" />\n </div>\n <a\n href={item.link.href}\n className=\"text-sm text-indigo-500 hover:underline\"\n >\n {item.link.text}\n </a>\n </div>\n )}\n />\n <p\n className={cn(\n 'mt-6 text-center text-sm text-gray-500',\n //\n )}\n >\n Last updated{' '}\n <time\n data-id=\"\"\n data-binding={[\n {\n label: 'Updated On',\n property: 'dateTime',\n type: 'date',\n required: true,\n },\n { label: 'Updated Text', property: 'innerText' },\n ]}\n dateTime=\"2026-09-26\"\n >\n September 26, 2026\n </time>\n {' · '}\n <a\n data-id=\"\"\n data-binding={[\n { label: 'Link Text', property: 'innerText' },\n {\n label: 'Link URL',\n property: 'href',\n type: 'url',\n required: true,\n pattern: '^https://',\n },\n ]}\n href=\"https://github.com/pjb0811/live-editor/releases\"\n className=\"text-indigo-500 hover:underline\"\n >\n All releases\n </a>\n </p>\n </div>\n </section>\n `,\n },\n {\n id: 'faq',\n name: 'FAQ',\n code: `\n <section\n data-name=\"FAQ\"\n className={cn(\n 'px-5 py-16',\n //\n )}\n >\n <div\n className={cn(\n 'mx-auto max-w-3xl',\n //\n )}\n >\n <ui.Typography.Title\n data-id=\"\"\n data-binding={[{ label: 'Title', property: 'innerText' }]}\n level={2}\n className={cn(\n 'mb-12 text-center text-4xl font-bold text-gray-800',\n //\n )}\n >\n Frequently Asked Questions\n </ui.Typography.Title>\n <ui.Collapse\n data-id=\"\"\n data-binding={[{ label: 'FAQ Items', property: 'items' }]}\n items={[\n {\n key: '1',\n label: (\n <ui.Typography.Text\n data-id=\"\"\n data-binding={[\n {\n label: 'Question 1',\n property: 'innerText',\n },\n ]}\n className={cn('text-lg font-semibold text-gray-800')}\n >\n What is Live Editor?\n </ui.Typography.Text>\n ),\n children: (\n <ui.Typography.Paragraph\n data-id=\"\"\n data-binding={[\n {\n label: 'Answer 1',\n type: 'richtext',\n },\n ]}\n className={cn('text-gray-600')}\n >\n Live Editor is an open-source tool for building web\n interfaces visually. It helps you learn web development by\n providing instant feedback as you build components.\n </ui.Typography.Paragraph>\n ),\n },\n {\n key: '2',\n label: (\n <ui.Typography.Text\n data-id=\"\"\n data-binding={[\n {\n label: 'Question 2',\n property: 'innerText',\n },\n ]}\n className={cn('text-lg font-semibold text-gray-800')}\n >\n Do I need coding experience?\n </ui.Typography.Text>\n ),\n children: (\n <ui.Typography.Paragraph\n data-id=\"\"\n data-binding={[\n {\n label: 'Answer 2',\n type: 'richtext',\n },\n ]}\n className={cn('text-gray-600')}\n >\n No! Live Editor is designed for all skill levels. Beginners\n can learn through visual editing, while experienced\n developers can work directly with code.\n </ui.Typography.Paragraph>\n ),\n },\n {\n key: '3',\n label: (\n <ui.Typography.Text\n data-id=\"\"\n data-binding={[\n {\n label: 'Question 3',\n property: 'innerText',\n },\n ]}\n className={cn('text-lg font-semibold text-gray-800')}\n >\n Is it really free?\n </ui.Typography.Text>\n ),\n children: (\n <ui.Typography.Paragraph\n data-id=\"\"\n data-binding={[\n {\n label: 'Answer 3',\n type: 'richtext',\n },\n ]}\n className={cn('text-gray-600')}\n >\n Yes, completely free! Live Editor is an open-source project\n with no hidden costs or premium tiers. All features are\n available to everyone.\n </ui.Typography.Paragraph>\n ),\n },\n {\n key: '4',\n label: (\n <ui.Typography.Text\n data-id=\"\"\n data-binding={[\n {\n label: 'Question 4',\n property: 'innerText',\n },\n ]}\n className={cn('text-lg font-semibold text-gray-800')}\n >\n How can I contribute?\n </ui.Typography.Text>\n ),\n children: (\n <ui.Typography.Paragraph\n data-id=\"\"\n data-binding={[\n {\n label: 'Answer 4',\n type: 'richtext',\n },\n ]}\n className={cn('text-gray-600')}\n >\n You can contribute by reporting bugs, suggesting features,\n sharing your projects, or contributing code. Check our\n GitHub repository for more details.\n </ui.Typography.Paragraph>\n ),\n },\n ]}\n />\n </div>\n </section>\n `,\n },\n];\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"core-Bgfek364.js","names":[],"sources":["../src/components/editor/use-format-code.ts","../src/components/editor/core.tsx"],"sourcesContent":["import { useCallback, useMemo } from 'react';\n\nimport { detectTypeScript } from '~/utils/detect-typescript';\n\n// Prettier is an optional peer dependency (#282), loaded on first use.\n// Without it, code comes back unformatted.\nconst loadPrettier = async () => {\n const [prettier, babel, estree, typescript] = await Promise.all([\n import('prettier'),\n import('prettier/plugins/babel'),\n import('prettier/plugins/estree'),\n import('prettier/plugins/typescript'),\n ]);\n\n return {\n format: prettier.format,\n plugins: [babel.default, estree.default, typescript.default],\n };\n};\n\nconst DEFAULT_PRETTIER_OPTIONS: Record<string, unknown> = {\n tabWidth: 2,\n singleQuote: true,\n trailingComma: 'all',\n htmlWhitespaceSensitivity: 'ignore',\n arrowParens: 'avoid',\n printWidth: 60,\n};\n\nexport interface UseFormatCodeOptions {\n fragment?: boolean;\n prettierOptions?: Record<string, unknown>;\n}\n\n// The formatter the built-in editor uses, shared with a custom\n// `renderEditor`.\nexport const useFormatCode = ({\n fragment,\n prettierOptions,\n}: UseFormatCodeOptions = {}) => {\n const prettierConfig = useMemo(\n () => ({\n ...DEFAULT_PRETTIER_OPTIONS,\n ...prettierOptions,\n }),\n [prettierOptions],\n );\n\n return useCallback(\n async (code: string) => {\n const isTypeScript = detectTypeScript(code);\n const source = fragment ? `<>${code}</>` : code;\n\n // Only a missing prettier is swallowed here; a genuine format error\n // (prettier present, but the code is unparseable) still propagates so\n // the editor's Cmd+S path can surface it via onError.\n let prettier: Awaited<ReturnType<typeof loadPrettier>>;\n try {\n prettier = await loadPrettier();\n } catch {\n return code;\n }\n\n const formatted = await prettier.format(source, {\n parser: isTypeScript ? 'typescript' : 'babel',\n plugins: prettier.plugins,\n ...prettierConfig,\n });\n\n if (fragment) {\n return formatted\n .replace(/^<>\\n?/, '')\n .replace(/\\n?<\\/>;?\\s*$/, '')\n .replace(/^ {2}/gm, '')\n .trim();\n }\n\n return formatted;\n },\n [prettierConfig, fragment],\n );\n};\n","import type { ComponentProps } from 'react';\n\nimport CodeEditor from '@jbpark/ui-kit/CodeEditor';\nimport { vscodeLight } from '@uiw/codemirror-theme-vscode';\nimport { type Extension } from '@uiw/react-codemirror';\n\nimport { cn } from '~/utils/cn';\n\nimport { useFormatCode } from './use-format-code';\n\nexport interface Props {\n value: string;\n height?: string;\n theme?: Extension | 'light' | 'dark' | 'none';\n prettierOptions?: Record<string, unknown>;\n fragment?: boolean;\n raw?: boolean;\n className?: string;\n onChange?: (value: string) => void;\n onSave?: (value: string) => void;\n onError?: (error: string | null) => void;\n onCreateEditor?: ComponentProps<typeof CodeEditor>['onCreateEditor'];\n onUpdate?: ComponentProps<typeof CodeEditor>['onUpdate'];\n}\n\n// `@jbpark/ui-kit`'s CodeEditor with this library's options (#346, #309):\n// `raw`, `fragment` and `prettierOptions` shape the Prettier formatter, and\n// `onError` is its `onFormatError`.\nconst Core = ({\n value,\n theme,\n height,\n className,\n prettierOptions,\n fragment,\n raw,\n onChange,\n onSave,\n onError,\n ...props\n}: Props) => {\n const formatCode = useFormatCode({ fragment, prettierOptions });\n\n return (\n <CodeEditor\n value={value}\n theme={theme ?? vscodeLight}\n height={height || '100%'}\n className={cn(className)}\n // `raw` (e.g. innerHTML) skips prettier; otherwise reuse the shared\n // prettier wiring as the Cmd+S formatter rather than reimplementing it.\n formatCode={raw ? undefined : formatCode}\n onChange={onChange}\n onSave={onSave}\n onFormatError={onError}\n {...props}\n />\n );\n};\n\nexport default Core;\n"],"mappings":";;;;;;;;AAMA,MAAM,eAAe,YAAY;CAC/B,MAAM,CAAC,UAAU,OAAO,QAAQ,cAAc,MAAM,QAAQ,IAAI;EAC9D,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;CACT,CAAC;CAED,OAAO;EACL,QAAQ,SAAS;EACjB,SAAS;GAAC,MAAM;GAAS,OAAO;GAAS,WAAW;EAAO;CAC7D;AACF;AAEA,MAAM,2BAAoD;CACxD,UAAU;CACV,aAAa;CACb,eAAe;CACf,2BAA2B;CAC3B,aAAa;CACb,YAAY;AACd;AASA,MAAa,iBAAiB,EAC5B,UACA,oBACwB,CAAC,MAAM;CAC/B,MAAM,iBAAiB,eACd;EACL,GAAG;EACH,GAAG;CACL,IACA,CAAC,eAAe,CAClB;CAEA,OAAO,YACL,OAAO,SAAiB;EACtB,MAAM,eAAe,iBAAiB,IAAI;EAC1C,MAAM,SAAS,WAAW,KAAK,KAAK,OAAO;EAK3C,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,aAAa;EAChC,QAAQ;GACN,OAAO;EACT;EAEA,MAAM,YAAY,MAAM,SAAS,OAAO,QAAQ;GAC9C,QAAQ,eAAe,eAAe;GACtC,SAAS,SAAS;GAClB,GAAG;EACL,CAAC;EAED,IAAI,UACF,OAAO,UACJ,QAAQ,UAAU,EAAE,CAAC,CACrB,QAAQ,iBAAiB,EAAE,CAAC,CAC5B,QAAQ,WAAW,EAAE,CAAC,CACtB,KAAK;EAGV,OAAO;CACT,GACA,CAAC,gBAAgB,QAAQ,CAC3B;AACF;;;ACrDA,MAAM,QAAQ,EACZ,OACA,OACA,QACA,WACA,iBACA,UACA,KACA,UACA,QACA,SACA,GAAG,YACQ;CACX,MAAM,aAAa,cAAc;EAAE;EAAU;CAAgB,CAAC;CAE9D,OACE,oBAAC,YAAD;EACS;EACP,OAAO,SAAS;EAChB,QAAQ,UAAU;EAClB,WAAW,GAAG,SAAS;EAGvB,YAAY,MAAM,KAAA,IAAY;EACpB;EACF;EACR,eAAe;EACf,GAAI;CACL,CAAA;AAEL"}
|