@xenosystem/blocks 0.11.5 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/edit/index.d.ts +4 -2
- package/dist/edit/index.js +329 -430
- package/dist/time/index.d.ts +157 -4
- package/dist/time/index.js +246 -84
- package/package.json +5 -5
package/dist/time/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PanelHost, PanelModule, PanelManifest } from '@xenosystem/block-sdk';
|
|
1
|
+
import { PanelHost, PanelModule, PanelManifest, BlockDeclaration } from '@xenosystem/block-sdk';
|
|
2
2
|
import { ThrottleClock, XenoPlayheadClock, XenoTimebase, XenoTransportState, XenoTick, XenoStepUnit, XenoLoopRange } from '@xenosystem/time-core';
|
|
3
3
|
export { BAR_SUBDIVISION, BarsBeats, DEFAULT_TIMEBASE, DISPLAY_CYCLE, ExtrapolateInput, IDLE_CLOCK, ThrottleClock, Throttled, XenoLoopRange, XenoPlayheadClock, XenoStepUnit, XenoTempoEntry, XenoTick, XenoTimeDisplay, XenoTimeSignature, XenoTimebase, XenoTransportIntent, XenoTransportState, clampTick, clockAt, createThrottle, displayLabel, effectiveFps, extrapolateTick, formatBars, formatFrames, formatSeconds, formatTick, formatTimecode, frameToTick, isAnimating, nextDisplay, normalizeClock, normalizeTimebase, secondsToTicks, tickToFrame, ticksToBarsBeats, ticksToSeconds, toTick, wrapIntoLoop } from '@xenosystem/time-core';
|
|
4
4
|
import { ReactElement, ReactNode } from 'react';
|
|
@@ -239,8 +239,9 @@ declare const transportManifest: PanelManifest;
|
|
|
239
239
|
* transport is not moving. That is the whole 60fps-without-streaming trick — no per-frame IPC, no
|
|
240
240
|
* idle wakeups.
|
|
241
241
|
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
242
|
+
* It draws the same model as `TRANSPORT_DECLARATION` (`transportPanelView(transportViewState(c))`);
|
|
243
|
+
* the declaration test holds the two byte-identical. The scrub gesture arrives from the Slider as a
|
|
244
|
+
* FRACTION and is mapped to a tick by the controller, exactly as the declaration's `ui` intent is.
|
|
244
245
|
*
|
|
245
246
|
* @module
|
|
246
247
|
*/
|
|
@@ -251,6 +252,158 @@ interface TransportPanelViewProps {
|
|
|
251
252
|
}
|
|
252
253
|
declare function TransportPanelView({ controller }: TransportPanelViewProps): ReactElement;
|
|
253
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Inline transport glyphs at lucide geometry, **strokeWidth 1.5** (DESIGN_SYSTEM §6). Zero runtime
|
|
257
|
+
* dependencies — no `lucide-react` import. Shapes are rectangles and triangles only; the record
|
|
258
|
+
* glyph is a rounded SQUARE, never a circle (§3 "no circles anywhere").
|
|
259
|
+
*
|
|
260
|
+
* @module
|
|
261
|
+
*/
|
|
262
|
+
|
|
263
|
+
interface IconProps {
|
|
264
|
+
size?: number;
|
|
265
|
+
}
|
|
266
|
+
/** lucide `play` (filled for weight at small sizes). */
|
|
267
|
+
declare function PlayIcon({ size }: IconProps): ReactElement;
|
|
268
|
+
/** lucide `pause`. */
|
|
269
|
+
declare function PauseIcon({ size }: IconProps): ReactElement;
|
|
270
|
+
/** lucide `square` — stop. */
|
|
271
|
+
declare function StopIcon({ size }: IconProps): ReactElement;
|
|
272
|
+
/** Record — a rounded SQUARE (the XENO shape language has no circles). */
|
|
273
|
+
declare function RecordIcon({ size }: IconProps): ReactElement;
|
|
274
|
+
/** lucide `skip-back` — go to start. */
|
|
275
|
+
declare function StartIcon({ size }: IconProps): ReactElement;
|
|
276
|
+
/** lucide `skip-forward` — go to end. */
|
|
277
|
+
declare function EndIcon({ size }: IconProps): ReactElement;
|
|
278
|
+
/** Step one unit back. */
|
|
279
|
+
declare function StepBackIcon({ size }: IconProps): ReactElement;
|
|
280
|
+
/** Step one unit forward. */
|
|
281
|
+
declare function StepForwardIcon({ size }: IconProps): ReactElement;
|
|
282
|
+
/** lucide `rewind` — shuttle reverse. */
|
|
283
|
+
declare function ShuttleLeftIcon({ size }: IconProps): ReactElement;
|
|
284
|
+
/** lucide `fast-forward` — shuttle forward. */
|
|
285
|
+
declare function ShuttleRightIcon({ size }: IconProps): ReactElement;
|
|
286
|
+
/** lucide `repeat` — loop. */
|
|
287
|
+
declare function LoopIcon({ size }: IconProps): ReactElement;
|
|
288
|
+
/** A metronome pendulum. */
|
|
289
|
+
declare function MetronomeIcon({ size }: IconProps): ReactElement;
|
|
290
|
+
/** Play or pause, decided by a prop — one node in a declaration instead of two switched by `when`. */
|
|
291
|
+
declare function PlayPauseIcon({ playing, size }: {
|
|
292
|
+
playing?: boolean;
|
|
293
|
+
size?: number;
|
|
294
|
+
}): ReactElement;
|
|
295
|
+
/**
|
|
296
|
+
* The glyphs by registry name, for a declaration (`transport/PlayIcon`). Block-local until the
|
|
297
|
+
* elements icon set lands, then these resolve to `icons/*` and this map goes.
|
|
298
|
+
*/
|
|
299
|
+
declare const TRANSPORT_ICONS: {
|
|
300
|
+
'transport/PlayIcon': typeof PlayIcon;
|
|
301
|
+
'transport/PauseIcon': typeof PauseIcon;
|
|
302
|
+
'transport/PlayPauseIcon': typeof PlayPauseIcon;
|
|
303
|
+
'transport/StopIcon': typeof StopIcon;
|
|
304
|
+
'transport/RecordIcon': typeof RecordIcon;
|
|
305
|
+
'transport/StartIcon': typeof StartIcon;
|
|
306
|
+
'transport/EndIcon': typeof EndIcon;
|
|
307
|
+
'transport/StepBackIcon': typeof StepBackIcon;
|
|
308
|
+
'transport/StepForwardIcon': typeof StepForwardIcon;
|
|
309
|
+
'transport/ShuttleLeftIcon': typeof ShuttleLeftIcon;
|
|
310
|
+
'transport/ShuttleRightIcon': typeof ShuttleRightIcon;
|
|
311
|
+
'transport/LoopIcon': typeof LoopIcon;
|
|
312
|
+
'transport/MetronomeIcon': typeof MetronomeIcon;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* The transport strip AS A DECLARATION — the fourth block written the §8d way (auth gate, agent,
|
|
317
|
+
* account, transport), and the first whose runtime is a real-time controller.
|
|
318
|
+
*
|
|
319
|
+
* Presentation contract: input `view` (a {@link TransportViewState} the runtime derives from the
|
|
320
|
+
* controller on every change and every animation frame), output `ui` (a {@link TransportUiIntent}),
|
|
321
|
+
* applied by the runtime to the controller's own methods. The declaration holds no state and does no
|
|
322
|
+
* time maths: the readout, the progress and the ARIA triple arrive already computed, and a scrub is
|
|
323
|
+
* a FRACTION of the track (the slider owns its geometry; `fractionToTick` stays in the controller).
|
|
324
|
+
*
|
|
325
|
+
* The code view (`TransportPanelView`) renders the same model through `transportPanelView`; the
|
|
326
|
+
* test holds the two byte-identical in every state the controller can be driven into.
|
|
327
|
+
*/
|
|
328
|
+
|
|
329
|
+
/** What the runtime pushes on `view` — everything the strip shows, already formatted. */
|
|
330
|
+
interface TransportViewState {
|
|
331
|
+
playing: boolean;
|
|
332
|
+
recording: boolean;
|
|
333
|
+
loopEnabled: boolean;
|
|
334
|
+
metronome: boolean;
|
|
335
|
+
punch: boolean;
|
|
336
|
+
bpm: number;
|
|
337
|
+
show: {
|
|
338
|
+
shuttle: boolean;
|
|
339
|
+
record: boolean;
|
|
340
|
+
loop: boolean;
|
|
341
|
+
metronome: boolean;
|
|
342
|
+
punch: boolean;
|
|
343
|
+
bpm: boolean;
|
|
344
|
+
inOut: boolean;
|
|
345
|
+
scrubber: boolean;
|
|
346
|
+
};
|
|
347
|
+
readout: string;
|
|
348
|
+
displayLabel: string;
|
|
349
|
+
durationReadout: string;
|
|
350
|
+
progress: number;
|
|
351
|
+
duration: number;
|
|
352
|
+
currentTick: number;
|
|
353
|
+
}
|
|
354
|
+
type TransportUiIntent = {
|
|
355
|
+
kind: 'goToStart';
|
|
356
|
+
} | {
|
|
357
|
+
kind: 'stepBack';
|
|
358
|
+
} | {
|
|
359
|
+
kind: 'shuttleBack';
|
|
360
|
+
} | {
|
|
361
|
+
kind: 'togglePlay';
|
|
362
|
+
} | {
|
|
363
|
+
kind: 'stop';
|
|
364
|
+
} | {
|
|
365
|
+
kind: 'shuttleForward';
|
|
366
|
+
} | {
|
|
367
|
+
kind: 'stepForward';
|
|
368
|
+
} | {
|
|
369
|
+
kind: 'goToEnd';
|
|
370
|
+
} | {
|
|
371
|
+
kind: 'record';
|
|
372
|
+
} | {
|
|
373
|
+
kind: 'toggleLoop';
|
|
374
|
+
} | {
|
|
375
|
+
kind: 'toggleMetronome';
|
|
376
|
+
} | {
|
|
377
|
+
kind: 'cycleDisplay';
|
|
378
|
+
} | {
|
|
379
|
+
kind: 'setTempo';
|
|
380
|
+
bpm: number;
|
|
381
|
+
} | {
|
|
382
|
+
kind: 'setIn';
|
|
383
|
+
} | {
|
|
384
|
+
kind: 'setOut';
|
|
385
|
+
} | {
|
|
386
|
+
kind: 'scrub';
|
|
387
|
+
fraction: number;
|
|
388
|
+
phase: 'begin' | 'move' | 'end' | 'cancel';
|
|
389
|
+
};
|
|
390
|
+
/** Derive the view state from the live controller — the runtime calls this on every draw. */
|
|
391
|
+
declare function transportViewState(c: TransportPanelController): TransportViewState;
|
|
392
|
+
/** Apply a `ui` intent to the controller — the runtime's half of the contract. */
|
|
393
|
+
declare function applyTransportUi(c: TransportPanelController, intent: TransportUiIntent): void;
|
|
394
|
+
declare const transportViewManifest: PanelManifest;
|
|
395
|
+
/** The view model both renderers draw from: the state plus every derived label. */
|
|
396
|
+
interface TransportPanelViewModel extends TransportViewState {
|
|
397
|
+
stopped: boolean;
|
|
398
|
+
playLabel: string;
|
|
399
|
+
positionLabel: string;
|
|
400
|
+
}
|
|
401
|
+
declare function transportPanelView(state: TransportViewState): TransportPanelViewModel;
|
|
402
|
+
declare const BEHAVIOURS: {
|
|
403
|
+
'time/transportPanelView': (inputs: Record<string, unknown>) => TransportPanelViewModel;
|
|
404
|
+
};
|
|
405
|
+
declare const TRANSPORT_DECLARATION: BlockDeclaration;
|
|
406
|
+
|
|
254
407
|
/**
|
|
255
408
|
* `XenoTimeScale` — **the only thing in this package that converts between pixels and ticks.**
|
|
256
409
|
*
|
|
@@ -2258,4 +2411,4 @@ interface CalendarPanelViewProps {
|
|
|
2258
2411
|
/** The Calendar panel view. */
|
|
2259
2412
|
declare function CalendarPanelView({ controller, firstDayOfWeek, emptyHint, }: CalendarPanelViewProps): ReactNode;
|
|
2260
2413
|
|
|
2261
|
-
export { ALL_CALENDAR_MODES, type BeginDragOptions, type BuildCellsOptions, CALENDAR_PANEL_ID, CalendarController, type CalendarControllerOptions, type CalendarHostBridge, type CalendarPanelState, CalendarPanelView, type CalendarPanelViewProps, type CalendarRenderContext, type CalendarViewState, type CreateCalendarPanelOptions, type CreateTimelinePanelOptions, type CreateTransportPanelOptions, DEFAULT_SNAP, DEFAULT_ZOOM, DRAG_THRESHOLD_PX, type DeriveOptions, EDGE_ZONE_MIN_CLIP_PX, EDGE_ZONE_PX, MIN_MAJOR_PX, MIN_MINOR_PX, TIMELINE_PANEL_ID, TRIM_SNAP_PX, TimelineController, type TimelineControllerOptions, type TimelineHostBridge, type TimelinePanelState, TimelinePanelView, type TimelinePanelViewProps, type TimelineRenderConfig, type TimelineRenderContext, type TimelineViewState, type TransportKeyEvent, TransportPanelController, type TransportPanelControllerOptions, type TransportPanelSerialized, TransportPanelView, type TransportPanelViewProps, type XenoArrangement, type XenoCachedTile, type XenoCalendarCell, type XenoCalendarEdit, type XenoCalendarEvent, type XenoCalendarSlotActivate, type XenoCalendarViewMode, type XenoCalendarWindow, type XenoClip, type XenoClipFade, type XenoDragKind, type XenoDragModifiers, type XenoDragPreview, type XenoDragState, type XenoHitTarget, type XenoLaneTile, type XenoLaneTileRequest, type XenoMarker, type XenoOverlaySpan, type XenoRenderTier, type XenoRowLayout, type XenoRulerTick, type XenoSnapCandidate, type XenoSnapConfig, type XenoSnapMode, type XenoSnapResult, type XenoTickWeight, XenoTileCache, type XenoTileCacheOptions, XenoTimeScale, type XenoTimeScaleGeometry, type XenoTimeScaleState, type XenoTimelineOp, type XenoTimelineSelection, type XenoTimelineTool, type XenoTimelineViewport, type XenoTrack, type XenoTrackKind, type XenoTrimMode, type XenoVisibleTrack, type XenoZoomBounds, addDays, addMonths, alignToTileGrid, beginDrag, blitTile, buildCells, buildRowLayout, calendarManifest, calendarPanel, chooseRung, clampScroll, clipDuration, clipsInMarquee, clipsOverlap, collectSnapTargets, commitDrag, computeVisible, createCalendarPanel, createTimelinePanel, createTransportPanel, dayKey, daysInMonth, dedupeByColumn, deriveEvents, edgeZone, gridStart, gridTicksFor, hasSourceWindow, isZoomGesture, isoWeekday, packOverlaps, parseDayKey, passedThreshold, previewDrag, renderTier, rowAtY, rowInWindow, rulerTicks, scrollToReveal, snapDelta, snapTick, snapWouldFight, sourceRangeFor, statusTone, ticksPerStep, tileKey, tileWidthTicks, timelineManifest, timelinePanel, todayKey, transportManifest, transportPanel, trimModeFor, visibleClips, visibleRows, weekStart, wheelZoomFactor, withSnapModifier, zoomAboutCursor, zoomBucket, zoomToFit };
|
|
2414
|
+
export { ALL_CALENDAR_MODES, type BeginDragOptions, type BuildCellsOptions, CALENDAR_PANEL_ID, CalendarController, type CalendarControllerOptions, type CalendarHostBridge, type CalendarPanelState, CalendarPanelView, type CalendarPanelViewProps, type CalendarRenderContext, type CalendarViewState, type CreateCalendarPanelOptions, type CreateTimelinePanelOptions, type CreateTransportPanelOptions, DEFAULT_SNAP, DEFAULT_ZOOM, DRAG_THRESHOLD_PX, type DeriveOptions, EDGE_ZONE_MIN_CLIP_PX, EDGE_ZONE_PX, MIN_MAJOR_PX, MIN_MINOR_PX, TIMELINE_PANEL_ID, BEHAVIOURS as TRANSPORT_BEHAVIOURS, TRANSPORT_DECLARATION, TRANSPORT_ICONS, TRIM_SNAP_PX, TimelineController, type TimelineControllerOptions, type TimelineHostBridge, type TimelinePanelState, TimelinePanelView, type TimelinePanelViewProps, type TimelineRenderConfig, type TimelineRenderContext, type TimelineViewState, type TransportKeyEvent, TransportPanelController, type TransportPanelControllerOptions, type TransportPanelSerialized, TransportPanelView, type TransportPanelViewModel, type TransportPanelViewProps, type TransportUiIntent, type TransportViewState, type XenoArrangement, type XenoCachedTile, type XenoCalendarCell, type XenoCalendarEdit, type XenoCalendarEvent, type XenoCalendarSlotActivate, type XenoCalendarViewMode, type XenoCalendarWindow, type XenoClip, type XenoClipFade, type XenoDragKind, type XenoDragModifiers, type XenoDragPreview, type XenoDragState, type XenoHitTarget, type XenoLaneTile, type XenoLaneTileRequest, type XenoMarker, type XenoOverlaySpan, type XenoRenderTier, type XenoRowLayout, type XenoRulerTick, type XenoSnapCandidate, type XenoSnapConfig, type XenoSnapMode, type XenoSnapResult, type XenoTickWeight, XenoTileCache, type XenoTileCacheOptions, XenoTimeScale, type XenoTimeScaleGeometry, type XenoTimeScaleState, type XenoTimelineOp, type XenoTimelineSelection, type XenoTimelineTool, type XenoTimelineViewport, type XenoTrack, type XenoTrackKind, type XenoTrimMode, type XenoVisibleTrack, type XenoZoomBounds, addDays, addMonths, alignToTileGrid, applyTransportUi, beginDrag, blitTile, buildCells, buildRowLayout, calendarManifest, calendarPanel, chooseRung, clampScroll, clipDuration, clipsInMarquee, clipsOverlap, collectSnapTargets, commitDrag, computeVisible, createCalendarPanel, createTimelinePanel, createTransportPanel, dayKey, daysInMonth, dedupeByColumn, deriveEvents, edgeZone, gridStart, gridTicksFor, hasSourceWindow, isZoomGesture, isoWeekday, packOverlaps, parseDayKey, passedThreshold, previewDrag, renderTier, rowAtY, rowInWindow, rulerTicks, scrollToReveal, snapDelta, snapTick, snapWouldFight, sourceRangeFor, statusTone, ticksPerStep, tileKey, tileWidthTicks, timelineManifest, timelinePanel, todayKey, transportManifest, transportPanel, transportPanelView, transportViewManifest, transportViewState, trimModeFor, visibleClips, visibleRows, weekStart, wheelZoomFactor, withSnapModifier, zoomAboutCursor, zoomBucket, zoomToFit };
|
package/dist/time/index.js
CHANGED
|
@@ -782,6 +782,209 @@ function ticksPerStep(unit, timebase) {
|
|
|
782
782
|
import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
|
|
783
783
|
import { Column, IconButton, NumberInput, Readout, Slider, Stack, TextButton } from "@xenosystem/components/chrome";
|
|
784
784
|
|
|
785
|
+
// src/time/transport/declaration.ts
|
|
786
|
+
function transportViewState(c) {
|
|
787
|
+
const state = c.getState();
|
|
788
|
+
return {
|
|
789
|
+
playing: c.isPlaying(),
|
|
790
|
+
recording: !!state.recording,
|
|
791
|
+
loopEnabled: !!state.loopEnabled,
|
|
792
|
+
metronome: !!state.metronome,
|
|
793
|
+
punch: !!state.punch,
|
|
794
|
+
bpm: state.bpm ?? 120,
|
|
795
|
+
show: {
|
|
796
|
+
shuttle: c.showsControl("shuttle"),
|
|
797
|
+
record: c.showsControl("record"),
|
|
798
|
+
loop: c.showsControl("loop"),
|
|
799
|
+
metronome: c.showsControl("metronome"),
|
|
800
|
+
punch: c.showsControl("punch"),
|
|
801
|
+
bpm: c.showsControl("bpm"),
|
|
802
|
+
inOut: c.showsControl("inOut"),
|
|
803
|
+
scrubber: c.showsControl("scrubber")
|
|
804
|
+
},
|
|
805
|
+
readout: c.getReadout(),
|
|
806
|
+
displayLabel: (0, format_exports.displayLabel)(c.getDisplay()),
|
|
807
|
+
durationReadout: c.getDurationReadout(),
|
|
808
|
+
progress: c.getProgress(),
|
|
809
|
+
duration: c.getDuration(),
|
|
810
|
+
currentTick: c.getCurrentTick()
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
function applyTransportUi(c, intent) {
|
|
814
|
+
switch (intent.kind) {
|
|
815
|
+
case "goToStart":
|
|
816
|
+
c.goToStart();
|
|
817
|
+
break;
|
|
818
|
+
case "stepBack":
|
|
819
|
+
c.step(-1);
|
|
820
|
+
break;
|
|
821
|
+
case "shuttleBack":
|
|
822
|
+
c.shuttleStep(-1);
|
|
823
|
+
break;
|
|
824
|
+
case "togglePlay":
|
|
825
|
+
c.togglePlay();
|
|
826
|
+
break;
|
|
827
|
+
case "stop":
|
|
828
|
+
c.stop();
|
|
829
|
+
break;
|
|
830
|
+
case "shuttleForward":
|
|
831
|
+
c.shuttleStep(1);
|
|
832
|
+
break;
|
|
833
|
+
case "stepForward":
|
|
834
|
+
c.step(1);
|
|
835
|
+
break;
|
|
836
|
+
case "goToEnd":
|
|
837
|
+
c.goToEnd();
|
|
838
|
+
break;
|
|
839
|
+
case "record":
|
|
840
|
+
c.record();
|
|
841
|
+
break;
|
|
842
|
+
case "toggleLoop":
|
|
843
|
+
c.toggleLoop();
|
|
844
|
+
break;
|
|
845
|
+
case "toggleMetronome":
|
|
846
|
+
c.toggleMetronome();
|
|
847
|
+
break;
|
|
848
|
+
case "cycleDisplay":
|
|
849
|
+
c.cycleDisplay();
|
|
850
|
+
break;
|
|
851
|
+
case "setTempo":
|
|
852
|
+
c.setTempo(intent.bpm);
|
|
853
|
+
break;
|
|
854
|
+
case "setIn":
|
|
855
|
+
c.setInPoint();
|
|
856
|
+
break;
|
|
857
|
+
case "setOut":
|
|
858
|
+
c.setOutPoint();
|
|
859
|
+
break;
|
|
860
|
+
case "scrub": {
|
|
861
|
+
const tick = c.fractionToTick(intent.fraction);
|
|
862
|
+
if (intent.phase === "begin") c.beginScrub(tick);
|
|
863
|
+
else if (intent.phase === "move") c.updateScrub(tick);
|
|
864
|
+
else if (intent.phase === "end") c.endScrub();
|
|
865
|
+
else c.cancelScrub();
|
|
866
|
+
break;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
var transportViewManifest = {
|
|
871
|
+
id: transportManifest.id,
|
|
872
|
+
version: "1.0.0",
|
|
873
|
+
title: "Transport",
|
|
874
|
+
category: "time",
|
|
875
|
+
layout: { slot: "bottom" },
|
|
876
|
+
inputs: [{ id: "view", type: "object", label: "View state", description: "The TransportViewState the runtime derives from the controller on every draw." }],
|
|
877
|
+
outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, bpm?, fraction?, phase? } \u2014 applied by the runtime to the controller." }],
|
|
878
|
+
commands: []
|
|
879
|
+
};
|
|
880
|
+
function transportPanelView(state) {
|
|
881
|
+
return {
|
|
882
|
+
...state,
|
|
883
|
+
stopped: !state.playing,
|
|
884
|
+
playLabel: state.playing ? "Pause (Space)" : "Play (Space)",
|
|
885
|
+
positionLabel: `Position ${state.readout}`
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
var EMPTY = {
|
|
889
|
+
playing: false,
|
|
890
|
+
recording: false,
|
|
891
|
+
loopEnabled: false,
|
|
892
|
+
metronome: false,
|
|
893
|
+
punch: false,
|
|
894
|
+
bpm: 120,
|
|
895
|
+
show: { shuttle: false, record: false, loop: false, metronome: false, punch: false, bpm: false, inOut: false, scrubber: true },
|
|
896
|
+
readout: "",
|
|
897
|
+
displayLabel: "",
|
|
898
|
+
durationReadout: "",
|
|
899
|
+
progress: 0,
|
|
900
|
+
duration: 0,
|
|
901
|
+
currentTick: 0
|
|
902
|
+
};
|
|
903
|
+
var BEHAVIOURS = {
|
|
904
|
+
"time/transportPanelView": (inputs) => transportPanelView(inputs.view ?? EMPTY)
|
|
905
|
+
};
|
|
906
|
+
var ui = (kind) => ({ emit: "ui", value: { object: { kind: { value: kind } } } });
|
|
907
|
+
var text = (b) => ({ component: "text", props: { text: b } });
|
|
908
|
+
var button = (icon, label, kind, extra = {}) => ({
|
|
909
|
+
component: "chrome/IconButton",
|
|
910
|
+
...extra.when ? { when: extra.when } : {},
|
|
911
|
+
props: { size: { value: "sm" }, icon: { node: { component: `transport/${icon}` } }, label, ...extra.props ?? {} },
|
|
912
|
+
on: { onClick: ui(kind) }
|
|
913
|
+
});
|
|
914
|
+
var TRANSPORT_DECLARATION = {
|
|
915
|
+
format: "xeno.block@1",
|
|
916
|
+
manifest: transportViewManifest,
|
|
917
|
+
behaviour: "time/transportPanelView",
|
|
918
|
+
composition: {
|
|
919
|
+
component: "chrome/Column",
|
|
920
|
+
props: { className: { value: "xeno-transport-panel" }, tabIndex: { value: 0 }, role: { value: "group" }, "aria-label": { value: "Transport" } },
|
|
921
|
+
children: [
|
|
922
|
+
{
|
|
923
|
+
component: "chrome/Stack",
|
|
924
|
+
props: { direction: { value: "row" }, gap: { value: 3 }, pad: { value: "gutter" } },
|
|
925
|
+
children: [
|
|
926
|
+
button("StartIcon", { value: "Go to start" }, "goToStart"),
|
|
927
|
+
button("StepBackIcon", { value: "Step back" }, "stepBack"),
|
|
928
|
+
button("ShuttleLeftIcon", { value: "Shuttle reverse (J)" }, "shuttleBack", { when: { input: "view.show.shuttle" } }),
|
|
929
|
+
{
|
|
930
|
+
component: "chrome/IconButton",
|
|
931
|
+
props: {
|
|
932
|
+
size: { value: "sm" },
|
|
933
|
+
variant: { value: "primary" },
|
|
934
|
+
icon: { node: { component: "transport/PlayPauseIcon", props: { playing: { input: "view.playing" } } } },
|
|
935
|
+
label: { input: "view.playLabel" },
|
|
936
|
+
active: { input: "view.playing" }
|
|
937
|
+
},
|
|
938
|
+
on: { onClick: ui("togglePlay") }
|
|
939
|
+
},
|
|
940
|
+
button("StopIcon", { value: "Stop" }, "stop"),
|
|
941
|
+
button("ShuttleRightIcon", { value: "Shuttle forward (L)" }, "shuttleForward", { when: { input: "view.show.shuttle" } }),
|
|
942
|
+
button("StepForwardIcon", { value: "Step forward" }, "stepForward"),
|
|
943
|
+
button("EndIcon", { value: "Go to end" }, "goToEnd"),
|
|
944
|
+
button("RecordIcon", { value: "Record" }, "record", { when: { input: "view.show.record" }, props: { variant: { value: "danger" }, active: { input: "view.recording" } } }),
|
|
945
|
+
button("LoopIcon", { value: "Loop" }, "toggleLoop", { when: { input: "view.show.loop" }, props: { active: { input: "view.loopEnabled" } } }),
|
|
946
|
+
button("MetronomeIcon", { value: "Metronome" }, "toggleMetronome", { when: { input: "view.show.metronome" }, props: { active: { input: "view.metronome" } } }),
|
|
947
|
+
{ component: "chrome/TextButton", when: { input: "view.show.punch" }, props: { strong: { input: "view.punch" } }, children: [text({ value: "PUNCH" })] },
|
|
948
|
+
{
|
|
949
|
+
component: "chrome/Readout",
|
|
950
|
+
props: { value: { input: "view.readout" }, unit: { input: "view.displayLabel" }, label: { input: "view.positionLabel" }, title: { value: "Click to change time display" } },
|
|
951
|
+
on: { onClick: ui("cycleDisplay") }
|
|
952
|
+
},
|
|
953
|
+
{ component: "chrome/Readout", props: { size: { value: "sm" }, value: { input: "view.durationReadout" } } },
|
|
954
|
+
{
|
|
955
|
+
component: "chrome/NumberInput",
|
|
956
|
+
when: { input: "view.show.bpm" },
|
|
957
|
+
props: { caption: { value: "BPM" }, label: { value: "Tempo" }, value: { input: "view.bpm" } },
|
|
958
|
+
on: { onChange: { emit: "ui", value: { object: { kind: { value: "setTempo" }, bpm: { arg: "" } } } } }
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
component: "chrome/Stack",
|
|
962
|
+
when: { input: "view.show.inOut" },
|
|
963
|
+
props: { direction: { value: "row" }, gap: { value: 2 }, pad: { value: "none" } },
|
|
964
|
+
children: [
|
|
965
|
+
{ component: "chrome/TextButton", on: { onClick: ui("setIn") }, children: [text({ value: "IN" })] },
|
|
966
|
+
{ component: "chrome/TextButton", on: { onClick: ui("setOut") }, children: [text({ value: "OUT" })] }
|
|
967
|
+
]
|
|
968
|
+
}
|
|
969
|
+
]
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
component: "chrome/Slider",
|
|
973
|
+
when: { input: "view.show.scrubber" },
|
|
974
|
+
props: {
|
|
975
|
+
progress: { input: "view.progress" },
|
|
976
|
+
label: { value: "Playhead" },
|
|
977
|
+
valueMin: { value: 0 },
|
|
978
|
+
valueMax: { input: "view.duration" },
|
|
979
|
+
valueNow: { input: "view.currentTick" },
|
|
980
|
+
valueText: { input: "view.readout" }
|
|
981
|
+
},
|
|
982
|
+
on: { onScrub: { emit: "ui", value: { object: { kind: { value: "scrub" }, fraction: { arg: "" }, phase: { arg: "", index: 1 } } } } }
|
|
983
|
+
}
|
|
984
|
+
]
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
|
|
785
988
|
// src/time/transport/react/icons.tsx
|
|
786
989
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
787
990
|
var base = (size) => ({
|
|
@@ -854,6 +1057,24 @@ function MetronomeIcon({ size = 11 }) {
|
|
|
854
1057
|
/* @__PURE__ */ jsx("path", { d: "m12 18 4-11" })
|
|
855
1058
|
] });
|
|
856
1059
|
}
|
|
1060
|
+
function PlayPauseIcon({ playing, size }) {
|
|
1061
|
+
return playing ? /* @__PURE__ */ jsx(PauseIcon, { size }) : /* @__PURE__ */ jsx(PlayIcon, { size });
|
|
1062
|
+
}
|
|
1063
|
+
var TRANSPORT_ICONS = {
|
|
1064
|
+
"transport/PlayIcon": PlayIcon,
|
|
1065
|
+
"transport/PauseIcon": PauseIcon,
|
|
1066
|
+
"transport/PlayPauseIcon": PlayPauseIcon,
|
|
1067
|
+
"transport/StopIcon": StopIcon,
|
|
1068
|
+
"transport/RecordIcon": RecordIcon,
|
|
1069
|
+
"transport/StartIcon": StartIcon,
|
|
1070
|
+
"transport/EndIcon": EndIcon,
|
|
1071
|
+
"transport/StepBackIcon": StepBackIcon,
|
|
1072
|
+
"transport/StepForwardIcon": StepForwardIcon,
|
|
1073
|
+
"transport/ShuttleLeftIcon": ShuttleLeftIcon,
|
|
1074
|
+
"transport/ShuttleRightIcon": ShuttleRightIcon,
|
|
1075
|
+
"transport/LoopIcon": LoopIcon,
|
|
1076
|
+
"transport/MetronomeIcon": MetronomeIcon
|
|
1077
|
+
};
|
|
857
1078
|
|
|
858
1079
|
// src/time/transport/react/TransportPanelView.tsx
|
|
859
1080
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -879,40 +1100,8 @@ function TransportPanelView({ controller }) {
|
|
|
879
1100
|
rafRef.current = null;
|
|
880
1101
|
};
|
|
881
1102
|
}, [controller, animating]);
|
|
882
|
-
const
|
|
883
|
-
const
|
|
884
|
-
const tickFromPointer = useCallback(
|
|
885
|
-
(clientX) => {
|
|
886
|
-
const el = scrubRef.current;
|
|
887
|
-
if (!el) return 0;
|
|
888
|
-
const rect = el.getBoundingClientRect();
|
|
889
|
-
return controller.fractionToTick(rect.width > 0 ? (clientX - rect.left) / rect.width : 0);
|
|
890
|
-
},
|
|
891
|
-
[controller]
|
|
892
|
-
);
|
|
893
|
-
const onScrubDown = useCallback(
|
|
894
|
-
(e) => {
|
|
895
|
-
e.currentTarget.setPointerCapture(e.pointerId);
|
|
896
|
-
controller.beginScrub(tickFromPointer(e.clientX));
|
|
897
|
-
},
|
|
898
|
-
[controller, tickFromPointer]
|
|
899
|
-
);
|
|
900
|
-
const onScrubMove = useCallback(
|
|
901
|
-
(e) => {
|
|
902
|
-
if (controller.isScrubbing()) controller.updateScrub(tickFromPointer(e.clientX));
|
|
903
|
-
},
|
|
904
|
-
[controller, tickFromPointer]
|
|
905
|
-
);
|
|
906
|
-
const onScrubUp = useCallback(
|
|
907
|
-
(e) => {
|
|
908
|
-
try {
|
|
909
|
-
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
910
|
-
} catch {
|
|
911
|
-
}
|
|
912
|
-
controller.endScrub();
|
|
913
|
-
},
|
|
914
|
-
[controller]
|
|
915
|
-
);
|
|
1103
|
+
const view = transportPanelView(transportViewState(controller));
|
|
1104
|
+
const scrub = useCallback((fraction, phase) => applyTransportUi(controller, { kind: "scrub", fraction, phase }), [controller]);
|
|
916
1105
|
const onKeyDown = useCallback(
|
|
917
1106
|
(e) => {
|
|
918
1107
|
if (controller.handleKey({ key: e.key, ctrl: e.ctrlKey, meta: e.metaKey, shift: e.shiftKey }, "down")) {
|
|
@@ -941,59 +1130,25 @@ function TransportPanelView({ controller }) {
|
|
|
941
1130
|
/* @__PURE__ */ jsxs2(Stack, { direction: "row", gap: 3, pad: "gutter", children: [
|
|
942
1131
|
/* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(StartIcon, {}), label: "Go to start", onClick: () => controller.goToStart() }),
|
|
943
1132
|
/* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(StepBackIcon, {}), label: "Step back", onClick: () => controller.step(-1) }),
|
|
944
|
-
|
|
945
|
-
/* @__PURE__ */ jsx2(
|
|
946
|
-
IconButton,
|
|
947
|
-
{
|
|
948
|
-
size: "sm",
|
|
949
|
-
variant: "primary",
|
|
950
|
-
icon: playing ? /* @__PURE__ */ jsx2(PauseIcon, {}) : /* @__PURE__ */ jsx2(PlayIcon, {}),
|
|
951
|
-
label: playing ? "Pause (Space)" : "Play (Space)",
|
|
952
|
-
active: playing,
|
|
953
|
-
onClick: () => controller.togglePlay()
|
|
954
|
-
}
|
|
955
|
-
),
|
|
1133
|
+
view.show.shuttle && /* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(ShuttleLeftIcon, {}), label: "Shuttle reverse (J)", onClick: () => controller.shuttleStep(-1) }),
|
|
1134
|
+
/* @__PURE__ */ jsx2(IconButton, { size: "sm", variant: "primary", icon: /* @__PURE__ */ jsx2(PlayPauseIcon, { playing: view.playing }), label: view.playLabel, active: view.playing, onClick: () => controller.togglePlay() }),
|
|
956
1135
|
/* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(StopIcon, {}), label: "Stop", onClick: () => controller.stop() }),
|
|
957
|
-
|
|
1136
|
+
view.show.shuttle && /* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(ShuttleRightIcon, {}), label: "Shuttle forward (L)", onClick: () => controller.shuttleStep(1) }),
|
|
958
1137
|
/* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(StepForwardIcon, {}), label: "Step forward", onClick: () => controller.step(1) }),
|
|
959
1138
|
/* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(EndIcon, {}), label: "Go to end", onClick: () => controller.goToEnd() }),
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
/* @__PURE__ */ jsx2(
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
unit: (0, format_exports.displayLabel)(controller.getDisplay()),
|
|
969
|
-
label: `Position ${controller.getReadout()}`,
|
|
970
|
-
title: "Click to change time display",
|
|
971
|
-
onClick: () => controller.cycleDisplay()
|
|
972
|
-
}
|
|
973
|
-
),
|
|
974
|
-
/* @__PURE__ */ jsx2(Readout, { size: "sm", value: controller.getDurationReadout() }),
|
|
975
|
-
controller.showsControl("bpm") && /* @__PURE__ */ jsx2(NumberInput, { caption: "BPM", label: "Tempo", value: state.bpm ?? 120, onChange: (bpm) => controller.setTempo(bpm) }),
|
|
976
|
-
controller.showsControl("inOut") && /* @__PURE__ */ jsxs2(Stack, { direction: "row", gap: 2, pad: "none", children: [
|
|
1139
|
+
view.show.record && /* @__PURE__ */ jsx2(IconButton, { size: "sm", variant: "danger", icon: /* @__PURE__ */ jsx2(RecordIcon, {}), label: "Record", active: view.recording, onClick: () => controller.record() }),
|
|
1140
|
+
view.show.loop && /* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(LoopIcon, {}), label: "Loop", active: view.loopEnabled, onClick: () => controller.toggleLoop() }),
|
|
1141
|
+
view.show.metronome && /* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(MetronomeIcon, {}), label: "Metronome", active: view.metronome, onClick: () => controller.toggleMetronome() }),
|
|
1142
|
+
view.show.punch && /* @__PURE__ */ jsx2(TextButton, { strong: view.punch, children: "PUNCH" }),
|
|
1143
|
+
/* @__PURE__ */ jsx2(Readout, { value: view.readout, unit: view.displayLabel, label: view.positionLabel, title: "Click to change time display", onClick: () => controller.cycleDisplay() }),
|
|
1144
|
+
/* @__PURE__ */ jsx2(Readout, { size: "sm", value: view.durationReadout }),
|
|
1145
|
+
view.show.bpm && /* @__PURE__ */ jsx2(NumberInput, { caption: "BPM", label: "Tempo", value: view.bpm, onChange: (bpm) => controller.setTempo(bpm) }),
|
|
1146
|
+
view.show.inOut && /* @__PURE__ */ jsxs2(Stack, { direction: "row", gap: 2, pad: "none", children: [
|
|
977
1147
|
/* @__PURE__ */ jsx2(TextButton, { onClick: () => controller.setInPoint(), children: "IN" }),
|
|
978
1148
|
/* @__PURE__ */ jsx2(TextButton, { onClick: () => controller.setOutPoint(), children: "OUT" })
|
|
979
1149
|
] })
|
|
980
1150
|
] }),
|
|
981
|
-
|
|
982
|
-
Slider,
|
|
983
|
-
{
|
|
984
|
-
trackRef: scrubRef,
|
|
985
|
-
progress: controller.getProgress(),
|
|
986
|
-
label: "Playhead",
|
|
987
|
-
valueMin: 0,
|
|
988
|
-
valueMax: controller.getDuration(),
|
|
989
|
-
valueNow: controller.getCurrentTick(),
|
|
990
|
-
valueText: controller.getReadout(),
|
|
991
|
-
onPointerDown: onScrubDown,
|
|
992
|
-
onPointerMove: onScrubMove,
|
|
993
|
-
onPointerUp: onScrubUp,
|
|
994
|
-
onPointerCancel: () => controller.cancelScrub()
|
|
995
|
-
}
|
|
996
|
-
)
|
|
1151
|
+
view.show.scrubber && /* @__PURE__ */ jsx2(Slider, { progress: view.progress, label: "Playhead", valueMin: 0, valueMax: view.duration, valueNow: view.currentTick, valueText: view.readout, onScrub: scrub })
|
|
997
1152
|
]
|
|
998
1153
|
}
|
|
999
1154
|
);
|
|
@@ -3338,10 +3493,10 @@ function gridStart(monthKey, firstDayOfWeek) {
|
|
|
3338
3493
|
return addDays(first, -offset);
|
|
3339
3494
|
}
|
|
3340
3495
|
function statusTone(value) {
|
|
3341
|
-
const
|
|
3342
|
-
if (/done|complete|published|paid|ok/.test(
|
|
3343
|
-
if (/pending|draft|scheduled|review/.test(
|
|
3344
|
-
if (/fail|error|cancel|reject|overdue/.test(
|
|
3496
|
+
const text2 = String(value ?? "").toLowerCase();
|
|
3497
|
+
if (/done|complete|published|paid|ok/.test(text2)) return "success";
|
|
3498
|
+
if (/pending|draft|scheduled|review/.test(text2)) return "warning";
|
|
3499
|
+
if (/fail|error|cancel|reject|overdue/.test(text2)) return "error";
|
|
3345
3500
|
return "neutral";
|
|
3346
3501
|
}
|
|
3347
3502
|
function columnMap(result) {
|
|
@@ -4403,6 +4558,9 @@ export {
|
|
|
4403
4558
|
MIN_MAJOR_PX,
|
|
4404
4559
|
MIN_MINOR_PX,
|
|
4405
4560
|
TIMELINE_PANEL_ID,
|
|
4561
|
+
BEHAVIOURS as TRANSPORT_BEHAVIOURS,
|
|
4562
|
+
TRANSPORT_DECLARATION,
|
|
4563
|
+
TRANSPORT_ICONS,
|
|
4406
4564
|
TRIM_SNAP_PX,
|
|
4407
4565
|
TimelineController,
|
|
4408
4566
|
TimelinePanelView,
|
|
@@ -4413,6 +4571,7 @@ export {
|
|
|
4413
4571
|
addDays,
|
|
4414
4572
|
addMonths,
|
|
4415
4573
|
alignToTileGrid,
|
|
4574
|
+
applyTransportUi,
|
|
4416
4575
|
beginDrag,
|
|
4417
4576
|
blitTile,
|
|
4418
4577
|
buildCells,
|
|
@@ -4483,6 +4642,9 @@ export {
|
|
|
4483
4642
|
todayKey,
|
|
4484
4643
|
transportManifest,
|
|
4485
4644
|
transportPanel,
|
|
4645
|
+
transportPanelView,
|
|
4646
|
+
transportViewManifest,
|
|
4647
|
+
transportViewState,
|
|
4486
4648
|
trimModeFor,
|
|
4487
4649
|
visibleClips,
|
|
4488
4650
|
visibleRows,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xenosystem/blocks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "XENO Blocks — rung 3 of the front ladder: components composed into a contract-bearing unit (manifest: ports, intents, commands, agent surface), rendered by @xenosystem/workbench. ONE package, one subpath per family (/auth, /edit, /data, /time, /ops, /trust, /agent), one named export per block — XENO PACKAGE NAMING - STANDARD.md.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@duckdb/duckdb-wasm": ">=1.28.0",
|
|
62
|
-
"@xenosystem/block-sdk": "
|
|
63
|
-
"@xenosystem/components": ">=0.
|
|
62
|
+
"@xenosystem/block-sdk": ">=0.3.2 <1",
|
|
63
|
+
"@xenosystem/components": ">=0.16.0 <1",
|
|
64
64
|
"@xenosystem/data-core": "^0.1.0",
|
|
65
65
|
"@xenosystem/elements-react": ">=0.0.1",
|
|
66
66
|
"@xenosystem/time-core": "^0.1.0",
|
|
@@ -75,8 +75,8 @@
|
|
|
75
75
|
"@types/node": "^22.0.0",
|
|
76
76
|
"@types/react": "^19.0.0",
|
|
77
77
|
"@types/react-dom": "^19.0.0",
|
|
78
|
-
"@xenosystem/block-sdk": "^0.3.
|
|
79
|
-
"@xenosystem/components": "^0.
|
|
78
|
+
"@xenosystem/block-sdk": "^0.3.2",
|
|
79
|
+
"@xenosystem/components": "^0.16.0",
|
|
80
80
|
"@xenosystem/data-core": "^0.1.0",
|
|
81
81
|
"@xenosystem/elements-react": "0.0.1",
|
|
82
82
|
"@xenosystem/time-core": "^0.1.0",
|