@upstart.gg/vite-plugins 0.1.83 → 0.1.85
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/dist/site-theme.d.ts +55 -0
- package/dist/site-theme.d.ts.map +1 -0
- package/dist/site-theme.js +184 -0
- package/dist/site-theme.js.map +1 -0
- package/dist/upstart-editor-api.d.ts +63 -1
- package/dist/upstart-editor-api.d.ts.map +1 -1
- package/dist/upstart-editor-api.js +233 -1
- package/dist/upstart-editor-api.js.map +1 -1
- package/dist/vite-plugin-upstart-attrs.d.ts +1 -1
- package/dist/vite-plugin-upstart-attrs.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-attrs.js +65 -2
- package/dist/vite-plugin-upstart-attrs.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/array-controls.js +2 -24
- package/dist/vite-plugin-upstart-editor/runtime/array-controls.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js +7 -1
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/controls-ui.js +33 -0
- package/dist/vite-plugin-upstart-editor/runtime/controls-ui.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/index.js +35 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/labels.d.ts +5 -0
- package/dist/vite-plugin-upstart-editor/runtime/labels.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/labels.js +5 -0
- package/dist/vite-plugin-upstart-editor/runtime/labels.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/move-controls.js +434 -0
- package/dist/vite-plugin-upstart-editor/runtime/move-controls.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +9 -3
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/site-theme.ts +205 -0
- package/src/tests/move-controls.test.ts +86 -0
- package/src/tests/site-theme.test.ts +122 -0
- package/src/tests/upstart-editor-api-move-element.test.ts +220 -0
- package/src/tests/vite-plugin-upstart-attrs.test.ts +165 -4
- package/src/upstart-editor-api.ts +294 -0
- package/src/vite-plugin-upstart-attrs.ts +93 -2
- package/src/vite-plugin-upstart-editor/runtime/array-controls.ts +2 -40
- package/src/vite-plugin-upstart-editor/runtime/click-handler.ts +17 -3
- package/src/vite-plugin-upstart-editor/runtime/controls-ui.ts +48 -0
- package/src/vite-plugin-upstart-editor/runtime/index.ts +46 -0
- package/src/vite-plugin-upstart-editor/runtime/labels.ts +10 -0
- package/src/vite-plugin-upstart-editor/runtime/move-controls.ts +536 -0
- package/src/vite-plugin-upstart-editor/runtime/types.ts +9 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getCurrentMode } from "./state.js";
|
|
2
2
|
import { sendToParent } from "./utils.js";
|
|
3
3
|
import { getLabels } from "./labels.js";
|
|
4
|
+
import { baseButton, createControlsLayer, cssAttr, setIcon } from "./controls-ui.js";
|
|
4
5
|
|
|
5
6
|
// Editor-only controls for inline-array .map() lists. Add/delete are staged LOCALLY
|
|
6
7
|
// (no server round-trip per action): "×" marks an item for removal, "+" inserts an
|
|
@@ -191,12 +192,7 @@ export function hideArrayControls(): void {
|
|
|
191
192
|
|
|
192
193
|
function ensureLayer(): HTMLDivElement {
|
|
193
194
|
if (layer) return layer;
|
|
194
|
-
layer =
|
|
195
|
-
layer.id = "upstart-array-controls";
|
|
196
|
-
layer.style.cssText =
|
|
197
|
-
"position: absolute; top: 0; left: 0; width: 0; height: 0; " +
|
|
198
|
-
"pointer-events: none; z-index: 2147483646;";
|
|
199
|
-
document.body.appendChild(layer);
|
|
195
|
+
layer = createControlsLayer("upstart-array-controls");
|
|
200
196
|
return layer;
|
|
201
197
|
}
|
|
202
198
|
|
|
@@ -237,10 +233,6 @@ function isReordered(arrayId: string): boolean {
|
|
|
237
233
|
return false;
|
|
238
234
|
}
|
|
239
235
|
|
|
240
|
-
function cssAttr(value: string): string {
|
|
241
|
-
return value.replace(/["\\]/g, "\\$&");
|
|
242
|
-
}
|
|
243
|
-
|
|
244
236
|
function textOf(el: HTMLElement): string {
|
|
245
237
|
return (el.textContent ?? "").trim();
|
|
246
238
|
}
|
|
@@ -261,36 +253,6 @@ function applyDeletionStyle(item: HTMLElement, marked: boolean): void {
|
|
|
261
253
|
// Buttons
|
|
262
254
|
// ---------------------------------------------------------------------------
|
|
263
255
|
|
|
264
|
-
// Geometric SVG icons — centred by the flex container + viewBox, so they don't
|
|
265
|
-
// depend on the host page's font metrics (which left text glyphs mis-aligned).
|
|
266
|
-
const ICON_PATHS: Record<string, string> = {
|
|
267
|
-
plus: '<path d="M12 5v14M5 12h14"/>',
|
|
268
|
-
cross: '<path d="M6 6 18 18M18 6 6 18"/>',
|
|
269
|
-
check: '<path d="M5 12.5 10 17.5 19 7"/>',
|
|
270
|
-
restore: '<path d="M3 4v5h5"/><path d="M3.5 9a8.5 8.5 0 1 0 2.2-3.8L3 9"/>',
|
|
271
|
-
grip: '<circle cx="9" cy="6" r="1.5"/><circle cx="15" cy="6" r="1.5"/><circle cx="9" cy="12" r="1.5"/><circle cx="15" cy="12" r="1.5"/><circle cx="9" cy="18" r="1.5"/><circle cx="15" cy="18" r="1.5"/>',
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
function setIcon(btn: HTMLElement, name: keyof typeof ICON_PATHS, px: number): void {
|
|
275
|
-
const dots = name === "grip";
|
|
276
|
-
btn.innerHTML =
|
|
277
|
-
`<svg width="${px}" height="${px}" viewBox="0 0 24 24" ` +
|
|
278
|
-
`fill="${dots ? "currentColor" : "none"}" stroke="${dots ? "none" : "currentColor"}" ` +
|
|
279
|
-
'stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" ' +
|
|
280
|
-
`style="display:block;pointer-events:none">${ICON_PATHS[name]}</svg>`;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function baseButton(top: number, left: number, size: number, bg: string): HTMLButtonElement {
|
|
284
|
-
const btn = document.createElement("button");
|
|
285
|
-
btn.type = "button";
|
|
286
|
-
btn.style.cssText =
|
|
287
|
-
`position: absolute; top: ${top}px; left: ${left}px; width: ${size}px; height: ${size}px; ` +
|
|
288
|
-
"padding: 0; display: flex; align-items: center; justify-content: center; " +
|
|
289
|
-
`background: ${bg}; color: #fff; border: 1px solid #fff; border-radius: 50%; ` +
|
|
290
|
-
"line-height: 1; cursor: pointer; pointer-events: auto; box-shadow: 0 1px 3px rgba(0,0,0,0.3);";
|
|
291
|
-
return btn;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
256
|
function makeItemDeleteButton(
|
|
295
257
|
arrayId: string,
|
|
296
258
|
index: number,
|
|
@@ -206,6 +206,15 @@ function handleSelectClick(event: MouseEvent, target: HTMLElement): void {
|
|
|
206
206
|
|
|
207
207
|
function handleClick(event: MouseEvent): void {
|
|
208
208
|
const mode = getCurrentMode();
|
|
209
|
+
if (mode === "move") {
|
|
210
|
+
// The page is inert while moving elements: following a link or submitting a form would
|
|
211
|
+
// reload the page and drop the staged moves. The move controls handle their own clicks.
|
|
212
|
+
const target = event.target as HTMLElement | null;
|
|
213
|
+
if (target?.closest("#upstart-move-controls, #upstart-move-bar")) return;
|
|
214
|
+
event.preventDefault();
|
|
215
|
+
event.stopPropagation();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
209
218
|
if (mode !== "edit" && mode !== "select") {
|
|
210
219
|
return;
|
|
211
220
|
}
|
|
@@ -225,9 +234,14 @@ function handleClick(event: MouseEvent): void {
|
|
|
225
234
|
return;
|
|
226
235
|
}
|
|
227
236
|
|
|
228
|
-
// Editor-only chrome (the array ×/+/✓ controls
|
|
229
|
-
// items handle their own clicks and must never be treated
|
|
230
|
-
|
|
237
|
+
// Editor-only chrome (the array ×/+/✓ controls, the move handles and pending-moves bar)
|
|
238
|
+
// and locally-staged draft list items handle their own clicks and must never be treated
|
|
239
|
+
// as element selection.
|
|
240
|
+
if (
|
|
241
|
+
target.closest(
|
|
242
|
+
"#upstart-array-controls, #upstart-move-controls, #upstart-move-bar, [data-upstart-draft-item]",
|
|
243
|
+
)
|
|
244
|
+
) {
|
|
231
245
|
return;
|
|
232
246
|
}
|
|
233
247
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Shared building blocks of the editor-only controls (array list buttons, move handles).
|
|
2
|
+
|
|
3
|
+
// Geometric SVG icons — centred by the flex container + viewBox, so they don't
|
|
4
|
+
// depend on the host page's font metrics (which left text glyphs mis-aligned).
|
|
5
|
+
const ICON_PATHS = {
|
|
6
|
+
plus: '<path d="M12 5v14M5 12h14"/>',
|
|
7
|
+
cross: '<path d="M6 6 18 18M18 6 6 18"/>',
|
|
8
|
+
check: '<path d="M5 12.5 10 17.5 19 7"/>',
|
|
9
|
+
restore: '<path d="M3 4v5h5"/><path d="M3.5 9a8.5 8.5 0 1 0 2.2-3.8L3 9"/>',
|
|
10
|
+
grip: '<circle cx="9" cy="6" r="1.5"/><circle cx="15" cy="6" r="1.5"/><circle cx="9" cy="12" r="1.5"/><circle cx="15" cy="12" r="1.5"/><circle cx="9" cy="18" r="1.5"/><circle cx="15" cy="18" r="1.5"/>',
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export type IconName = keyof typeof ICON_PATHS;
|
|
14
|
+
|
|
15
|
+
export function setIcon(btn: HTMLElement, name: IconName, px: number): void {
|
|
16
|
+
const dots = name === "grip";
|
|
17
|
+
btn.innerHTML =
|
|
18
|
+
`<svg width="${px}" height="${px}" viewBox="0 0 24 24" ` +
|
|
19
|
+
`fill="${dots ? "currentColor" : "none"}" stroke="${dots ? "none" : "currentColor"}" ` +
|
|
20
|
+
'stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" ' +
|
|
21
|
+
`style="display:block;pointer-events:none">${ICON_PATHS[name]}</svg>`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function baseButton(top: number, left: number, size: number, bg: string): HTMLButtonElement {
|
|
25
|
+
const btn = document.createElement("button");
|
|
26
|
+
btn.type = "button";
|
|
27
|
+
btn.style.cssText =
|
|
28
|
+
`position: absolute; top: ${top}px; left: ${left}px; width: ${size}px; height: ${size}px; ` +
|
|
29
|
+
"padding: 0; display: flex; align-items: center; justify-content: center; " +
|
|
30
|
+
`background: ${bg}; color: #fff; border: 1px solid #fff; border-radius: 50%; ` +
|
|
31
|
+
"line-height: 1; cursor: pointer; pointer-events: auto; box-shadow: 0 1px 3px rgba(0,0,0,0.3);";
|
|
32
|
+
return btn;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Absolutely positioned, click-through layer appended to <body> that hosts editor controls. */
|
|
36
|
+
export function createControlsLayer(id: string): HTMLDivElement {
|
|
37
|
+
const layer = document.createElement("div");
|
|
38
|
+
layer.id = id;
|
|
39
|
+
layer.style.cssText =
|
|
40
|
+
"position: absolute; top: 0; left: 0; width: 0; height: 0; " +
|
|
41
|
+
"pointer-events: none; z-index: 2147483646;";
|
|
42
|
+
document.body.appendChild(layer);
|
|
43
|
+
return layer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function cssAttr(value: string): string {
|
|
47
|
+
return value.replace(/["\\]/g, "\\$&");
|
|
48
|
+
}
|
|
@@ -4,6 +4,12 @@ import { initErrorHandler } from "./error-handler.js";
|
|
|
4
4
|
import { installDomGuard } from "./dom-guard.js";
|
|
5
5
|
import { initTextEditor, activateAllEditors, destroyAllActiveEditors } from "./text-editor.js";
|
|
6
6
|
import { initArrayControls, refreshArrayControls, hideArrayControls } from "./array-controls.js";
|
|
7
|
+
import {
|
|
8
|
+
initMoveControls,
|
|
9
|
+
refreshMoveControls,
|
|
10
|
+
hideMoveControls,
|
|
11
|
+
onMovesApplyFailed,
|
|
12
|
+
} from "./move-controls.js";
|
|
7
13
|
import { initFormGuard } from "./form-guard.js";
|
|
8
14
|
import { sendToParent } from "./utils.js";
|
|
9
15
|
import { getCurrentMode, setCurrentMode, clearSelection, removeSelection, getSelection } from "./state.js";
|
|
@@ -22,13 +28,20 @@ export function setMode(mode: EditorMode): void {
|
|
|
22
28
|
console.log(`[Upstart Editor] Setting mode to: ${mode}`);
|
|
23
29
|
if (mode === "edit") {
|
|
24
30
|
disableSelectMode();
|
|
31
|
+
disableMoveMode();
|
|
25
32
|
enableEditMode();
|
|
26
33
|
} else if (mode === "select") {
|
|
27
34
|
disableEditMode();
|
|
35
|
+
disableMoveMode();
|
|
28
36
|
enableSelectMode();
|
|
37
|
+
} else if (mode === "move") {
|
|
38
|
+
disableEditMode();
|
|
39
|
+
disableSelectMode();
|
|
40
|
+
enableMoveMode();
|
|
29
41
|
} else {
|
|
30
42
|
disableEditMode();
|
|
31
43
|
disableSelectMode();
|
|
44
|
+
disableMoveMode();
|
|
32
45
|
}
|
|
33
46
|
}
|
|
34
47
|
|
|
@@ -156,6 +169,7 @@ export function initUpstartEditor(): void {
|
|
|
156
169
|
initClickHandler();
|
|
157
170
|
initHoverOverlay();
|
|
158
171
|
initArrayControls();
|
|
172
|
+
initMoveControls();
|
|
159
173
|
initFormGuard();
|
|
160
174
|
initErrorHandler();
|
|
161
175
|
initRouteReporter();
|
|
@@ -237,6 +251,21 @@ function handleParentMessage(event: MessageEvent): void {
|
|
|
237
251
|
} else if (styleEl) {
|
|
238
252
|
styleEl.remove();
|
|
239
253
|
}
|
|
254
|
+
} else if (message.type === "preview-theme") {
|
|
255
|
+
// Appended last in <head> so it wins over the theme's own variables until it is cleared.
|
|
256
|
+
const STYLE_ID = "upstart-preview-theme";
|
|
257
|
+
let styleEl = document.getElementById(STYLE_ID) as HTMLStyleElement | null;
|
|
258
|
+
const css = typeof message.css === "string" ? message.css : "";
|
|
259
|
+
if (css) {
|
|
260
|
+
if (!styleEl) {
|
|
261
|
+
styleEl = document.createElement("style");
|
|
262
|
+
styleEl.id = STYLE_ID;
|
|
263
|
+
}
|
|
264
|
+
styleEl.textContent = css;
|
|
265
|
+
document.head.appendChild(styleEl);
|
|
266
|
+
} else if (styleEl) {
|
|
267
|
+
styleEl.remove();
|
|
268
|
+
}
|
|
240
269
|
} else if (message.type === "request-scroll-position") {
|
|
241
270
|
sendToParent({ type: "scroll-position", x: window.scrollX, y: window.scrollY });
|
|
242
271
|
} else if (message.type === "restore-scroll-position") {
|
|
@@ -251,6 +280,8 @@ function handleParentMessage(event: MessageEvent): void {
|
|
|
251
280
|
sendToParent({ type: "selection-changed", selection: getSelection() });
|
|
252
281
|
} else if (message.type === "set-labels") {
|
|
253
282
|
setLabels(message.labels);
|
|
283
|
+
} else if (message.type === "moves-apply-failed") {
|
|
284
|
+
onMovesApplyFailed();
|
|
254
285
|
}
|
|
255
286
|
}
|
|
256
287
|
|
|
@@ -288,6 +319,21 @@ function enableSelectMode(): void {
|
|
|
288
319
|
document.documentElement.setAttribute("data-upstart-select-mode", "");
|
|
289
320
|
}
|
|
290
321
|
|
|
322
|
+
function enableMoveMode(): void {
|
|
323
|
+
console.log("[Upstart Editor] Move mode enabled");
|
|
324
|
+
document.documentElement.setAttribute("data-upstart-move-mode", "");
|
|
325
|
+
refreshMoveControls();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Staged moves are kept when leaving the mode (the moved elements stay where they were
|
|
330
|
+
* dropped); the pending-moves bar comes back with the mode.
|
|
331
|
+
*/
|
|
332
|
+
function disableMoveMode(): void {
|
|
333
|
+
document.documentElement.removeAttribute("data-upstart-move-mode");
|
|
334
|
+
hideMoveControls();
|
|
335
|
+
}
|
|
336
|
+
|
|
291
337
|
function disableSelectMode(): void {
|
|
292
338
|
document.documentElement.removeAttribute("data-upstart-select-mode");
|
|
293
339
|
hideOverlays();
|
|
@@ -11,6 +11,11 @@ export interface RuntimeLabels {
|
|
|
11
11
|
addItem: string;
|
|
12
12
|
applyChanges: string;
|
|
13
13
|
dragToReorder: string;
|
|
14
|
+
dragToMove: string;
|
|
15
|
+
pendingMoves: string;
|
|
16
|
+
undoMove: string;
|
|
17
|
+
discardMoves: string;
|
|
18
|
+
applyMoves: string;
|
|
14
19
|
clickToEdit: string;
|
|
15
20
|
startTyping: string;
|
|
16
21
|
recoveryTitle: string;
|
|
@@ -34,6 +39,11 @@ export const DEFAULT_LABELS: Readonly<RuntimeLabels> = Object.freeze({
|
|
|
34
39
|
addItem: "Add item",
|
|
35
40
|
applyChanges: "Apply changes",
|
|
36
41
|
dragToReorder: "Drag to reorder",
|
|
42
|
+
dragToMove: "Drag to move",
|
|
43
|
+
pendingMoves: "Moves not saved yet:",
|
|
44
|
+
undoMove: "Undo",
|
|
45
|
+
discardMoves: "Discard all",
|
|
46
|
+
applyMoves: "Apply",
|
|
37
47
|
clickToEdit: "Click to edit...",
|
|
38
48
|
startTyping: "Start typing...",
|
|
39
49
|
recoveryTitle: "The preview needs a refresh",
|