@hankchen/canvas-editor-vue 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/canvas/CanvasSurface.d.ts +28 -1
  2. package/dist/canvas/{ContentTypePill.d.ts → EditOverlayToolbar.d.ts} +19 -0
  3. package/dist/canvas/TextEditingOverlay.d.ts +31 -3
  4. package/dist/canvas/index.d.ts +1 -1
  5. package/dist/canvas/useTextEditing.d.ts +30 -8
  6. package/dist/{canvas-C70NSsoF.js → canvas-DqQislJ_.js} +813 -434
  7. package/dist/canvas.js +2 -2
  8. package/dist/index.js +15 -14
  9. package/dist/{layer-panel-BEcT9HhJ.js → layer-panel-DskyCgF5.js} +10 -10
  10. package/dist/layer-panel.js +1 -1
  11. package/dist/panel-theme.css +24 -0
  12. package/dist/popupPosition-Ck5Gsvjy.js +526 -0
  13. package/dist/property-panel/PropertyField.d.ts +6 -0
  14. package/dist/property-panel/fieldSchema.d.ts +7 -1
  15. package/dist/property-panel/fields/ImageSrcField.d.ts +5 -1
  16. package/dist/property-panel/fields/ResourceValueField.d.ts +19 -0
  17. package/dist/property-panel/fields/TextareaField.d.ts +1 -0
  18. package/dist/property-panel/fields/ValueTypeSegmented.d.ts +3 -1
  19. package/dist/property-panel/index.d.ts +5 -3
  20. package/dist/property-panel/usePropertyPanel.d.ts +16 -4
  21. package/dist/{property-panel-C2etsIhF.js → property-panel-B32ZBQdf.js} +472 -376
  22. package/dist/property-panel.js +3 -2
  23. package/dist/resourceTemplate-cdcQtp9a.js +246 -0
  24. package/dist/shared/ExpressionPathPicker.d.ts +25 -0
  25. package/dist/shared/completion.d.ts +40 -16
  26. package/dist/shared/expressionContext.d.ts +2 -2
  27. package/dist/shared/index.d.ts +14 -1
  28. package/dist/shared/mergeKey.d.ts +2 -0
  29. package/dist/shared/pathPickerModel.d.ts +11 -0
  30. package/dist/shared/popupPosition.d.ts +41 -0
  31. package/dist/shared/resourceTemplate.d.ts +7 -0
  32. package/dist/shared/useExpressionCompletion.d.ts +0 -7
  33. package/dist/shared/useExpressionDiagnostics.d.ts +15 -0
  34. package/dist/shared.js +10 -9
  35. package/dist/style.css +1 -1
  36. package/dist/uploadFile-D1RY-ji0.js +516 -0
  37. package/dist/usePathCompletion-Do17CQ6l.js +220 -0
  38. package/package.json +2 -2
  39. package/dist/completionAnchor-CDR5YZhu.js +0 -273
  40. package/dist/uploadFile-BOfv8aLG.js +0 -331
  41. package/dist/usePathCompletion-uJ5c_gQ0.js +0 -229
  42. /package/dist/{property-panel → shared}/fontPicker.d.ts +0 -0
