mechanica 2.0.0-alpha.3 → 2.0.0-alpha.5

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.
@@ -1,4 +1,11 @@
1
1
  import { Fragment, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, inject, mergeProps, normalizeClass, onMounted, openBlock, ref, renderList, renderSlot, shallowReactive, toDisplayString, unref, withCtx, withModifiers } from "vue";
2
+ //#region \0plugin-vue:export-helper
3
+ var _plugin_vue_export_helper_default = (sfc, props) => {
4
+ const target = sfc.__vccOpts || sfc;
5
+ for (const [key, val] of props) target[key] = val;
6
+ return target;
7
+ };
8
+ //#endregion
2
9
  //#region \0svg-glob:C:\Users\Den\Desktop\CURVES\mechanica\packages\mechanica\src\editor\icons
3
10
  var contents = {
4
11
  "align-center": `<path d="M4 6h16M7 12h10M6 18h12"/>`,
@@ -543,13 +550,6 @@ var VIcon_default = /* @__PURE__ */ defineComponent({
543
550
  }
544
551
  });
545
552
  //#endregion
546
- //#region \0plugin-vue:export-helper
547
- var _plugin_vue_export_helper_default = (sfc, props) => {
548
- const target = sfc.__vccOpts || sfc;
549
- for (const [key, val] of props) target[key] = val;
550
- return target;
551
- };
552
- //#endregion
553
553
  //#region src/editor/ui/dialog.ts
554
554
  var dialogKey = Symbol("mech-dialog");
555
555
  /** Create the reactive dialog store (provided once by the editor shell). */
@@ -869,4 +869,4 @@ var ImagePickerDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default(
869
869
  }
870
870
  }), [["__scopeId", "data-v-4ef9dd0d"]]);
871
871
  //#endregion
872
- export { renderCrop as a, useDialog as c, contents as d, readImageSize as i, _plugin_vue_export_helper_default as l, VDialog_default as n, createDialogStore as o, analyzeImageFile as r, dialogKey as s, ImagePickerDialog_default as t, VIcon_default as u };
872
+ export { renderCrop as a, useDialog as c, _plugin_vue_export_helper_default as d, readImageSize as i, VIcon_default as l, VDialog_default as n, createDialogStore as o, analyzeImageFile as r, dialogKey as s, ImagePickerDialog_default as t, contents as u };
@@ -446,7 +446,7 @@ var Frame = defineComponent({
446
446
  };
447
447
  }
448
448
  });
