castle-web-cli 0.4.108 → 0.4.110
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/agent-failures.d.ts +1 -1
- package/dist/agent-failures.js +2 -0
- package/dist/agent-prompts.js +59 -10
- package/dist/agent.js +97 -16
- package/dist/metering.d.ts +1 -0
- package/dist/metering.js +3 -0
- package/dist/shell/assets/index-BK2M69q4.js +144 -0
- package/dist/shell/index.html +1 -1
- package/kits/physics-2d/castle.json +1 -1
- package/kits/physics-2d/engine/ui.jsx +96 -15
- package/kits/physics-2d/engine/ui.module.css +18 -2
- package/package.json +1 -1
- package/dist/shell/assets/index-BU9_JpPe.js +0 -144
package/dist/shell/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>Castle Editor</title>
|
|
7
|
-
<script type="module" crossorigin src="/__castle/ide/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/__castle/ide/assets/index-BK2M69q4.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/__castle/ide/assets/index-wU4ol--C.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -67,22 +67,39 @@ export function useElementSize(ref) {
|
|
|
67
67
|
}, [ref]);
|
|
68
68
|
return size;
|
|
69
69
|
}
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const query = '(max-width: 600px)';
|
|
75
|
-
const [compact, setCompact] = useState(
|
|
70
|
+
// Live `matchMedia` as a hook: re-renders when the query starts/stops matching
|
|
71
|
+
// (plugging in a mouse, rotating, resizing a panel).
|
|
72
|
+
function useMediaQuery(query) {
|
|
73
|
+
const [matches, setMatches] = useState(
|
|
76
74
|
() => typeof window !== 'undefined' && window.matchMedia(query).matches
|
|
77
75
|
);
|
|
78
76
|
useEffect(() => {
|
|
79
77
|
const mql = window.matchMedia(query);
|
|
80
|
-
const onChange = (event) =>
|
|
78
|
+
const onChange = (event) => setMatches(event.matches);
|
|
81
79
|
mql.addEventListener('change', onChange);
|
|
82
|
-
|
|
80
|
+
setMatches(mql.matches);
|
|
83
81
|
return () => mql.removeEventListener('change', onChange);
|
|
84
|
-
}, []);
|
|
85
|
-
return
|
|
82
|
+
}, [query]);
|
|
83
|
+
return matches;
|
|
84
|
+
}
|
|
85
|
+
// True when the viewport is in the compact (≤600px) layout, where the file
|
|
86
|
+
// list and inspector become bottom sheets. Mirrors the `@media (max-width:
|
|
87
|
+
// 600px)` breakpoint so JS behavior (toggle target) tracks the CSS layout.
|
|
88
|
+
//
|
|
89
|
+
// NOTE: this is a question about SPACE, not about the input device. The editor
|
|
90
|
+
// renders inside an iframe whose viewport is the PANEL's width, so a narrow
|
|
91
|
+
// Flow card trips this on a desktop machine — which is correct for layout
|
|
92
|
+
// density and wrong for anything touch-shaped. For that, use useCoarsePointer.
|
|
93
|
+
export function useCompactViewport() {
|
|
94
|
+
return useMediaQuery('(max-width: 600px)');
|
|
95
|
+
}
|
|
96
|
+
// True when the primary pointer is a finger rather than a mouse/trackpad/stylus
|
|
97
|
+
// — i.e. the person can't reliably hit a small target, has no hover, and gets a
|
|
98
|
+
// native keyboard instead of a physical one. The right gate for touch-first
|
|
99
|
+
// affordances (the numpad sheet, big hit areas), since it stays false on a
|
|
100
|
+
// desktop no matter how narrow the panel gets.
|
|
101
|
+
export function useCoarsePointer() {
|
|
102
|
+
return useMediaQuery('(pointer: coarse)');
|
|
86
103
|
}
|
|
87
104
|
export function AppShell({ children }) {
|
|
88
105
|
return <div className={styles.appShell}>{children}</div>;
|
|
@@ -378,13 +395,60 @@ const SCRUB_THRESHOLD = 12;
|
|
|
378
395
|
const SCRUB_PX_PER_STEP = 12;
|
|
379
396
|
const SCRUB_ACCEL_RANGE = 240;
|
|
380
397
|
|
|
398
|
+
// The mouse/trackpad answer to "I want to type an exact number": a real input in
|
|
399
|
+
// place of the scrub box, focused and selected so typing replaces the value.
|
|
400
|
+
// Enter/blur commits, Esc reverts. The numpad sheet exists because a phone has
|
|
401
|
+
// no minus key and we don't want the native keyboard covering the field — with a
|
|
402
|
+
// physical keyboard neither is a problem, so a fine pointer never sees it.
|
|
403
|
+
function NumberInlineInput({ value, min, max, step, onCommit, onDone }) {
|
|
404
|
+
const [draft, setDraft] = useState(() => numberText(value));
|
|
405
|
+
const cancelRef = useRef(false);
|
|
406
|
+
function finish(raw) {
|
|
407
|
+
if (cancelRef.current) return onDone();
|
|
408
|
+
const next = Number(raw);
|
|
409
|
+
onCommit(Number.isFinite(next) ? next : value);
|
|
410
|
+
onDone();
|
|
411
|
+
}
|
|
412
|
+
return (
|
|
413
|
+
<div className={styles.numberField}>
|
|
414
|
+
<input
|
|
415
|
+
className={cx(styles.input, styles.numberInput)}
|
|
416
|
+
type="number"
|
|
417
|
+
min={min}
|
|
418
|
+
max={max}
|
|
419
|
+
step={step}
|
|
420
|
+
value={draft}
|
|
421
|
+
autoFocus
|
|
422
|
+
onFocus={(event) => event.currentTarget.select()}
|
|
423
|
+
onChange={(event) => setDraft(event.target.value)}
|
|
424
|
+
onBlur={(event) => finish(event.target.value)}
|
|
425
|
+
onKeyDown={(event) => {
|
|
426
|
+
if (event.key === 'Enter') {
|
|
427
|
+
event.preventDefault();
|
|
428
|
+
event.currentTarget.blur();
|
|
429
|
+
} else if (event.key === 'Escape') {
|
|
430
|
+
event.preventDefault();
|
|
431
|
+
cancelRef.current = true;
|
|
432
|
+
event.currentTarget.blur();
|
|
433
|
+
}
|
|
434
|
+
}}
|
|
435
|
+
/>
|
|
436
|
+
</div>
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
381
440
|
export function NumberField({ label, value, onChange, min, max, step = 1, overridden, defaultValue, onReset }) {
|
|
382
441
|
const current = Number.isFinite(value) ? (value ?? 0) : 0;
|
|
383
442
|
// A field with BOTH a min and max is a true slider (fill + thumb), the pointer's X
|
|
384
443
|
// mapped onto [min,max]. Everything else is a horizontal scrubber that can pass
|
|
385
444
|
// into negatives, marked with a grip so it reads as draggable.
|
|
386
445
|
const bounded = min != null && max != null && max > min;
|
|
446
|
+
// Drag-to-scrub is for every pointer, but what a plain CLICK/TAP opens differs:
|
|
447
|
+
// a finger gets the numpad sheet, a mouse gets an inline text input (see
|
|
448
|
+
// NumberInlineInput). Both are the "type an exact value" path for their device.
|
|
449
|
+
const coarse = useCoarsePointer();
|
|
387
450
|
const [padOpen, setPadOpen] = useState(false);
|
|
451
|
+
const [typing, setTyping] = useState(false);
|
|
388
452
|
// While scrubbing, a value bubble floats above the finger (which occludes the
|
|
389
453
|
// field itself) — { x, y } client coords, or null when not scrubbing.
|
|
390
454
|
const [scrubPos, setScrubPos] = useState(null);
|
|
@@ -401,7 +465,7 @@ export function NumberField({ label, value, onChange, min, max, step = 1, overri
|
|
|
401
465
|
if (v !== current) onChange(v);
|
|
402
466
|
}
|
|
403
467
|
function onPointerDown(event) {
|
|
404
|
-
if (padOpen) return;
|
|
468
|
+
if (padOpen || typing) return;
|
|
405
469
|
if (event.pointerType === 'mouse' && event.button !== 0) return;
|
|
406
470
|
// Don't capture or preventDefault yet: `touch-action: pan-y` lets the browser
|
|
407
471
|
// own a VERTICAL drag (scroll the field list); we only claim a HORIZONTAL one.
|
|
@@ -447,7 +511,8 @@ export function NumberField({ label, value, onChange, min, max, step = 1, overri
|
|
|
447
511
|
const wasTap = drag.mode === 'pending';
|
|
448
512
|
dragRef.current = null;
|
|
449
513
|
setScrubPos(null);
|
|
450
|
-
|
|
514
|
+
// A plain tap/click opens the device-appropriate way to type a value.
|
|
515
|
+
if (wasTap) (coarse ? setPadOpen : setTyping)(true);
|
|
451
516
|
}
|
|
452
517
|
// A pointercancel = the browser took the gesture (a vertical scroll under pan-y).
|
|
453
518
|
// Just drop the drag; it must NOT be treated as a tap.
|
|
@@ -456,6 +521,24 @@ export function NumberField({ label, value, onChange, min, max, step = 1, overri
|
|
|
456
521
|
setScrubPos(null);
|
|
457
522
|
}
|
|
458
523
|
const fillPct = bounded ? Math.min(100, Math.max(0, ((current - min) / (max - min)) * 100)) : 0;
|
|
524
|
+
const commit = (v) => {
|
|
525
|
+
const next = clampVal(v);
|
|
526
|
+
if (next !== current) onChange(next);
|
|
527
|
+
};
|
|
528
|
+
if (typing) {
|
|
529
|
+
return (
|
|
530
|
+
<FieldRow label={label} overridden={overridden} defaultValue={defaultValue} onReset={onReset}>
|
|
531
|
+
<NumberInlineInput
|
|
532
|
+
value={current}
|
|
533
|
+
min={min}
|
|
534
|
+
max={max}
|
|
535
|
+
step={step}
|
|
536
|
+
onCommit={commit}
|
|
537
|
+
onDone={() => setTyping(false)}
|
|
538
|
+
/>
|
|
539
|
+
</FieldRow>
|
|
540
|
+
);
|
|
541
|
+
}
|
|
459
542
|
return (
|
|
460
543
|
<FieldRow label={label} overridden={overridden} defaultValue={defaultValue} onReset={onReset}>
|
|
461
544
|
<div
|
|
@@ -496,9 +579,7 @@ export function NumberField({ label, value, onChange, min, max, step = 1, overri
|
|
|
496
579
|
min={min}
|
|
497
580
|
max={max}
|
|
498
581
|
step={step}
|
|
499
|
-
onCommit={
|
|
500
|
-
if (v !== current) onChange(v);
|
|
501
|
-
}}
|
|
582
|
+
onCommit={commit}
|
|
502
583
|
onClose={() => setPadOpen(false)}
|
|
503
584
|
/>
|
|
504
585
|
) : null}
|
|
@@ -393,6 +393,14 @@
|
|
|
393
393
|
cursor: pointer;
|
|
394
394
|
font-size: 14px;
|
|
395
395
|
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
|
|
396
|
+
/* Drag-to-place is a pointer gesture we implement ourselves (see
|
|
397
|
+
BlueprintLibrary's onSlotPointerDown). On a touch screen the browser claims
|
|
398
|
+
an unqualified gesture for scrolling and fires pointercancel the moment the
|
|
399
|
+
finger moves, so the drag died on contact -- the slot only ever registered
|
|
400
|
+
as a tap. `pan-x` hands the browser exactly the axis the hotbar scrolls on
|
|
401
|
+
and leaves the other to us: swipe sideways to scroll through blueprints,
|
|
402
|
+
drag up toward the stage to place one. */
|
|
403
|
+
touch-action: pan-x;
|
|
396
404
|
}
|
|
397
405
|
|
|
398
406
|
.bpSlotBadge {
|
|
@@ -1302,12 +1310,20 @@
|
|
|
1302
1310
|
padding: 6px 8px 12px;
|
|
1303
1311
|
}
|
|
1304
1312
|
|
|
1313
|
+
/* Label and number are one value line, not a caption plus a value — same size
|
|
1314
|
+
and color. A long label (e.g. "Friction Static") truncates rather than
|
|
1315
|
+
pushing the number off, which is why the number never shrinks. */
|
|
1305
1316
|
.numpadValueLabel {
|
|
1306
|
-
color: var(--castle-inspector-
|
|
1307
|
-
font-size:
|
|
1317
|
+
color: var(--castle-inspector-text);
|
|
1318
|
+
font-size: 22px;
|
|
1319
|
+
min-width: 0;
|
|
1320
|
+
overflow: hidden;
|
|
1321
|
+
text-overflow: ellipsis;
|
|
1322
|
+
white-space: nowrap;
|
|
1308
1323
|
}
|
|
1309
1324
|
|
|
1310
1325
|
.numpadValueNum {
|
|
1326
|
+
flex: 0 0 auto;
|
|
1311
1327
|
color: var(--castle-inspector-text);
|
|
1312
1328
|
font-size: 22px;
|
|
1313
1329
|
font-variant-numeric: tabular-nums;
|