@@ -1,2 +1,3 @@
1
- import { a as e, c as t, d as n, f as r, i, l as a, m as o, n as s, o as c, p as l, r as u, s as d, t as f, u as p } from "./property-panel-C2etsIhF.js";
2
- export { a as CANVAS_FIELD_SECTIONS, p as FIELD_SECTIONS_BY_TYPE, d as FONT_PICKER_KEY, e as IMAGE_UPLOAD_KEY, s as PropertyField, f as PropertyPanel, i as controlRegistry, n as fieldSectionsForPath, r as fieldSectionsForType, t as injectFontPicker, c as injectImageUpload, l as layerRoleAt, o as readField, u as usePropertyPanel };
1
+ import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, r as c, s as l, t as u, u as d } from "./property-panel-B32ZBQdf.js";
2
+ import { i as f, r as p } from "./uploadFile-D1RY-ji0.js";
3
+ export { l as CANVAS_FIELD_SECTIONS, t as FIELD_SECTIONS_BY_TYPE, p as FONT_PICKER_KEY, e as IMAGE_UPLOAD_KEY, o as PropertyField, u as PropertyPanel, i as controlRegistry, a as fieldSectionsForPath, d as fieldSectionsForType, f as injectFontPicker, s as injectImageUpload, n as layerRoleAt, r as readField, c as usePropertyPanel };
@@ -0,0 +1,246 @@
1
+ import { l as e, r as t } from "./popupPosition-Ck5Gsvjy.js";
2
+ import { Fragment as n, Teleport as r, computed as i, createBlock as a, createCommentVNode as o, createElementBlock as s, createElementVNode as c, defineComponent as l, nextTick as u, normalizeClass as d, normalizeStyle as ee, onScopeDispose as f, openBlock as p, ref as m, renderList as te, toDisplayString as h, watch as g, withModifiers as _ } from "vue";
3
+ //#region ../canvas-editor/src/shared/resourcePath.ts
4
+ function v(e, t) {
5
+ return {
6
+ kind: t,
7
+ start: 0,
8
+ end: e.length,
9
+ at: {
10
+ start: 0,
11
+ end: e.length
12
+ }
13
+ };
14
+ }
15
+ function y(t) {
16
+ let n = t.endsWith("\n") ? t.slice(0, -1) : t, r = /^\{\{([^{}]*)\}\}$/.exec(n);
17
+ if (r === null) return [v(t, "resource_not_single_ref")];
18
+ let i = e(r[1] ?? "");
19
+ return i === "" ? [v(t, "empty_fragment")] : i.includes("(") ? [v(t, "resource_function_call")] : [];
20
+ }
21
+ //#endregion
22
+ //#region src/shared/pathPickerModel.ts
23
+ function b(e, t) {
24
+ let n = e(t);
25
+ if (n === null) return [];
26
+ let r = [];
27
+ for (let t of n.candidates) t.kind === "path" && t.path !== void 0 && r.push({
28
+ path: t.path,
29
+ item: t,
30
+ drillable: x(e, t.path)
31
+ });
32
+ return r;
33
+ }
34
+ function x(e, t) {
35
+ let n = e(`${t}.`);
36
+ return n !== null && n.candidates.some((e) => e.kind === "path");
37
+ }
38
+ //#endregion
39
+ //#region src/shared/ExpressionPathPicker.vue?vue&type=script&setup=true&lang.ts
40
+ var S = ["title", "aria-label"], C = ["aria-expanded", "aria-label"], w = ["aria-label"], T = { class: "truncate font-mono" }, ne = {
41
+ key: 1,
42
+ class: "cn-pathpicker__empty px-2 py-2 text-cn-muted"
43
+ }, E = [
44
+ "aria-label",
45
+ "title",
46
+ "onClick",
47
+ "onMousemove"
48
+ ], D = { class: "cn-pathpicker__segment min-w-0 truncate font-mono text-cn-fg" }, O = {
49
+ key: 0,
50
+ class: "shrink-0 rounded-sm bg-cn-field px-1 py-0.5 text-[9px] leading-none text-cn-muted"
51
+ }, k = {
52
+ key: 1,
53
+ class: "min-w-0 flex-1 truncate text-[10px] leading-none text-cn-muted"
54
+ }, A = {
55
+ key: 2,
56
+ class: "cn-pathpicker__drill ml-auto shrink-0 text-cn-muted",
57
+ "aria-hidden": "true"
58
+ }, j = /* @__PURE__ */ l({
59
+ __name: "ExpressionPathPicker",
60
+ props: {
61
+ modelValue: {},
62
+ source: { type: [Function, null] },
63
+ disabled: { type: Boolean },
64
+ ariaLabel: {},
65
+ placeholder: {}
66
+ },
67
+ emits: ["update:modelValue", "change"],
68
+ setup(e, { emit: l }) {
69
+ let v = e, y = l, x = i(() => !v.disabled && v.source !== null), j = m(!1), M = m([]), N = m(0), P = i(() => M.value[M.value.length - 1] ?? null), re = i(() => {
70
+ let e = M.value[M.value.length - 2];
71
+ return e === void 0 ? "" : e.label === "" ? "根级字段" : e.label;
72
+ });
73
+ function F(e, t) {
74
+ return {
75
+ prefix: e,
76
+ label: t,
77
+ entries: v.source === null ? [] : b(v.source, e)
78
+ };
79
+ }
80
+ function ie() {
81
+ x.value && !j.value && (M.value = [F("", "")], N.value = 0, j.value = !0, u(() => {
82
+ Y(), K.value?.focus();
83
+ }));
84
+ }
85
+ function I() {
86
+ j.value && (L(), G.value?.focus());
87
+ }
88
+ function L() {
89
+ j.value = !1, M.value = [], N.value = 0;
90
+ }
91
+ function R() {
92
+ j.value ? I() : ie();
93
+ }
94
+ function z(e) {
95
+ v.source !== null && (M.value.push(F(`${e.path}.`, e.path)), N.value = 0, u(Y));
96
+ }
97
+ function B() {
98
+ M.value.length <= 1 || (M.value.pop(), N.value = 0, u(Y));
99
+ }
100
+ function V(e) {
101
+ if (e.drillable) {
102
+ z(e);
103
+ return;
104
+ }
105
+ y("update:modelValue", e.path), y("change", e.path), I();
106
+ }
107
+ function H() {
108
+ return P.value?.entries[N.value] ?? null;
109
+ }
110
+ function U(e) {
111
+ if (e.key === "Escape") {
112
+ e.preventDefault(), I();
113
+ return;
114
+ }
115
+ if (e.key === "Tab") {
116
+ I();
117
+ return;
118
+ }
119
+ let t = P.value?.entries ?? [];
120
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
121
+ e.preventDefault();
122
+ let n = t.length;
123
+ n > 0 && (N.value = (N.value + (e.key === "ArrowDown" ? 1 : n - 1)) % n);
124
+ return;
125
+ }
126
+ if (e.key === "ArrowRight") {
127
+ let t = H();
128
+ t?.drillable && (e.preventDefault(), z(t));
129
+ return;
130
+ }
131
+ if (e.key === "ArrowLeft") {
132
+ M.value.length > 1 && (e.preventDefault(), B());
133
+ return;
134
+ }
135
+ if (e.key === "Enter" || e.key === " ") {
136
+ let t = H();
137
+ t !== null && (e.preventDefault(), V(t));
138
+ }
139
+ }
140
+ let W = m(null), G = m(null), K = m(null), q = m(0), J = m(0);
141
+ function Y() {
142
+ if (!j.value) return;
143
+ let e = W.value, n = K.value;
144
+ if (e === null) return;
145
+ let r = e.getBoundingClientRect(), i = t({
146
+ anchor: r,
147
+ popupWidth: n?.offsetWidth ?? 0,
148
+ popupHeight: n?.offsetHeight ?? 0,
149
+ viewportWidth: window.innerWidth,
150
+ viewportHeight: window.innerHeight
151
+ });
152
+ q.value = i.top, J.value = i.left;
153
+ }
154
+ function X() {
155
+ Y();
156
+ }
157
+ g(j, (e) => {
158
+ e ? (window.addEventListener("pointerdown", Z), window.addEventListener("keydown", Q), window.addEventListener("scroll", X, !0), window.addEventListener("resize", X)) : $();
159
+ });
160
+ function Z(e) {
161
+ let t = e.target;
162
+ t instanceof Node && (W.value?.contains(t) || K.value?.contains(t) || L());
163
+ }
164
+ function Q(e) {
165
+ e.key === "Escape" && I();
166
+ }
167
+ function $() {
168
+ window.removeEventListener("pointerdown", Z), window.removeEventListener("keydown", Q), window.removeEventListener("scroll", X, !0), window.removeEventListener("resize", X);
169
+ }
170
+ return f($, !0), (t, i) => (p(), s("div", {
171
+ ref_key: "rootRef",
172
+ ref: W,
173
+ class: "cn-pathpicker flex w-full items-center gap-1.5"
174
+ }, [
175
+ c("div", {
176
+ class: d(["cn-pathpicker__value min-w-0 flex-1 truncate rounded-md border border-cn-field-line bg-cn-field px-2 py-1.5 font-mono text-[11px] leading-none text-cn-fg", e.modelValue === "" ? "text-cn-muted" : ""]),
177
+ title: e.modelValue,
178
+ "aria-label": e.ariaLabel
179
+ }, h(e.modelValue === "" ? e.placeholder ?? "点击右侧按钮选择字段" : e.modelValue), 11, S),
180
+ x.value ? (p(), s("button", {
181
+ key: 0,
182
+ ref_key: "toggleButton",
183
+ ref: G,
184
+ type: "button",
185
+ class: "cn-pathpicker__toggle h-7 shrink-0 rounded-md border border-cn-field-line bg-cn-field px-1.5 text-[11px] leading-none text-cn-muted transition-colors duration-100 hover:border-cn-muted/40 hover:text-cn-fg",
186
+ "aria-haspopup": "menu",
187
+ "aria-expanded": j.value,
188
+ "aria-label": e.ariaLabel === void 0 ? "选择字段路径" : `选择字段路径(${e.ariaLabel})`,
189
+ title: "选择字段路径",
190
+ onClick: R
191
+ }, "▾", 8, C)) : o("", !0),
192
+ (p(), a(r, { to: "body" }, [j.value && P.value !== null ? (p(), s("div", {
193
+ key: 0,
194
+ ref_key: "menuRoot",
195
+ ref: K,
196
+ class: "cn-props cn-pathpicker__menu fixed z-50 max-h-[240px] min-w-[180px] max-w-[280px] overflow-y-auto rounded-md border border-cn-field-line py-1 text-[11px] leading-none",
197
+ style: ee({
198
+ top: `${q.value}px`,
199
+ left: `${J.value}px`
200
+ }),
201
+ role: "menu",
202
+ "aria-label": e.ariaLabel === void 0 ? "字段路径菜单" : `${e.ariaLabel} 字段路径菜单`,
203
+ tabindex: "-1",
204
+ onKeydown: _(U, ["stop"])
205
+ }, [
206
+ M.value.length > 1 ? (p(), s("button", {
207
+ key: 0,
208
+ type: "button",
209
+ role: "menuitem",
210
+ class: "cn-pathpicker__back flex w-full items-center gap-1 border-b border-cn-line/70 px-2 py-1.5 text-left text-cn-muted",
211
+ onClick: B
212
+ }, [i[0] ||= c("span", { "aria-hidden": "true" }, "‹", -1), c("span", T, h(re.value), 1)])) : o("", !0),
213
+ P.value.entries.length === 0 ? (p(), s("div", ne, "无可选字段")) : o("", !0),
214
+ (p(!0), s(n, null, te(P.value.entries, (e, t) => (p(), s("button", {
215
+ key: `${e.path}`,
216
+ type: "button",
217
+ role: "menuitem",
218
+ class: d(["cn-pathpicker__option flex w-full items-center gap-1.5 px-2 py-1.5 text-left", t === N.value ? "cn-pathpicker__option--active" : ""]),
219
+ "aria-label": e.path,
220
+ title: e.path,
221
+ onClick: (t) => V(e),
222
+ onMousemove: (e) => N.value = t
223
+ }, [
224
+ c("span", D, h(e.item.segment), 1),
225
+ e.item.type ? (p(), s("span", O, h(e.item.type), 1)) : o("", !0),
226
+ e.item.description ?? e.item.title ? (p(), s("span", k, h(e.item.description ?? e.item.title), 1)) : o("", !0),
227
+ e.drillable ? (p(), s("span", A, "›")) : o("", !0)
228
+ ], 42, E))), 128))
229
+ ], 44, w)) : o("", !0)]))
230
+ ], 512));
231
+ }
232
+ });
233
+ //#endregion
234
+ //#region src/shared/resourceTemplate.ts
235
+ function M(t) {
236
+ if (y(t).length > 0) return null;
237
+ let n = t.endsWith("\n") ? t.slice(0, -1) : t, r = /^\{\{([^{}]*)\}\}$/.exec(n)?.[1];
238
+ if (r === void 0) return null;
239
+ let i = e(r);
240
+ return i === "" ? null : i;
241
+ }
242
+ function N(e) {
243
+ return `{{${e}}}`;
244
+ }
245
+ //#endregion
246
+ export { y as a, b as i, M as n, j as r, N as t };
@@ -0,0 +1,25 @@
1
+ import { CompletionSource } from './completion';
2
+ type __VLS_Props = {
3
+ /** 裸点路径原文(壳已拆 `{{}}`;存量违例拆不动时壳原样透传,只读回显) */
4
+ modelValue: string;
5
+ /** 候选源(面板同款注入缝);null = 未绑声明,不渲染菜单入口整体只读 */
6
+ source: CompletionSource | null;
7
+ /** 禁用(会话内容锁定面):同 null 的只读呈现 */
8
+ disabled?: boolean;
9
+ /** 展示框/菜单的可访问名(字段标签透传) */
10
+ ariaLabel?: string;
11
+ /** 空值占位文案 */
12
+ placeholder?: string;
13
+ };
14
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
15
+ change: (path: string) => any;
16
+ "update:modelValue": (path: string) => any;
17
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
18
+ onChange?: ((path: string) => any) | undefined;
19
+ "onUpdate:modelValue"?: ((path: string) => any) | undefined;
20
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
21
+ rootRef: HTMLDivElement;
22
+ toggleButton: HTMLButtonElement;
23
+ menuRoot: HTMLDivElement;
24
+ }, any>;
25
+ export default _default;
@@ -11,39 +11,63 @@ export interface CompletionItem {
11
11
  title?: string;
12
12
  /** 类型徽标数据(未声明为 undefined) */
13
13
  type?: string;
14
+ /**
15
+ * 插入后光标从插入文本末端回退的码元数(expression-type-methods 工单 12):
16
+ * date() 调用插入 = 2(光标落引号内 format 参数占位)、函数名候选 = 1(落括号
17
+ * 内);缺省 0(落插入末尾)
18
+ */
19
+ cursorBack?: number;
20
+ /**
21
+ * 接受时从光标锚位回退替换的长度(工单 12;缺省 = partial 长度):桶候选携带
22
+ * 整段 expr 长度——调用插入重写「前缀 + 部分段」全量
23
+ */
24
+ replaceLen?: number;
25
+ /**
26
+ * 前缀过滤键(工单 12;缺省 = segment):桶候选 = 字段键名——与同键路径候选
27
+ * 一起按 partial 过滤(segment 是整段调用文本,直接过滤永不匹配)
28
+ */
29
+ filterKey?: string;
30
+ /**
31
+ * 显式分桶判别(resource-expression-single-ref 工单 03/05,ADR 0020):'path' =
32
+ * 字段/结构头路径候选,'function' = 函数名建议 + 形状分桶调用插入——资源面
33
+ * 选择器(ExpressionPathPicker)按 `kind === 'path'` 过滤函数桶,不用
34
+ * filterKey/含 ( 启发式(工单 12 内部约定不作契约)。缺省 = 两桶之外的
35
+ * 建议面(date 格式快捷项等);镜像内核 ExpressionCandidate 同名字段
36
+ * (结构兼容面),文本浮层零感知。
37
+ */
38
+ kind?: 'path' | 'function';
14
39
  }