449
- var TEXT_TAGS = new Set([
449
+ var TEXT_TAGS = /* @__PURE__ */ new Set([
450
450
  "h1",
451
451
  "h2",
452
452
  "h3",
@@ -1,4 +1,4 @@
1
- import { a as renderCrop, c as useDialog, i as readImageSize, l as _plugin_vue_export_helper_default, n as VDialog_default, r as analyzeImageFile, s as dialogKey, t as ImagePickerDialog_default, u as VIcon_default } from "./ImagePickerDialog-BfjNtRoR.js";
1
+ import { a as renderCrop, c as useDialog, d as _plugin_vue_export_helper_default, i as readImageSize, l as VIcon_default, n as VDialog_default, r as analyzeImageFile, s as dialogKey, t as ImagePickerDialog_default } from "./ImagePickerDialog-CY-mn2UP.js";
2
2
  import { Fragment, Teleport, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, inject, mergeProps, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onErrorCaptured, onMounted, openBlock, provide, reactive, ref, renderList, renderSlot, resolveDynamicComponent, shallowRef, toDisplayString, unref, vShow, watch, withCtx, withDirectives, withKeys, withModifiers } from "vue";
3
3
  import { deepEqual, getDefaultValue } from "mechanica-shared";
4
4
  import { unfoldSchema } from "compact-json-schema";
@@ -19,9 +19,146 @@ function resolveFieldEditor(schema) {
19
19
  if (schema.type && editors.has(schema.type)) return editors.get(schema.type);
20
20
  return editors.get("string");
21
21
  }
22
+ /**
23
+ * The non-breaking character a keyboard event asks for, or `null`. Matches Word's
24
+ * muscle memory: Ctrl/Cmd+Shift+Space → nbsp, Ctrl/Cmd+Shift+Minus → nb-hyphen.
25
+ * Keyed off `event.code` (physical key) so it fires on any keyboard layout, the
26
+ * same way the editor's other shortcuts stay layout-independent.
27
+ */
28
+ function typographyCharForEvent(event) {
29
+ if (!(event.ctrlKey || event.metaKey) || !event.shiftKey || event.altKey) return null;
30
+ if (event.code === "Space") return "\xA0";
31
+ if (event.code === "Minus") return "‑";
32
+ return null;
33
+ }
34
+ /**
35
+ * Insert `char` at the caret of an `<input>`/`<textarea>`, replacing any
36
+ * selection, and leave the caret after it. Returns the field's new value so the
37
+ * caller can emit it. `setRangeText` keeps native undo working.
38
+ */
39
+ function insertAtCursor(el, char) {
40
+ const start = el.selectionStart ?? el.value.length;
41
+ const end = el.selectionEnd ?? start;
42
+ el.setRangeText(char, start, end, "end");
43
+ return el.value;
44
+ }
45
+ var HTML_ESCAPES = {
46
+ "&": "&amp;",
47
+ "<": "&lt;",
48
+ ">": "&gt;"
49
+ };
50
+ function escapeHtml(text) {
51
+ return text.replace(/[&<>]/g, (ch) => HTML_ESCAPES[ch] ?? ch);
52
+ }
53
+ /**
54
+ * HTML that mirrors `text` for the plain-field marker overlay: every character
55
+ * keeps its exact glyph advance (so the layer lines up with the real field), but
56
+ * the two non-breaking characters are wrapped in marker spans the CSS lights up.
57
+ * The characters themselves stay in the spans — that's what preserves the width.
58
+ */
59
+ function highlightTypographyHtml(text) {
60
+ return escapeHtml(text).replaceAll("\xA0", `<span class="mech-typo__nbsp"> </span>`).replaceAll("‑", `<span class="mech-typo__nbhyphen">‑</span>`);
61
+ }
62
+ /**
63
+ * vuewrite `parser`: scans a block's text and emits a one-character style span
64
+ * over every non-breaking character. vuewrite merges these at *render* time only
65
+ * — they never enter the stored model, so the Markdown on disk stays clean while
66
+ * the editor still shows where the glue is.
67
+ */
68
+ function typographyParser(text) {
69
+ const spans = [];
70
+ for (let i = 0; i < text.length; i++) if (text[i] === "\xA0") spans.push({
71
+ start: i,
72
+ end: i + 1,
73
+ style: "nbsp"
74
+ });
75
+ else if (text[i] === "‑") spans.push({
76
+ start: i,
77
+ end: i + 1,
78
+ style: "nbHyphen"
79
+ });
80
+ return spans;
81
+ }
82
+ //#endregion
83
+ //#region src/editor/components/TypographyInput.vue?vue&type=script&setup=true&lang.ts
84
+ var _hoisted_1$19 = { class: "mech-typo" };
85
+ var _hoisted_2$15 = [
86
+ "value",
87
+ "placeholder",
88
+ "rows"
89
+ ];
90
+ var _hoisted_3$12 = ["value", "placeholder"];
91
+ var _hoisted_4$9 = ["innerHTML"];
22
92
  //#endregion
23
- //#region src/editor/fields/editors/StringField.vue?vue&type=script&setup=true&lang.ts
24
- var _hoisted_1$20 = ["value", "placeholder"];
93
+ //#region src/editor/components/TypographyInput.vue
94
+ var TypographyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
95
+ __name: "TypographyInput",
96
+ props: {
97
+ modelValue: {},
98
+ multiline: { type: Boolean },
99
+ placeholder: {},
100
+ rows: { default: 3 }
101
+ },
102
+ emits: ["update:modelValue"],
103
+ setup(__props, { emit: __emit }) {
104
+ const props = __props;
105
+ const emit = __emit;
106
+ const fieldRef = ref();
107
+ const overlayRef = ref();
108
+ const highlighted = computed(() => highlightTypographyHtml(props.modelValue ?? ""));
109
+ function onInput(event) {
110
+ emit("update:modelValue", event.target.value);
111
+ nextTick(syncScroll);
112
+ }
113
+ function onKeyDown(event) {
114
+ const char = typographyCharForEvent(event);
115
+ if (!char) return;
116
+ event.preventDefault();
117
+ const el = fieldRef.value;
118
+ if (!el) return;
119
+ emit("update:modelValue", insertAtCursor(el, char));
120
+ nextTick(syncScroll);
121
+ }
122
+ function syncScroll() {
123
+ const el = fieldRef.value;
124
+ const overlay = overlayRef.value;
125
+ if (!el || !overlay) return;
126
+ overlay.scrollTop = el.scrollTop;
127
+ overlay.scrollLeft = el.scrollLeft;
128
+ }
129
+ return (_ctx, _cache) => {
130
+ return openBlock(), createElementBlock("div", _hoisted_1$19, [__props.multiline ? (openBlock(), createElementBlock("textarea", {
131
+ key: 0,
132
+ ref_key: "fieldRef",
133
+ ref: fieldRef,
134
+ class: "mech-input mech-textarea",
135
+ value: __props.modelValue,
136
+ placeholder: __props.placeholder,
137
+ rows: __props.rows,
138
+ onInput,
139
+ onKeydown: onKeyDown,
140
+ onScroll: syncScroll
141
+ }, null, 40, _hoisted_2$15)) : (openBlock(), createElementBlock("input", {
142
+ key: 1,
143
+ ref_key: "fieldRef",
144
+ ref: fieldRef,
145
+ class: "mech-input",
146
+ type: "text",
147
+ value: __props.modelValue,
148
+ placeholder: __props.placeholder,
149
+ onInput,
150
+ onKeydown: onKeyDown,
151
+ onScroll: syncScroll
152
+ }, null, 40, _hoisted_3$12)), createElementVNode("div", {
153
+ ref_key: "overlayRef",
154
+ ref: overlayRef,
155
+ class: normalizeClass(["mech-typo__overlay", __props.multiline ? "is-multiline" : "is-single"]),
156
+ "aria-hidden": "true",
157
+ innerHTML: highlighted.value
158
+ }, null, 10, _hoisted_4$9)]);
159
+ };
160
+ }
161
+ }), [["__scopeId", "data-v-0086c808"]]);
25
162
  //#endregion
26
163
  //#region src/editor/fields/editors/StringField.vue
27
164
  var StringField_default = /* @__PURE__ */ defineComponent({
@@ -33,22 +170,16 @@ var StringField_default = /* @__PURE__ */ defineComponent({
33
170
  emits: ["update:modelValue"],
34
171
  setup(__props, { emit: __emit }) {
35
172
  const emit = __emit;
36
- const onInput = (event) => emit("update:modelValue", event.target.value);
37
173
  return (_ctx, _cache) => {
38
- return openBlock(), createElementBlock("input", {
39
- class: "mech-input",
40
- type: "text",
41
- value: __props.modelValue,
174
+ return openBlock(), createBlock(TypographyInput_default, {
175
+ "model-value": __props.modelValue,
42
176
  placeholder: __props.schema.placeholder,
43
- onInput
44
- }, null, 40, _hoisted_1$20);
177
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => emit("update:modelValue", $event))
178
+ }, null, 8, ["model-value", "placeholder"]);
45
179
  };
46
180
  }
47
181
  });
48
182
  //#endregion
49
- //#region src/editor/fields/editors/TextField.vue?vue&type=script&setup=true&lang.ts
50
- var _hoisted_1$19 = ["value", "placeholder"];
51
- //#endregion
52
183
  //#region src/editor/fields/editors/TextField.vue
53
184
  var TextField_default = /* @__PURE__ */ defineComponent({
54
185
  __name: "TextField",
@@ -59,15 +190,14 @@ var TextField_default = /* @__PURE__ */ defineComponent({
59
190
  emits: ["update:modelValue"],
60
191
  setup(__props, { emit: __emit }) {
61
192
  const emit = __emit;
62
- const onInput = (event) => emit("update:modelValue", event.target.value);
63
193
  return (_ctx, _cache) => {
64
- return openBlock(), createElementBlock("textarea", {
65
- class: "mech-input mech-textarea",
66
- value: __props.modelValue,
194
+ return openBlock(), createBlock(TypographyInput_default, {
195
+ multiline: "",
196
+ "model-value": __props.modelValue,
67
197
  placeholder: __props.schema.placeholder,
68
- rows: "3",
69
- onInput
70
- }, null, 40, _hoisted_1$19);
198
+ rows: 3,
199
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => emit("update:modelValue", $event))
200
+ }, null, 8, ["model-value", "placeholder"]);
71
201
  };
72
202
  }
73
203
  });
@@ -1477,6 +1607,14 @@ var decorator = (style) => {
1477
1607
  }
1478
1608
  };
1479
1609
  case "color": return { style: `color: ${style.meta?.color};` };
1610
+ case "nbsp": return {
1611
+ tag: "span",
1612
+ class: "rt-nbsp"
1613
+ };
1614
+ case "nbHyphen": return {
1615
+ tag: "span",
1616
+ class: "rt-nbhyphen"
1617
+ };
1480
1618
  default: return;
1481
1619
  }
1482
1620
  };