15
40
  /**
16
- * 候选源一次求值结果(工单 02 partial/candidates 契约 + 工单 10 open 信号扩展):
17
- * 浮层据此开合与渲染,候选空与否和 open 信号无关(D10 语义)。
41
+ * 候选源一次求值结果(工单 02 partial/candidates 契约):浮层据此开合与渲染。
42
+ * 开放映射信号(D10)已随契约废除移除(ADR 0020)——schema 解析边界显式拒绝后,
43
+ * 候选源只服务具体键声明。
18
44
  */
19
45
  export interface CompletionResult {
20
46
  /** 正在输入的未完整段(恒为 expr 的后缀,尾点态 = 空后缀) */
21
47
  partial: string;
22
48
  /** 全量合法候选(不按 partial 预过滤——前缀过滤归浮层) */
23
49
  candidates: readonly CompletionItem[];
24
- /**
25
- * 开放映射信号(D10):候选来源节点标 open(additionalProperties: true)时为
26
- * true,非 open 恒缺省——浮层渲染「动态字段,键由模板定义」占位提示行(不可
27
- * 接受、不进导航序,仅解释无候选的原因),信号在场时候选空也不关浮层。
28
- */
29
- open?: boolean;
30
50
  }
31
51
  /**
32
52
  * 候选源契约(纯函数注入缝):入参 = 光标所在片段表达式(trim 后,可能带单个
33
53
  * 补全尾点),出参 = 正在输入的 partial(恒为 expr 后缀,工单 02 契约)+ 全量
34
- * 合法候选(不按 partial 预过滤——前缀过滤归浮层)+ open 信号(工单 10);
35
- * null = 无补全态(语法错误/无候选),浮层不开。
54
+ * 合法候选(不按 partial 预过滤——前缀过滤归浮层);null = 无补全态(语法错误/
55
+ * 无候选),浮层不开。
36
56
  */
37
57
  export type CompletionSource = (expr: string) => CompletionResult | null;
38
58
  /** PHP trim() 默认字符集——内核 expressionScan 导出(单一事实源,勿按值复刻) */
39
59
  /**
40
- * 接受手术:把片段表达式末尾的 partial 换成候选 segment(工单 02 钉定的替换
41
- * 面——非整段重插、不包 `{{}}` 外壳)。part 是扫描产物(start/end 为码元索引,
42
- * end 独占),expr 物理终点 = 片段内容区终点回退 PHP trim 尾空白,partial 恒
43
- * 为其后缀;值与浮层状态漂移(partial 对不上)时拒绝手术返回 null,由调用方
44
- * 安全收口。
60
+ * 接受手术(工单 12 起光标锚定):把**光标锚位前**的替换域换成候选段(工单 02
61
+ * 钉定的替换面——非整段重插、不包 `{{}}` 外壳)。part 是扫描产物(start/end 为
62
+ * 码元索引,end 独占),cursor 是刷新时的光标锚位(片段内容区内);替换域 =
63
+ * [锚位 - 尾随空白 - replaceLen, 锚位 - 尾随空白),replaceLen 缺省 = partial
64
+ * 长度(路径面与旧行为全等——尾随输入位锚定)、桶候选携带整段 expr 长度(调用
65
+ * 插入重写前缀+部分段,工单 12)。默认替换保留漂移检验(锚位文本须等于 partial,
66
+ * 对不上拒绝手术返回 null 由调用方安全收口);replaceLen 面由锚位边界把守
67
+ * (expr 全量来自同一次刷新,漂移即越界拒绝)。返回光标 = 插入末端回退
68
+ * cursorBack(format 参数占位/括号内落点)。
45
69
  */
46
- export declare function applyCompletion(value: string, part: ExpressionScanFragment | ExpressionScanOpen, partial: string, segment: string): {
70
+ export declare function applyCompletion(value: string, part: ExpressionScanFragment | ExpressionScanOpen, cursor: number, partial: string, item: Pick<CompletionItem, 'segment' | 'replaceLen' | 'cursorBack'>): {
47
71
  value: string;
48
72
  cursor: number;
49
73
  } | null;
@@ -16,7 +16,7 @@ export declare function expressionFieldContext(schema: ExpressionSchemaNode | nu
16
16
  * 候选源适配(工单 04 注入缝):schema null(未注入/声明被拒降级)= null 源,
17
17
  * 浮层恒闭;否则包裹内核枚举器——ok:false(语法错误/无补全态)映射 null,
18
18
  * ok:true 原样透传(ExpressionCandidate 结构兼容 CompletionItem——description/
19
- * title D8 分离透传、展示回落归浮层;open 信号随结果面透出供工单 10 占位提示,
20
- * partial/candidates 契约见 ./completion)。
19
+ * title D8 分离透传、展示回落归浮层;partial/candidates 契约见 ./completion)。
20
+ * 开放映射 open 信号透传已随契约废除移除(ADR 0020)。
21
21
  */
22
22
  export declare function expressionCompletionSource(schema: ExpressionSchemaNode | null, context: ExpressionFieldContext): CompletionSource | null;
@@ -10,7 +10,13 @@
10
10
  * 02:复用同门 Popup/Anchor/applyCompletion,触发语义分轨——裸路径输入即弹)、
11
11
  * rowsPath 候选源组装 rowsPathCompletionSource(工单 03/04:「行数组」文案置入
12
12
  * 的公共合流点)与 schema 声明切片桥 useDataSourceSchema(工单 04 自 status-bar
13
- * 收口——状态栏读数段与建表表单两域共用)。
13
+ * 收口——状态栏读数段与建表表单两域共用)与字体清单注入缝 fontPicker(
14
+ * edit-overlay-toolbar 工单 04 自属性面板域迁入——面板字体控件与编辑浮层工具条
15
+ * 字体段两域共用)与字段权威写 mergeKey 约定 mergeKey(面板与工具条双入口同键
16
+ * 合并的格式单点)与资源表达式级联路径选择器 ExpressionPathPicker + `{{}}` 拆装
17
+ * 纯函数 resourceTemplate + 弹层视口定位纯函数 popupPosition(resource-expression-
18
+ * single-ref 工单 05/06:资源内容字段的作者面 = 纯菜单点选,垂直翻转/钳制定位
19
+ * 两浮层与选择器菜单共用)。
14
20
  * 这是唯一允许被各域引用的层——域间横向 import 被依赖红线
15
21
  * (editor-vue-*-isolation)禁止,跨域消费一律收口到这里。
16
22
  */