@@ -1703,7 +1841,7 @@ var builtinWidgets = [
1703
1841
  }
1704
1842
  ];
1705
1843
  /** Block types owned by the core renderer/codec — widgets can't claim them. */
1706
- var RESERVED_TYPES = new Set([
1844
+ var RESERVED_TYPES = /* @__PURE__ */ new Set([
1707
1845
  "h1",
1708
1846
  "h2",
1709
1847
  "h3",
@@ -2065,9 +2203,28 @@ var RichTextEditor_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2065
2203
  autosizeMarkdown();
2066
2204
  nextTick(() => fromMarkdown = false);
2067
2205
  }
2206
+ /** The nbsp / non-breaking-hyphen shortcuts in the raw Markdown view too. */
2207
+ function onMarkdownKeyDown(event) {
2208
+ const char = typographyCharForEvent(event);
2209
+ if (!char || !markdownRef.value) return;
2210
+ event.preventDefault();
2211
+ const md = insertAtCursor(markdownRef.value, char);
2212
+ markdown.value = md;
2213
+ fromMarkdown = true;
2214
+ model.value = markdownToBlocks(md, model.value, { softBreaks: true });
2215
+ autosizeMarkdown();
2216
+ nextTick(() => fromMarkdown = false);
2217
+ }
2068
2218
  function onKeyDown(event) {
2069
2219
  const editor = editorRef.value;
2070
2220
  if (!editor) return;
2221
+ const nb = typographyCharForEvent(event);
2222
+ if (nb) {
2223
+ event.preventDefault();
2224
+ editor.insertText(nb);
2225
+ editor.pushHistory("insertText");
2226
+ return;
2227
+ }
2071
2228
  if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) {
2072
2229
  const style = {
2073
2230
  KeyB: "bold",
@@ -2192,6 +2349,7 @@ var RichTextEditor_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2192
2349
  class: "mech-rte__surface",
2193
2350
  renderer: unref(renderer),
2194
2351
  decorator: unref(decorator),
2352
+ parser: unref(typographyParser),
2195
2353
  "html-parser": unref(htmlParser),
2196
2354
  onKeydown: onKeyDown,
2197
2355
  onContextmenu: onContextMenu
@@ -2213,6 +2371,7 @@ var RichTextEditor_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2213
2371
  "modelValue",
2214
2372
  "renderer",
2215
2373
  "decorator",
2374
+ "parser",
2216
2375
  "html-parser"
2217
2376
  ])) : (openBlock(), createElementBlock("textarea", {
2218
2377
  key: 2,
@@ -2222,11 +2381,12 @@ var RichTextEditor_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2222
2381
  value: markdown.value,
2223
2382
  spellcheck: "false",
2224
2383
  placeholder: __props.placeholder,
2225
- onInput: onMarkdownInput
2384
+ onInput: onMarkdownInput,
2385
+ onKeydown: onMarkdownKeyDown
2226
2386
  }, null, 40, _hoisted_8))])], 2);