@@ -20,12 +26,17 @@ export { useDropdownMenu, type DropdownMenuController, type DropdownMenuEntry, t
20
26
  export { ARM_LAYER_CREATE_HINT, detectShortcutPlatform, shortcutActionLabel, } from './shortcutsHelp';
21
27
  export { isEditableEventTarget } from './editableTarget';
22
28
  export { default as ExpressionCompletionPopup } from './ExpressionCompletionPopup';
29
+ export { default as ExpressionPathPicker } from './ExpressionPathPicker';
23
30
  export { applyCompletion, type CompletionItem, type CompletionResult, type CompletionSource } from './completion';
24
31
  export { measureCursorAnchor, type CompletionAnchor } from './completionAnchor';
25
32
  export { expressionCompletionSource, expressionFieldContext, type ExpressionFieldContext, } from './expressionContext';
26
33
  export { useExpressionCompletion, type ExpressionCompletionState } from './useExpressionCompletion';
34
+ export { expressionDiagnosticHint, useExpressionDiagnostics } from './useExpressionDiagnostics';
27
35
  export { usePathCompletion } from './usePathCompletion';
28
36
  export { rowsPathCompletionSource } from './rowsPathCompletionSource';
37
+ export { attachResourceTemplate, stripResourceTemplate } from './resourceTemplate';
38
+ export { pathPickerEntries, type PathPickerEntry } from './pathPickerModel';
39
+ export { placePopupInViewport, POPUP_GAP, POPUP_MARGIN, type PopupAnchorBox, type PopupViewportPosition, } from './popupPosition';
29
40
  export { useHistory, type HistoryAvailability } from './useHistory';
30
41
  export { useSelection } from './useSelection';
31
42
  export { useDataSourceSchema } from './useDataSourceSchema';
@@ -33,5 +44,7 @@ export { useShortcuts } from './useShortcuts';
33
44
  export { useShortcutsHelp, type ShortcutsHelp } from './useShortcutsHelp';
34
45
  export { useTransientFeedback, type TransientFeedback } from './useTransientFeedback';
35
46
  export { uploadFileFromDom } from './uploadFile';
47
+ export { FONT_PICKER_KEY, injectFontPicker, type FontPickerContext } from './fontPicker';
48
+ export { selectionFieldMergeKey } from './mergeKey';
36
49
  export { useViewport } from './useViewport';
37
50
  export { default as PanelIcon } from './PanelIcon';
@@ -0,0 +1,2 @@
1
+ import { LayerPath } from '@hankchen/canvas-editor';
2
+ export declare function selectionFieldMergeKey(path: LayerPath, key: readonly string[]): string;
@@ -0,0 +1,11 @@
1
+ import { CompletionItem, CompletionSource } from './completion';
2
+ /** 一条菜单条目:完整点路径 + 候选元信息 + 可下钻判定(drillable = 点选进入下一层,否则点选提交) */
3
+ export interface PathPickerEntry {
4
+ /** 提交/下钻的完整点路径(缺 path 的候选不构成条目——选择器的 v-model 面就是它) */
5
+ readonly path: string;
6
+ /** 候选元信息(segment/type/description/title,展示回落归组件) */
7
+ readonly item: CompletionItem;
8
+ readonly drillable: boolean;
9
+ }
10
+ /** 取一层候选(kind 过滤后);候选源 null 态(语法错/降级)= 空层 */
11
+ export declare function pathPickerEntries(source: CompletionSource, prefix: string): readonly PathPickerEntry[];
@@ -0,0 +1,41 @@
1
+ /**
2
+ * 弹层视口定位纯函数(resource-expression-single-ref 工单 05/06,ADR 0020 伴生
3
+ * 修复):表达式补全浮层与路径选择器菜单共用的落点决策——
4
+ * - 垂直:锚点下方优先 → 下方放不下向上翻转 → 翻转仍不足钳制视口内(下缘贴
5
+ * viewport − margin;浮层比视口还高钉在上 margin);
6
+ * - 横向:右缘收口不越 viewport − margin,左缘同理(浮层比视口还宽钉左)。
7
+ *
8
+ * 实测缺陷(工单 06 背景,example 开发轨 2026-10-14):浮层恒锚光标下方无翻转
9
+ * 钳制,资源字段居面板底部——候选大半截出屏鼠标不可点。两控件(useExpression
10
+ * Completion / usePathCompletion)的私有 clampToViewport 与本函数合并为单点,
11
+ * 路径选择器菜单(工单 05)同函数消费。
12
+ *
13
+ * 高/宽度量不到(0——首帧未渲染/降级):按「放得下/原锚放」处理,不误翻转不
14
+ * 误收口(首开路径由调用方延一拍补量)。纯几何、零 DOM、Node 可测。
15
+ */
16
+ /** 锚点盒(视口坐标;取 CompletionAnchor 同形的 top/bottom/left 三轴) */
17
+ export interface PopupAnchorBox {
18
+ top: number;
19
+ bottom: number;
20
+ left: number;
21
+ }
22
+ /** 弹层落点(视口坐标,fixed 定位直用) */
23
+ export interface PopupViewportPosition {
24
+ top: number;
25
+ left: number;
26
+ }
27
+ /** 锚缘与弹层的间隙(px;既有两控件同语常量收口至此) */
28
+ export declare const POPUP_GAP = 4;
29
+ /** 弹层与视口边缘的最小留白(px) */
30
+ export declare const POPUP_MARGIN = 4;
31
+ export declare function placePopupInViewport(options: {
32
+ anchor: PopupAnchorBox;
33
+ popupWidth: number;
34
+ popupHeight: number;
35
+ viewportWidth: number;
36
+ viewportHeight: number;
37
+ /** 锚缘间隙;缺省 POPUP_GAP */
38
+ gap?: number;
39
+ /** 视口留白;缺省 POPUP_MARGIN */
40
+ margin?: number;
41
+ }): PopupViewportPosition;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 拆壳:合法单值引用 → 裸点路径(片段内空白按 phpTrim 收整);违例/拆不动 →
3
+ * null(原文只读回显面)。
4
+ */
5
+ export declare function stripResourceTemplate(template: string): string | null;
6
+ /** 包壳:裸点路径 → 规范形态 `{{path}}`(提交面模板原文) */
7
+ export declare function attachResourceTemplate(path: string): string;
@@ -8,13 +8,6 @@ export interface ExpressionCompletionState {
8
8
  /** 视口坐标(fixed 定位;光标下缘 + 间隙) */
9
9
  top: number;
10
10
  left: number;
11
- /**
12
- * 开放映射占位提示(D10,工单 10):候选来源节点标 open 时为 true——浮层渲染
13
- * 「动态字段,键由模板定义」占位提示行(不可接受、不进导航序,仅解释无候选的
14
- * 原因)。信号与候选空否无关(宿主合入 properties 的键照常枚举),true 时提示
15
- * 行恒在场;items 导航/接受只认候选条目,提示行不在其中。
16
- */
17
- openMapping: boolean;
18
11
  }
19
12
  export declare function useExpressionCompletion(options: {
20
13
  /** 宿主输入元素(textarea/input),模板 ref 传入 */
@@ -0,0 +1,15 @@
1
+ import { ComputedRef, MaybeRefOrGetter, Ref } from 'vue';
2
+ import { ExpressionFragmentDiagnostic } from '@hankchen/canvas-editor';
3
+ /** 诊断汇总文案:多处取首条 kind 带计数;无诊断返回空串 */
4
+ export declare function expressionDiagnosticHint(diagnostics: readonly ExpressionFragmentDiagnostic[]): string;
5
+ export declare function useExpressionDiagnostics(options: {
6
+ /** 宿主输入元素(textarea/input),模板 ref 传入 */
7
+ target: Ref<HTMLTextAreaElement | HTMLInputElement | null>;
8
+ /** 表达式态开关(静态态恒空);响应式 getter/ ref 均可 */
9
+ enabled: MaybeRefOrGetter<boolean>;
10
+ }): {
11
+ /** 全部片段结构错诊断(模板坐标);关闭态恒空 */
12
+ diagnostics: ComputedRef<readonly ExpressionFragmentDiagnostic[]>;
13
+ /** 程序化写值后的显式同步(初值注入等不走 input 事件的路径调用) */
14
+ refresh: () => void;
15
+ };
package/dist/shared.js CHANGED
@@ -1,9 +1,10 @@
1
- import { i as e, n as t, r as n, t as r } from "./completionAnchor-CDR5YZhu.js";
2
- import { c as i, i as a, n as o, s, t as c } from "./useTransientFeedback-CTjmMmFF.js";
3
- import { i as l, n as u, r as d, t as f } from "./uploadFile-BOfv8aLG.js";
4
- import { n as p, t as m } from "./usePathCompletion-uJ5c_gQ0.js";
5
- import { n as h, t as g } from "./useViewport-D9Ipevzs.js";
6
- import { n as _, r as v, t as y } from "./useDataSourceSchema-CGbldbUj.js";
7
- import { t as b } from "./useShortcutsHelp-CsyarO2P.js";
8
- import { n as x, r as S, t as C } from "./shared-wuupBoZg.js";
9
- export { a as ARM_LAYER_CREATE_HINT, _ as DropdownMenu, n as ExpressionCompletionPopup, S as HelpDialog, e as PanelIcon, t as applyCompletion, s as detectShortcutPlatform, d as expressionCompletionSource, l as expressionFieldContext, o as isEditableEventTarget, r as measureCursorAnchor, p as rowsPathCompletionSource, i as shortcutActionLabel, f as uploadFileFromDom, y as useDataSourceSchema, v as useDropdownMenu, u as useExpressionCompletion, x as useHistory, m as usePathCompletion, h as useSelection, C as useShortcuts, b as useShortcutsHelp, c as useTransientFeedback, g as useViewport };
1
+ import { a as e, i as t, n, o as r, r as i, s as a, t as o } from "./popupPosition-Ck5Gsvjy.js";
2
+ import { c as s, i as c, n as l, s as u, t as d } from "./useTransientFeedback-CTjmMmFF.js";
3
+ import { a as f, c as p, i as m, l as h, n as g, o as _, r as v, s as y, t as b } from "./uploadFile-D1RY-ji0.js";
4
+ import { i as x, n as S, r as C, t as w } from "./resourceTemplate-cdcQtp9a.js";
5
+ import { n as T, t as E } from "./usePathCompletion-Do17CQ6l.js";
6
+ import { n as D, t as O } from "./useViewport-D9Ipevzs.js";
7
+ import { n as k, r as A, t as j } from "./useDataSourceSchema-CGbldbUj.js";
8
+ import { t as M } from "./useShortcutsHelp-CsyarO2P.js";
9
+ import { n as N, r as P, t as F } from "./shared-wuupBoZg.js";
10
+ export { c as ARM_LAYER_CREATE_HINT, k as DropdownMenu, r as ExpressionCompletionPopup, C as ExpressionPathPicker, v as FONT_PICKER_KEY, P as HelpDialog, o as POPUP_GAP, n as POPUP_MARGIN, a as PanelIcon, e as applyCompletion, w as attachResourceTemplate, u as detectShortcutPlatform, p as expressionCompletionSource, f as expressionDiagnosticHint, h as expressionFieldContext, m as injectFontPicker, l as isEditableEventTarget, t as measureCursorAnchor, x as pathPickerEntries, i as placePopupInViewport, T as rowsPathCompletionSource, g as selectionFieldMergeKey, s as shortcutActionLabel, S as stripResourceTemplate, b as uploadFileFromDom, j as useDataSourceSchema, A as useDropdownMenu, y as useExpressionCompletion, _ as useExpressionDiagnostics, N as useHistory, E as usePathCompletion, D as useSelection, F as useShortcuts, M as useShortcutsHelp, d as useTransientFeedback, O as useViewport };
package/dist/style.css CHANGED
@@ -1,2 +1,2 @@
1
- .cn-align-float[data-v-9a761d37]{--cn-fg:#e6edf7;--cn-muted:#7c8ca5;--cn-field-line:#2a3a57;--cn-accent:#38bdf8;--cn-hover:#16223a;border:1px solid var(--cn-field-line);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);color:var(--cn-fg);-webkit-user-select:none;user-select:none;background:#0b1220eb;border-radius:9px;align-items:center;gap:4px;padding:5px 8px;display:inline-flex;box-shadow:0 10px 26px #00000073}.cn-align-float__group[data-v-9a761d37]{align-items:center;gap:2px;display:flex}.cn-align-float__divider[data-v-9a761d37]{background:var(--cn-field-line);width:1px;height:16px}.cn-align-float__key[data-v-9a761d37]{width:24px;height:24px;color:var(--cn-fg);cursor:pointer;background:0 0;border:none;border-radius:6px;place-content:center;padding:0;display:grid}.cn-align-float__key[data-v-9a761d37]:hover:not(:disabled){background:var(--cn-hover);color:var(--cn-accent)}.cn-align-float--disabled[data-v-9a761d37]{color:var(--cn-muted)}.cn-align-float--disabled .cn-align-float__key[data-v-9a761d37]{color:var(--cn-muted);cursor:not-allowed}.cn-context-menu[data-v-e554ef38]{--cn-bg:#0b1220;--cn-bg-elevated:#101a2e;--cn-fg:#e6edf7;--cn-muted:#7c8ca5;--cn-line:#1e2a40;--cn-field:#94a3b812;--cn-accent:#38bdf8;--cn-accent-soft:#38bdf81f;--cn-danger:#f87171;z-index:30;background:var(--cn-bg-elevated);border:1px solid var(--cn-line);border-radius:10px;flex-direction:column;gap:2px;min-width:120px;padding:4px;display:flex;position:absolute;box-shadow:0 12px 32px #0206178c}.cn-context-menu__item[data-v-e554ef38]{color:var(--cn-fg);text-align:left;cursor:pointer;background:0 0;border:0;border-radius:6px;padding:6px 12px;font-size:13px;line-height:1.4}.cn-context-menu__item[data-v-e554ef38]:hover:not(:disabled){background:var(--cn-accent-soft);color:var(--cn-accent)}.cn-context-menu__item[data-v-e554ef38]:disabled{color:var(--cn-muted);cursor:not-allowed}.cn-context-menu__item--delete[data-v-e554ef38]:hover:not(:disabled){color:var(--cn-danger);background:#f871711f}.cn-context-menu__form[data-v-e554ef38]{flex-direction:column;gap:6px;min-width:180px;padding:4px;display:flex}.cn-context-menu__hint[data-v-e554ef38]{color:var(--cn-muted);margin:0;padding:0 4px;font-size:11px;line-height:1.5}.cn-context-menu__hint--error[data-v-e554ef38]{color:var(--cn-danger)}.cn-context-menu__input[data-v-e554ef38]{border:1px solid var(--cn-line);background:var(--cn-field);color:var(--cn-fg);border-radius:6px;outline:none;padding:5px 8px;font-size:12px;line-height:1.4}.cn-context-menu__input[data-v-e554ef38]:focus{border-color:var(--cn-accent)}.cn-context-menu__input--invalid[data-v-e554ef38]{border-color:var(--cn-danger)}.cn-context-menu__form-actions[data-v-e554ef38]{gap:4px;display:flex}.cn-context-menu__form-actions .cn-context-menu__item[data-v-e554ef38]{text-align:center;flex:1}.cn-context-menu__item--confirm[data-v-e554ef38]{background:var(--cn-accent-soft);color:var(--cn-accent)}.cn-find-bar[data-v-71f0a3ae]{--cn-fg:#e6edf7;--cn-muted:#7c8ca5;--cn-field-line:#2a3a57;--cn-accent:#38bdf8;--cn-hover:#16223a;z-index:25;border:1px solid var(--cn-field-line);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);color:var(--cn-fg);-webkit-user-select:none;user-select:none;background:#0b1220eb;border-radius:9px;align-items:center;gap:6px;padding:6px 8px;display:inline-flex;position:absolute;top:12px;left:50%;transform:translate(-50%);box-shadow:0 10px 26px #00000073}.cn-find-bar__input[data-v-71f0a3ae]{border:1px solid var(--cn-field-line);width:132px;color:var(--cn-fg);background:#0b1220e6;border-radius:6px;padding:4px 8px;font-size:13px;line-height:1.4}.cn-find-bar__input[data-v-71f0a3ae]::placeholder{color:var(--cn-muted)}.cn-find-bar__input[data-v-71f0a3ae]:focus{border-color:var(--cn-accent);outline:none}.cn-find-bar__count[data-v-71f0a3ae]{text-align:center;min-width:56px;color:var(--cn-muted);white-space:nowrap;font-size:12px}.cn-find-bar__note[data-v-71f0a3ae]{color:var(--cn-accent);white-space:nowrap;font-size:12px}.cn-find-bar__key[data-v-71f0a3ae]{width:24px;height:24px;color:var(--cn-fg);cursor:pointer;background:0 0;border:none;border-radius:6px;place-content:center;padding:0;display:grid}.cn-find-bar__action[data-v-71f0a3ae]{white-space:nowrap;color:var(--cn-fg);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:4px 10px;font-size:12px;line-height:1.4}.cn-find-bar__key[data-v-71f0a3ae]:hover:not(:disabled),.cn-find-bar__action[data-v-71f0a3ae]:hover:not(:disabled){background:var(--cn-hover);color:var(--cn-accent)}.cn-find-bar__key[data-v-71f0a3ae]:disabled,.cn-find-bar__action[data-v-71f0a3ae]:disabled{color:var(--cn-muted);cursor:not-allowed}.cn-content-pill[data-v-d99d2c13]{--cn-bg-elevated:#101a2e;--cn-fg:#e6edf7;--cn-muted:#7c8ca5;--cn-line:#1e2a40;--cn-accent:#38bdf8;--cn-accent-soft:#38bdf81f;z-index:30;background:var(--cn-bg-elevated);border:1px solid var(--cn-line);pointer-events:auto;-webkit-user-select:none;user-select:none;border-radius:7px;align-items:stretch;display:inline-flex;position:absolute;top:0;left:0;overflow:hidden;box-shadow:0 8px 20px #02061773}.cn-content-pill__item[data-v-d99d2c13]{color:var(--cn-muted);white-space:nowrap;cursor:pointer;background:0 0;border:0;padding:3px 10px;font-size:11px;line-height:1.4}.cn-content-pill__item+.cn-content-pill__item[data-v-d99d2c13]{border-left:1px solid var(--cn-line)}.cn-content-pill__item[data-v-d99d2c13]:hover{color:var(--cn-fg)}.cn-content-pill__item--active[data-v-d99d2c13]{background:var(--cn-accent-soft);color:var(--cn-accent);cursor:default}.cn-text-overlay[data-v-bc9281f8]{pointer-events:none;position:absolute;inset:0}.cn-text-overlay__textarea[data-v-bc9281f8]{resize:none;-webkit-user-select:text;user-select:text;pointer-events:auto;letter-spacing:normal;text-transform:none;background:0 0;border:0;outline:none;margin:0;padding:0;font-style:normal;font-weight:400;position:absolute;top:0;left:0;overflow:hidden}.cn-surface[data-v-f2b89db2]{touch-action:none;-webkit-user-select:none;user-select:none;position:relative;overflow:hidden}.cn-surface__canvas[data-v-f2b89db2]{width:100%;height:100%;display:block;position:absolute;inset:0}.cn-surface__canvas--overlay[data-v-f2b89db2]{pointer-events:none}.cn-surface--pannable[data-v-f2b89db2]{cursor:grab}.cn-surface--panning[data-v-f2b89db2]{cursor:grabbing}.cn-surface__drop-hint[data-v-f2b89db2]{z-index:20;pointer-events:none;background:#38bdf814;border:2px dashed #38bdf899;border-radius:12px;justify-content:center;align-items:center;display:flex;position:absolute;inset:8px}.cn-surface__drop-hint-text[data-v-f2b89db2]{color:#38bdf8;background:#0b1220;border-radius:8px;padding:6px 14px;font-size:13px;line-height:1.4}.cn-guides[data-v-61872121]{--cn-guide-line:#38bdf8;--cn-guide-glow:#38bdf866;--cn-guide-will-delete:#f87171;--cn-guide-will-delete-glow:#f8717173;--cn-snap-line:#fb7185;--cn-snap-glow:#fb718573;--cn-snap-dash:4px;--cn-guide-hit:9px;--cn-guide-clip-radius:12px;border-radius:var(--cn-guide-clip-radius);pointer-events:none;-webkit-user-select:none;user-select:none;position:absolute;inset:0;overflow:hidden}.cn-guides__guide[data-v-61872121]{pointer-events:auto;touch-action:none;position:absolute}.cn-guides__guide--vertical[data-v-61872121]{width:var(--cn-guide-hit);cursor:ew-resize;transform:translate(-50%)}.cn-guides__guide--horizontal[data-v-61872121]{height:var(--cn-guide-hit);cursor:ns-resize;transform:translateY(-50%)}.cn-guides__guide--vertical[data-v-61872121]:after,.cn-guides__guide--horizontal[data-v-61872121]:after{content:"";background:var(--cn-guide-line);box-shadow:0 0 6px var(--cn-guide-glow);position:absolute}.cn-guides__guide--vertical[data-v-61872121]:after{width:1px;top:0;bottom:0;left:calc(50% - .5px)}.cn-guides__guide--horizontal[data-v-61872121]:after{height:1px;top:calc(50% - .5px);left:0;right:0}.cn-guides__guide--preview[data-v-61872121]{pointer-events:none}.cn-guides__guide--preview[data-v-61872121]:after{opacity:.85}.cn-guides__guide--grabbed[data-v-61872121]:after{opacity:.75}.cn-guides__guide--will-delete[data-v-61872121]:after{background:var(--cn-guide-will-delete);box-shadow:0 0 6px var(--cn-guide-will-delete-glow)}.cn-guides__snap[data-v-61872121]{pointer-events:none;filter:drop-shadow(0 0 3px var(--cn-snap-glow));position:absolute}.cn-guides__snap--vertical[data-v-61872121]{background-image:repeating-linear-gradient(to bottom, var(--cn-snap-line) 0 var(--cn-snap-dash), transparent var(--cn-snap-dash) calc(var(--cn-snap-dash) * 2));width:1px}.cn-guides__snap--horizontal[data-v-61872121]{background-image:repeating-linear-gradient(to right, var(--cn-snap-line) 0 var(--cn-snap-dash), transparent var(--cn-snap-dash) calc(var(--cn-snap-dash) * 2));height:1px}.cn-ruler[data-v-5bd7248c]{--cn-ruler-thickness:20px;--cn-ruler-bg:#0b1220eb;--cn-ruler-line:#1e2a40;--cn-ruler-tick:#7c8ca5;--cn-ruler-label:#aab6c8;--cn-ruler-font:"SF Mono", Menlo, Consolas, monospace;pointer-events:none;-webkit-user-select:none;user-select:none;width:100%;height:100%;font-family:var(--cn-ruler-font);position:relative}.cn-ruler__bar[data-v-5bd7248c]{pointer-events:auto;touch-action:none;background:var(--cn-ruler-bg);position:absolute;top:0;left:0;overflow:hidden}.cn-ruler__bar--top[data-v-5bd7248c]{height:var(--cn-ruler-thickness);border-bottom:1px solid var(--cn-ruler-line);right:0}.cn-ruler__bar--left[data-v-5bd7248c]{width:var(--cn-ruler-thickness);border-right:1px solid var(--cn-ruler-line);bottom:0}.cn-ruler__corner[data-v-5bd7248c]{z-index:1;width:var(--cn-ruler-thickness);height:var(--cn-ruler-thickness);background:var(--cn-ruler-bg);border-right:1px solid var(--cn-ruler-line);border-bottom:1px solid var(--cn-ruler-line);position:absolute;top:0;left:0}.cn-ruler__tick[data-v-5bd7248c]{background:var(--cn-ruler-tick);position:absolute}.cn-ruler__bar--top .cn-ruler__tick[data-v-5bd7248c]{width:1px;height:4px;bottom:0}.cn-ruler__bar--top .cn-ruler__tick--major[data-v-5bd7248c]{height:8px}.cn-ruler__bar--left .cn-ruler__tick[data-v-5bd7248c]{width:4px;height:1px;right:0}.cn-ruler__bar--left .cn-ruler__tick--major[data-v-5bd7248c]{width:8px}.cn-ruler__label[data-v-5bd7248c]{white-space:nowrap;color:var(--cn-ruler-label);font-size:9px;line-height:1;position:absolute}.cn-ruler__bar--top .cn-ruler__label[data-v-5bd7248c]{bottom:9px;left:3px}.cn-ruler__bar--left .cn-ruler__label[data-v-5bd7248c]{writing-mode:vertical-rl;top:2px;right:10px}.cn-dropdown[data-v-ec8a19c1]{--cn-fg:#e6edf7;--cn-fg-2:#c3cddd;--cn-muted:#7c8ca5;--cn-line:#1e2a40;--cn-bg-elevated:#101a2e;--cn-hover:#16223a;--cn-accent:#38bdf8;--cn-accent-soft:#38bdf81f;display:inline-flex;position:relative}.cn-dropdown__trigger[data-v-ec8a19c1]{white-space:nowrap;color:var(--cn-fg-2);cursor:pointer;background:0 0;border:none;border-radius:6px;align-items:center;gap:3px;padding:5px 9px;font-size:13px;display:inline-flex}.cn-dropdown__trigger[data-v-ec8a19c1]:hover{background:var(--cn-hover);color:var(--cn-fg)}.cn-dropdown__caret[data-v-ec8a19c1]{color:var(--cn-muted);font-size:10px;line-height:1}.cn-dropdown__menu[data-v-ec8a19c1]{z-index:40;background:var(--cn-bg-elevated);border:1px solid var(--cn-line);border-radius:10px;flex-direction:column;gap:2px;min-width:148px;padding:4px;display:flex;position:fixed;box-shadow:0 12px 32px #0206178c}.cn-dropdown__item[data-v-ec8a19c1]{color:var(--cn-fg);text-align:left;white-space:nowrap;cursor:pointer;background:0 0;border:0;border-radius:6px;justify-content:space-between;align-items:center;gap:14px;padding:6px 12px;font-size:12px;line-height:1.4;display:flex}.cn-dropdown__item[data-v-ec8a19c1]:hover:not(:disabled){background:var(--cn-accent-soft);color:var(--cn-accent)}.cn-dropdown__item[data-v-ec8a19c1]:disabled{color:var(--cn-muted);cursor:not-allowed}.cn-dropdown__shortcut[data-v-ec8a19c1]{color:var(--cn-muted);font-variant-numeric:tabular-nums;flex:none;font-size:11px;line-height:1.4}.cn-dropdown__separator[data-v-ec8a19c1]{background:var(--cn-line);flex:none;height:1px;margin:3px 6px}.cn-statusbar[data-v-040479fb]{--cn-bg:#0b1220;--cn-bg-elevated:#101a2e;--cn-fg:#e6edf7;--cn-muted:#7c8ca5;--cn-line:#1e2a40;--cn-accent:#38bdf8;background-color:var(--cn-bg);color:var(--cn-muted);font-variant-numeric:tabular-nums;-webkit-user-select:none;user-select:none;align-items:center;gap:10px;padding:6px 12px;font-size:12px;line-height:1.4;display:flex}.cn-statusbar__segment[data-v-040479fb]{white-space:nowrap}.cn-statusbar__zoom-trigger[data-v-040479fb]{font:inherit;color:inherit;font-variant-numeric:tabular-nums;cursor:pointer;background:0 0;border:0;padding:0}.cn-statusbar__zoom-trigger[data-v-040479fb]:hover{color:var(--cn-accent)}.cn-statusbar__help[data-v-040479fb]{font:inherit;color:inherit;cursor:pointer;background:0 0;border:0;padding:0}.cn-statusbar__help[data-v-040479fb]:hover,.cn-statusbar__segment--pending[data-v-040479fb]{color:var(--cn-accent)}.cn-statusbar__divider[data-v-040479fb]{background:var(--cn-line);width:1px;height:12px}.cn-help[data-v-486e5be3]{--cn-bg:#0b1220;--cn-bg-elevated:#101a2e;--cn-fg:#e6edf7;--cn-muted:#7c8ca5;--cn-line:#1e2a40;--cn-accent:#38bdf8;z-index:50;background:#0206178c;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}.cn-help__panel[data-v-486e5be3]{background:var(--cn-bg-elevated);border:1px solid var(--cn-line);border-radius:12px;flex-direction:column;min-width:320px;max-width:480px;max-height:100%;display:flex;box-shadow:0 24px 64px #02061799}.cn-help__header[data-v-486e5be3]{border-bottom:1px solid var(--cn-line);justify-content:space-between;align-items:center;padding:12px 16px;display:flex}.cn-help__title[data-v-486e5be3]{color:var(--cn-fg);margin:0;font-size:14px;font-weight:600;line-height:1.4}.cn-help__close[data-v-486e5be3]{color:var(--cn-muted);cursor:pointer;background:0 0;border:0;border-radius:6px;padding:2px 8px;font-size:13px;line-height:1.4}.cn-help__close[data-v-486e5be3]:hover{color:var(--cn-fg);background:#94a3b812}.cn-help__body[data-v-486e5be3]{padding:8px 16px 16px;overflow-y:auto}.cn-help__section[data-v-486e5be3]{margin-top:12px}.cn-help__section[data-v-486e5be3]:first-child{margin-top:8px}.cn-help__section-title[data-v-486e5be3]{color:var(--cn-accent);margin:0 0 4px;font-size:11px;font-weight:600;line-height:1.4}.cn-help__list[data-v-486e5be3]{margin:0;padding:0;list-style:none}.cn-help__row[data-v-486e5be3]{justify-content:space-between;align-items:baseline;gap:16px;padding:3px 0;display:flex}.cn-help__label[data-v-486e5be3]{color:var(--cn-fg);font-size:12px;line-height:1.5}.cn-help__keys[data-v-486e5be3]{border:1px solid var(--cn-line);color:var(--cn-muted);white-space:nowrap;background:#94a3b812;border-radius:5px;flex-shrink:0;padding:1px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.4}
1
+ .cn-align-float[data-v-9a761d37]{--cn-fg:#e6edf7;--cn-muted:#7c8ca5;--cn-field-line:#2a3a57;--cn-accent:#38bdf8;--cn-hover:#16223a;border:1px solid var(--cn-field-line);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);color:var(--cn-fg);-webkit-user-select:none;user-select:none;background:#0b1220eb;border-radius:9px;align-items:center;gap:4px;padding:5px 8px;display:inline-flex;box-shadow:0 10px 26px #00000073}.cn-align-float__group[data-v-9a761d37]{align-items:center;gap:2px;display:flex}.cn-align-float__divider[data-v-9a761d37]{background:var(--cn-field-line);width:1px;height:16px}.cn-align-float__key[data-v-9a761d37]{width:24px;height:24px;color:var(--cn-fg);cursor:pointer;background:0 0;border:none;border-radius:6px;place-content:center;padding:0;display:grid}.cn-align-float__key[data-v-9a761d37]:hover:not(:disabled){background:var(--cn-hover);color:var(--cn-accent)}.cn-align-float--disabled[data-v-9a761d37]{color:var(--cn-muted)}.cn-align-float--disabled .cn-align-float__key[data-v-9a761d37]{color:var(--cn-muted);cursor:not-allowed}.cn-context-menu[data-v-e554ef38]{--cn-bg:#0b1220;--cn-bg-elevated:#101a2e;--cn-fg:#e6edf7;--cn-muted:#7c8ca5;--cn-line:#1e2a40;--cn-field:#94a3b812;--cn-accent:#38bdf8;--cn-accent-soft:#38bdf81f;--cn-danger:#f87171;z-index:30;background:var(--cn-bg-elevated);border:1px solid var(--cn-line);border-radius:10px;flex-direction:column;gap:2px;min-width:120px;padding:4px;display:flex;position:absolute;box-shadow:0 12px 32px #0206178c}.cn-context-menu__item[data-v-e554ef38]{color:var(--cn-fg);text-align:left;cursor:pointer;background:0 0;border:0;border-radius:6px;padding:6px 12px;font-size:13px;line-height:1.4}.cn-context-menu__item[data-v-e554ef38]:hover:not(:disabled){background:var(--cn-accent-soft);color:var(--cn-accent)}.cn-context-menu__item[data-v-e554ef38]:disabled{color:var(--cn-muted);cursor:not-allowed}.cn-context-menu__item--delete[data-v-e554ef38]:hover:not(:disabled){color:var(--cn-danger);background:#f871711f}.cn-context-menu__form[data-v-e554ef38]{flex-direction:column;gap:6px;min-width:180px;padding:4px;display:flex}.cn-context-menu__hint[data-v-e554ef38]{color:var(--cn-muted);margin:0;padding:0 4px;font-size:11px;line-height:1.5}.cn-context-menu__hint--error[data-v-e554ef38]{color:var(--cn-danger)}.cn-context-menu__input[data-v-e554ef38]{border:1px solid var(--cn-line);background:var(--cn-field);color:var(--cn-fg);border-radius:6px;outline:none;padding:5px 8px;font-size:12px;line-height:1.4}.cn-context-menu__input[data-v-e554ef38]:focus{border-color:var(--cn-accent)}.cn-context-menu__input--invalid[data-v-e554ef38]{border-color:var(--cn-danger)}.cn-context-menu__form-actions[data-v-e554ef38]{gap:4px;display:flex}.cn-context-menu__form-actions .cn-context-menu__item[data-v-e554ef38]{text-align:center;flex:1}.cn-context-menu__item--confirm[data-v-e554ef38]{background:var(--cn-accent-soft);color:var(--cn-accent)}.cn-find-bar[data-v-71f0a3ae]{--cn-fg:#e6edf7;--cn-muted:#7c8ca5;--cn-field-line:#2a3a57;--cn-accent:#38bdf8;--cn-hover:#16223a;z-index:25;border:1px solid var(--cn-field-line);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);color:var(--cn-fg);-webkit-user-select:none;user-select:none;background:#0b1220eb;border-radius:9px;align-items:center;gap:6px;padding:6px 8px;display:inline-flex;position:absolute;top:12px;left:50%;transform:translate(-50%);box-shadow:0 10px 26px #00000073}.cn-find-bar__input[data-v-71f0a3ae]{border:1px solid var(--cn-field-line);width:132px;color:var(--cn-fg);background:#0b1220e6;border-radius:6px;padding:4px 8px;font-size:13px;line-height:1.4}.cn-find-bar__input[data-v-71f0a3ae]::placeholder{color:var(--cn-muted)}.cn-find-bar__input[data-v-71f0a3ae]:focus{border-color:var(--cn-accent);outline:none}.cn-find-bar__count[data-v-71f0a3ae]{text-align:center;min-width:56px;color:var(--cn-muted);white-space:nowrap;font-size:12px}.cn-find-bar__note[data-v-71f0a3ae]{color:var(--cn-accent);white-space:nowrap;font-size:12px}.cn-find-bar__key[data-v-71f0a3ae]{width:24px;height:24px;color:var(--cn-fg);cursor:pointer;background:0 0;border:none;border-radius:6px;place-content:center;padding:0;display:grid}.cn-find-bar__action[data-v-71f0a3ae]{white-space:nowrap;color:var(--cn-fg);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:4px 10px;font-size:12px;line-height:1.4}.cn-find-bar__key[data-v-71f0a3ae]:hover:not(:disabled),.cn-find-bar__action[data-v-71f0a3ae]:hover:not(:disabled){background:var(--cn-hover);color:var(--cn-accent)}.cn-find-bar__key[data-v-71f0a3ae]:disabled,.cn-find-bar__action[data-v-71f0a3ae]:disabled{color:var(--cn-muted);cursor:not-allowed}.cn-edit-toolbar[data-v-dcead501]{--cn-bg-elevated:#101a2e;--cn-fg:#e6edf7;--cn-muted:#7c8ca5;--cn-line:#1e2a40;--cn-accent:#38bdf8;--cn-accent-soft:#38bdf81f;z-index:30;background:var(--cn-bg-elevated);border:1px solid var(--cn-line);pointer-events:auto;-webkit-user-select:none;user-select:none;border-radius:8px;align-items:stretch;gap:2px;padding:3px;display:inline-flex;position:absolute;top:0;left:0;box-shadow:0 8px 20px #02061773}.cn-edit-toolbar__segment+.cn-edit-toolbar__segment[data-v-dcead501]{border-left:1px solid var(--cn-line);margin-left:2px;padding-left:3px}.cn-edit-toolbar__segment[data-v-dcead501]{border-radius:5px;align-items:center;display:inline-flex;overflow:hidden}.cn-edit-toolbar__item[data-v-dcead501]{color:var(--cn-muted);white-space:nowrap;cursor:pointer;background:0 0;border:0;padding:3px 10px;font-size:11px;line-height:1.4}.cn-edit-toolbar__item+.cn-edit-toolbar__item[data-v-dcead501]{border-left:1px solid var(--cn-line)}.cn-edit-toolbar__item[data-v-dcead501]:hover{color:var(--cn-fg)}.cn-edit-toolbar__item--active[data-v-dcead501]{background:var(--cn-accent-soft);color:var(--cn-accent);cursor:default}.cn-edit-toolbar__icon-item[data-v-dcead501]{justify-content:center;align-items:center;padding:3px 6px;display:inline-flex}.cn-edit-toolbar__font[data-v-dcead501]{max-width:110px;height:22px;color:var(--cn-fg);cursor:pointer;background:0 0;border:0;border-radius:4px;padding:0 2px;font-size:11px;line-height:1.4}.cn-edit-toolbar__font[data-v-dcead501]:focus-visible{outline:1px solid var(--cn-accent)}.cn-edit-toolbar__font option[data-v-dcead501]{background:var(--cn-bg-elevated);color:var(--cn-fg)}.cn-edit-toolbar__mini[data-v-dcead501]{color:var(--cn-muted);cursor:pointer;background:0 0;border:0;justify-content:center;align-items:center;padding:3px 4px;display:inline-flex}.cn-edit-toolbar__mini[data-v-dcead501]:hover{color:var(--cn-fg)}.cn-edit-toolbar__size[data-v-dcead501]{border:1px solid var(--cn-line);width:34px;height:20px;color:var(--cn-fg);text-align:center;appearance:textfield;background:0 0;border-radius:4px;padding:0 2px;font-size:11px;line-height:1.4}.cn-edit-toolbar__size[data-v-dcead501]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.cn-edit-toolbar__size[data-v-dcead501]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.cn-edit-toolbar__size[data-v-dcead501]:focus-visible{outline:1px solid var(--cn-accent)}.cn-edit-toolbar__color[data-v-dcead501]{border:1px solid var(--cn-line);cursor:pointer;background:0 0;border-radius:4px;width:24px;height:20px;padding:0}.cn-edit-toolbar__color[data-v-dcead501]:focus-visible{outline:1px solid var(--cn-accent)}.cn-text-overlay[data-v-9e96d660]{pointer-events:none;position:absolute;inset:0}.cn-text-overlay__textarea[data-v-9e96d660]{resize:none;-webkit-user-select:text;user-select:text;pointer-events:auto;letter-spacing:normal;text-transform:none;background:0 0;border:0;outline:none;margin:0;padding:0;font-style:normal;font-weight:400;position:absolute;top:0;left:0;overflow:hidden}.cn-text-overlay__diagnostic[data-v-9e96d660]{text-overflow:ellipsis;white-space:nowrap;color:#f87171;pointer-events:none;-webkit-user-select:none;user-select:none;background:#0b1220e0;border:1px solid #f8717166;border-radius:4px;max-width:240px;padding:2px 6px;font-size:12px;line-height:16px;position:absolute;top:0;left:0;overflow:hidden}.cn-surface[data-v-f2b89db2]{touch-action:none;-webkit-user-select:none;user-select:none;position:relative;overflow:hidden}.cn-surface__canvas[data-v-f2b89db2]{width:100%;height:100%;display:block;position:absolute;inset:0}.cn-surface__canvas--overlay[data-v-f2b89db2]{pointer-events:none}.cn-surface--pannable[data-v-f2b89db2]{cursor:grab}.cn-surface--panning[data-v-f2b89db2]{cursor:grabbing}.cn-surface__drop-hint[data-v-f2b89db2]{z-index:20;pointer-events:none;background:#38bdf814;border:2px dashed #38bdf899;border-radius:12px;justify-content:center;align-items:center;display:flex;position:absolute;inset:8px}.cn-surface__drop-hint-text[data-v-f2b89db2]{color:#38bdf8;background:#0b1220;border-radius:8px;padding:6px 14px;font-size:13px;line-height:1.4}.cn-guides[data-v-61872121]{--cn-guide-line:#38bdf8;--cn-guide-glow:#38bdf866;--cn-guide-will-delete:#f87171;--cn-guide-will-delete-glow:#f8717173;--cn-snap-line:#fb7185;--cn-snap-glow:#fb718573;--cn-snap-dash:4px;--cn-guide-hit:9px;--cn-guide-clip-radius:12px;border-radius:var(--cn-guide-clip-radius);pointer-events:none;-webkit-user-select:none;user-select:none;position:absolute;inset:0;overflow:hidden}.cn-guides__guide[data-v-61872121]{pointer-events:auto;touch-action:none;position:absolute}.cn-guides__guide--vertical[data-v-61872121]{width:var(--cn-guide-hit);cursor:ew-resize;transform:translate(-50%)}.cn-guides__guide--horizontal[data-v-61872121]{height:var(--cn-guide-hit);cursor:ns-resize;transform:translateY(-50%)}.cn-guides__guide--vertical[data-v-61872121]:after,.cn-guides__guide--horizontal[data-v-61872121]:after{content:"";background:var(--cn-guide-line);box-shadow:0 0 6px var(--cn-guide-glow);position:absolute}.cn-guides__guide--vertical[data-v-61872121]:after{width:1px;top:0;bottom:0;left:calc(50% - .5px)}.cn-guides__guide--horizontal[data-v-61872121]:after{height:1px;top:calc(50% - .5px);left:0;right:0}.cn-guides__guide--preview[data-v-61872121]{pointer-events:none}.cn-guides__guide--preview[data-v-61872121]:after{opacity:.85}.cn-guides__guide--grabbed[data-v-61872121]:after{opacity:.75}.cn-guides__guide--will-delete[data-v-61872121]:after{background:var(--cn-guide-will-delete);box-shadow:0 0 6px var(--cn-guide-will-delete-glow)}.cn-guides__snap[data-v-61872121]{pointer-events:none;filter:drop-shadow(0 0 3px var(--cn-snap-glow));position:absolute}.cn-guides__snap--vertical[data-v-61872121]{background-image:repeating-linear-gradient(to bottom, var(--cn-snap-line) 0 var(--cn-snap-dash), transparent var(--cn-snap-dash) calc(var(--cn-snap-dash) * 2));width:1px}.cn-guides__snap--horizontal[data-v-61872121]{background-image:repeating-linear-gradient(to right, var(--cn-snap-line) 0 var(--cn-snap-dash), transparent var(--cn-snap-dash) calc(var(--cn-snap-dash) * 2));height:1px}.cn-ruler[data-v-5bd7248c]{--cn-ruler-thickness:20px;--cn-ruler-bg:#0b1220eb;--cn-ruler-line:#1e2a40;--cn-ruler-tick:#7c8ca5;--cn-ruler-label:#aab6c8;--cn-ruler-font:"SF Mono", Menlo, Consolas, monospace;pointer-events:none;-webkit-user-select:none;user-select:none;width:100%;height:100%;font-family:var(--cn-ruler-font);position:relative}.cn-ruler__bar[data-v-5bd7248c]{pointer-events:auto;touch-action:none;background:var(--cn-ruler-bg);position:absolute;top:0;left:0;overflow:hidden}.cn-ruler__bar--top[data-v-5bd7248c]{height:var(--cn-ruler-thickness);border-bottom:1px solid var(--cn-ruler-line);right:0}.cn-ruler__bar--left[data-v-5bd7248c]{width:var(--cn-ruler-thickness);border-right:1px solid var(--cn-ruler-line);bottom:0}.cn-ruler__corner[data-v-5bd7248c]{z-index:1;width:var(--cn-ruler-thickness);height:var(--cn-ruler-thickness);background:var(--cn-ruler-bg);border-right:1px solid var(--cn-ruler-line);border-bottom:1px solid var(--cn-ruler-line);position:absolute;top:0;left:0}.cn-ruler__tick[data-v-5bd7248c]{background:var(--cn-ruler-tick);position:absolute}.cn-ruler__bar--top .cn-ruler__tick[data-v-5bd7248c]{width:1px;height:4px;bottom:0}.cn-ruler__bar--top .cn-ruler__tick--major[data-v-5bd7248c]{height:8px}.cn-ruler__bar--left .cn-ruler__tick[data-v-5bd7248c]{width:4px;height:1px;right:0}.cn-ruler__bar--left .cn-ruler__tick--major[data-v-5bd7248c]{width:8px}.cn-ruler__label[data-v-5bd7248c]{white-space:nowrap;color:var(--cn-ruler-label);font-size:9px;line-height:1;position:absolute}.cn-ruler__bar--top .cn-ruler__label[data-v-5bd7248c]{bottom:9px;left:3px}.cn-ruler__bar--left .cn-ruler__label[data-v-5bd7248c]{writing-mode:vertical-rl;top:2px;right:10px}.cn-dropdown[data-v-ec8a19c1]{--cn-fg:#e6edf7;--cn-fg-2:#c3cddd;--cn-muted:#7c8ca5;--cn-line:#1e2a40;--cn-bg-elevated:#101a2e;--cn-hover:#16223a;--cn-accent:#38bdf8;--cn-accent-soft:#38bdf81f;display:inline-flex;position:relative}.cn-dropdown__trigger[data-v-ec8a19c1]{white-space:nowrap;color:var(--cn-fg-2);cursor:pointer;background:0 0;border:none;border-radius:6px;align-items:center;gap:3px;padding:5px 9px;font-size:13px;display:inline-flex}.cn-dropdown__trigger[data-v-ec8a19c1]:hover{background:var(--cn-hover);color:var(--cn-fg)}.cn-dropdown__caret[data-v-ec8a19c1]{color:var(--cn-muted);font-size:10px;line-height:1}.cn-dropdown__menu[data-v-ec8a19c1]{z-index:40;background:var(--cn-bg-elevated);border:1px solid var(--cn-line);border-radius:10px;flex-direction:column;gap:2px;min-width:148px;padding:4px;display:flex;position:fixed;box-shadow:0 12px 32px #0206178c}.cn-dropdown__item[data-v-ec8a19c1]{color:var(--cn-fg);text-align:left;white-space:nowrap;cursor:pointer;background:0 0;border:0;border-radius:6px;justify-content:space-between;align-items:center;gap:14px;padding:6px 12px;font-size:12px;line-height:1.4;display:flex}.cn-dropdown__item[data-v-ec8a19c1]:hover:not(:disabled){background:var(--cn-accent-soft);color:var(--cn-accent)}.cn-dropdown__item[data-v-ec8a19c1]:disabled{color:var(--cn-muted);cursor:not-allowed}.cn-dropdown__shortcut[data-v-ec8a19c1]{color:var(--cn-muted);font-variant-numeric:tabular-nums;flex:none;font-size:11px;line-height:1.4}.cn-dropdown__separator[data-v-ec8a19c1]{background:var(--cn-line);flex:none;height:1px;margin:3px 6px}.cn-statusbar[data-v-040479fb]{--cn-bg:#0b1220;--cn-bg-elevated:#101a2e;--cn-fg:#e6edf7;--cn-muted:#7c8ca5;--cn-line:#1e2a40;--cn-accent:#38bdf8;background-color:var(--cn-bg);color:var(--cn-muted);font-variant-numeric:tabular-nums;-webkit-user-select:none;user-select:none;align-items:center;gap:10px;padding:6px 12px;font-size:12px;line-height:1.4;display:flex}.cn-statusbar__segment[data-v-040479fb]{white-space:nowrap}.cn-statusbar__zoom-trigger[data-v-040479fb]{font:inherit;color:inherit;font-variant-numeric:tabular-nums;cursor:pointer;background:0 0;border:0;padding:0}.cn-statusbar__zoom-trigger[data-v-040479fb]:hover{color:var(--cn-accent)}.cn-statusbar__help[data-v-040479fb]{font:inherit;color:inherit;cursor:pointer;background:0 0;border:0;padding:0}.cn-statusbar__help[data-v-040479fb]:hover,.cn-statusbar__segment--pending[data-v-040479fb]{color:var(--cn-accent)}.cn-statusbar__divider[data-v-040479fb]{background:var(--cn-line);width:1px;height:12px}.cn-help[data-v-486e5be3]{--cn-bg:#0b1220;--cn-bg-elevated:#101a2e;--cn-fg:#e6edf7;--cn-muted:#7c8ca5;--cn-line:#1e2a40;--cn-accent:#38bdf8;z-index:50;background:#0206178c;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}.cn-help__panel[data-v-486e5be3]{background:var(--cn-bg-elevated);border:1px solid var(--cn-line);border-radius:12px;flex-direction:column;min-width:320px;max-width:480px;max-height:100%;display:flex;box-shadow:0 24px 64px #02061799}.cn-help__header[data-v-486e5be3]{border-bottom:1px solid var(--cn-line);justify-content:space-between;align-items:center;padding:12px 16px;display:flex}.cn-help__title[data-v-486e5be3]{color:var(--cn-fg);margin:0;font-size:14px;font-weight:600;line-height:1.4}.cn-help__close[data-v-486e5be3]{color:var(--cn-muted);cursor:pointer;background:0 0;border:0;border-radius:6px;padding:2px 8px;font-size:13px;line-height:1.4}.cn-help__close[data-v-486e5be3]:hover{color:var(--cn-fg);background:#94a3b812}.cn-help__body[data-v-486e5be3]{padding:8px 16px 16px;overflow-y:auto}.cn-help__section[data-v-486e5be3]{margin-top:12px}.cn-help__section[data-v-486e5be3]:first-child{margin-top:8px}.cn-help__section-title[data-v-486e5be3]{color:var(--cn-accent);margin:0 0 4px;font-size:11px;font-weight:600;line-height:1.4}.cn-help__list[data-v-486e5be3]{margin:0;padding:0;list-style:none}.cn-help__row[data-v-486e5be3]{justify-content:space-between;align-items:baseline;gap:16px;padding:3px 0;display:flex}.cn-help__label[data-v-486e5be3]{color:var(--cn-fg);font-size:12px;line-height:1.5}.cn-help__keys[data-v-486e5be3]{border:1px solid var(--cn-line);color:var(--cn-muted);white-space:nowrap;background:#94a3b812;border-radius:5px;flex-shrink:0;padding:1px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.4}
2
2
  /*$vite$:1*/