2227
2387
  };
2228
2388
  }
2229
- }), [["__scopeId", "data-v-f118319c"]]);
2389
+ }), [["__scopeId", "data-v-6729e5a2"]]);
2230
2390
  //#endregion
2231
2391
  //#region src/editor/dialogs/RichTextDialog.vue
2232
2392
  var RichTextDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -1,4 +1,4 @@
1
- import { i as renderBlocks, t as createMechanica } from "./create-mechanica-CM_DrG9j.js";
1
+ import { i as renderBlocks, t as createMechanica } from "./create-mechanica-CnBFiyfJ.js";
2
2
  import { createApp, defineComponent, h } from "vue";
3
3
  import { areFieldSchemasRegistered, buildPreviewData, registerFieldSchemas } from "mechanica-shared";
4
4
  import { unfoldSchema } from "compact-json-schema";
package/dist/cli.js CHANGED
@@ -363,7 +363,7 @@ async function exportProject(cwd, ssr, options = {}) {
363
363
  force: true
364
364
  });
365
365
  await mkdir(exportDir, { recursive: true });
366
- const DIST_PRIVATE = new Set([
366
+ const DIST_PRIVATE = /* @__PURE__ */ new Set([
367
367
  "index.html",
368
368
  "ssr.js",
369
369
  "ssr.js.map",
package/dist/composer.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import './editor.css';
2
- import { c as ANCHOR_H, i as renderBlocks, l as ANCHOR_V, t as createMechanica } from "./chunks/create-mechanica-CM_DrG9j.js";
3
- import { C as registerBuiltinFieldEditors, N as VSelect_default, P as VPopover_default, S as uid, _ as findParentSlot, a as FieldControl_default, b as relocateBlock, d as humanize, f as cloneBlock, g as findParentList, h as findBlock, i as SchemaField_default, m as ensureSlotList, n as createSaveQueue, o as shortcutChar, p as duplicateBlock, t as SaveConflictError, u as toBlockMeta, v as moveBlock, x as removeBlock, y as placeBlock } from "./chunks/editor-pyN0-GS5.js";
4
- import { d as contents, l as _plugin_vue_export_helper_default, u as VIcon_default } from "./chunks/ImagePickerDialog-BfjNtRoR.js";
2
+ import { c as ANCHOR_H, i as renderBlocks, l as ANCHOR_V, t as createMechanica } from "./chunks/create-mechanica-CnBFiyfJ.js";
3
+ import { C as registerBuiltinFieldEditors, N as VSelect_default, P as VPopover_default, S as uid, _ as findParentSlot, a as FieldControl_default, b as relocateBlock, d as humanize, f as cloneBlock, g as findParentList, h as findBlock, i as SchemaField_default, m as ensureSlotList, n as createSaveQueue, o as shortcutChar, p as duplicateBlock, t as SaveConflictError, u as toBlockMeta, v as moveBlock, x as removeBlock, y as placeBlock } from "./chunks/editor-9tquvDcS.js";
4
+ import { d as _plugin_vue_export_helper_default, l as VIcon_default, u as contents } from "./chunks/ImagePickerDialog-CY-mn2UP.js";
5
5
  import { Fragment, Teleport, computed, createApp, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, inject, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, onScopeDispose, openBlock, provide, reactive, ref, renderList, renderSlot, resolveComponent, toDisplayString, unref, vModelText, watch, withCtx, withDirectives, withKeys, withModifiers } from "vue";
6
6
  import { buildPreviewData, getDefaultValue, isBinding, registerFieldSchemas, resolveBindings, walkTree } from "mechanica-shared";
7
7
  import { unfoldSchema } from "compact-json-schema";
@@ -2031,7 +2031,8 @@ var CanvasOverlay_default = /* @__PURE__ */ defineComponent({
2031
2031
  };
2032
2032
  gapRaf = requestAnimationFrame(loop);
2033
2033
  const onMove = (e) => {
2034
- const raw = startGap + ((axis === "x" ? e.clientX : e.clientY) - startPos) / store.zoom;
2034
+ const pos = axis === "x" ? e.clientX : e.clientY;
2035
+ const raw = startGap + (pos - startPos) / store.zoom;
2035
2036
  const gap = Math.max(0, e.shiftKey ? snapTo(raw, SNAP) : Math.round(raw));
2036
2037
  store.setData(id, { gap }, { responsive: true });
2037
2038
  };
@@ -2065,7 +2066,9 @@ var CanvasOverlay_default = /* @__PURE__ */ defineComponent({
2065
2066
  const onKey = (e) => padAlt.value = e.altKey;
2066
2067
  const onMove = (e) => {
2067
2068
  padAlt.value = e.altKey;
2068
- const sides = computePaddingDrag(startSides, side, sign * ((axis === "x" ? e.clientX : e.clientY) - startPos) / store.zoom, {
2069
+ const pos = axis === "x" ? e.clientX : e.clientY;
2070
+ const delta = sign * (pos - startPos) / store.zoom;
2071
+ const sides = computePaddingDrag(startSides, side, delta, {
2069
2072
  symmetric: e.altKey,
2070
2073
  snap: e.shiftKey ? SNAP : 0
2071
2074
  });
@@ -2417,7 +2420,8 @@ var ComposerCanvas_default = /* @__PURE__ */ defineComponent({
2417
2420
  hoverId.value = null;
2418
2421
  return;
2419
2422
  }
2420
- hoverId.value = (document.elementFromPoint(event.clientX, event.clientY)?.closest("[data-block-id]"))?.getAttribute("data-block-id") ?? null;
2423
+ const el = document.elementFromPoint(event.clientX, event.clientY)?.closest("[data-block-id]");
2424
+ hoverId.value = el?.getAttribute("data-block-id") ?? null;
2421
2425
  }
2422
2426
  function onCanvasDblClick(event) {
2423
2427
  const el = event.target.closest("[data-block-id]");