@bendyline/squisq-react 2.0.1 → 2.2.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/LICENSE +21 -0
- package/NOTICE.md +20 -0
- package/README.md +6 -0
- package/dist/index.d.ts +101 -96
- package/dist/index.js +1483 -782
- package/dist/squisq-player.full.global.js +4165 -0
- package/dist/squisq-player.global.js +561 -168
- package/dist/standalone-source.d.ts +1 -0
- package/dist/standalone-source.js +1 -1
- package/dist/styles/index.css +64 -0
- package/package.json +8 -4
- package/dist/index.js.map +0 -1
- package/dist/squisq-player.css +0 -2
- package/dist/squisq-player.css.map +0 -1
- package/dist/squisq-player.global.js.map +0 -1
- package/src/BlockRenderer.tsx +0 -194
- package/src/CaptionOverlay.tsx +0 -111
- package/src/DocControlsBottom.tsx +0 -109
- package/src/DocControlsOverlay.tsx +0 -190
- package/src/DocControlsSidebar.tsx +0 -113
- package/src/DocControlsSlideshow.tsx +0 -360
- package/src/DocPlayer.tsx +0 -1528
- package/src/DocPlayerWithSidebar.tsx +0 -155
- package/src/DocProgressBar.tsx +0 -257
- package/src/InlineAudioPlayer.tsx +0 -46
- package/src/InlineVideoPlayer.tsx +0 -70
- package/src/LinearDocView.tsx +0 -569
- package/src/MarkdownRenderer.tsx +0 -624
- package/src/MediaClipLayer.tsx +0 -145
- package/src/SocialCaptionOverlay.tsx +0 -255
- package/src/__tests__/BlockRenderer.test.tsx +0 -235
- package/src/__tests__/DocControlsSlideshow.test.tsx +0 -221
- package/src/__tests__/DocPlayer.test.tsx +0 -635
- package/src/__tests__/DocPlayerStylesSentinel.test.tsx +0 -41
- package/src/__tests__/DocProgressBar.test.tsx +0 -102
- package/src/__tests__/JsonView.test.tsx +0 -111
- package/src/__tests__/LinearDocView.test.tsx +0 -426
- package/src/__tests__/MapLayer.test.tsx +0 -63
- package/src/__tests__/MarkdownRenderer.test.tsx +0 -357
- package/src/__tests__/MediaClipLayer.test.tsx +0 -70
- package/src/__tests__/MediaContext.test.tsx +0 -51
- package/src/__tests__/PathLayer.test.tsx +0 -84
- package/src/__tests__/TableLayer.test.tsx +0 -142
- package/src/__tests__/VideoLayer.test.tsx +0 -94
- package/src/__tests__/exports.test.ts +0 -55
- package/src/__tests__/fillStyle.test.tsx +0 -160
- package/src/__tests__/standaloneEntry.test.tsx +0 -103
- package/src/__tests__/transitionStyles.test.ts +0 -125
- package/src/__tests__/useAudioSync.test.ts +0 -49
- package/src/__tests__/useDocPlayback.transition.test.ts +0 -113
- package/src/__tests__/useJsonViewTokens.test.ts +0 -41
- package/src/__tests__/useSlideSwipe.test.ts +0 -81
- package/src/__tests__/useViewportOrientation.test.ts +0 -22
- package/src/hooks/AudioController.ts +0 -114
- package/src/hooks/MediaContext.tsx +0 -97
- package/src/hooks/index.ts +0 -11
- package/src/hooks/useAudioSync.ts +0 -456
- package/src/hooks/useAutoSurface.ts +0 -33
- package/src/hooks/useDocPlayback.ts +0 -265
- package/src/hooks/useMediaSchedule.ts +0 -39
- package/src/hooks/useSlideSwipe.ts +0 -265
- package/src/hooks/useViewportOrientation.ts +0 -115
- package/src/index.ts +0 -70
- package/src/jsonView/JsonView.tsx +0 -51
- package/src/jsonView/RenderNode.tsx +0 -51
- package/src/jsonView/index.ts +0 -2
- package/src/jsonView/json-view.css +0 -206
- package/src/jsonView/useJsonViewTokens.ts +0 -32
- package/src/jsonView/viewers.tsx +0 -343
- package/src/layers/ImageLayer.tsx +0 -297
- package/src/layers/MapLayer.tsx +0 -185
- package/src/layers/PathLayer.tsx +0 -155
- package/src/layers/ShapeLayer.tsx +0 -158
- package/src/layers/TableLayer.tsx +0 -129
- package/src/layers/TextLayer.tsx +0 -571
- package/src/layers/TreeLayer.tsx +0 -167
- package/src/layers/VideoLayer.tsx +0 -171
- package/src/layers/index.ts +0 -6
- package/src/standalone-entry.tsx +0 -337
- package/src/standalone-source.d.ts +0 -10
- package/src/styles/doc-animations.css +0 -2431
- package/src/styles/index.css +0 -7
- package/src/types.ts +0 -183
- package/src/utils/animationUtils.ts +0 -13
- package/src/utils/fillStyle.tsx +0 -148
- package/src/utils/layerUtils.ts +0 -42
- package/src/utils/mapTileUtils.ts +0 -375
package/src/styles/index.css
DELETED
package/src/types.ts
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Doc Player Control Types
|
|
3
|
-
*
|
|
4
|
-
* Shared type definitions for doc player controls across different
|
|
5
|
-
* layout modes (overlay, sidebar, bottom). These interfaces allow
|
|
6
|
-
* control components to be decoupled from the core DocPlayer.
|
|
7
|
-
*
|
|
8
|
-
* Related Files:
|
|
9
|
-
* - DocPlayer.tsx — Core player component
|
|
10
|
-
* - DocProgressBar.tsx — Extracted progress bar
|
|
11
|
-
* - DocControlsOverlay.tsx — Overlay controls (default)
|
|
12
|
-
* - DocControlsSidebar.tsx — Vertical sidebar controls
|
|
13
|
-
* - DocControlsBottom.tsx — Horizontal bottom strip controls
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import type { Block } from '@bendyline/squisq/schemas';
|
|
17
|
-
|
|
18
|
-
/** Layout mode for doc player controls */
|
|
19
|
-
export type ControlsLayout = 'overlay' | 'sidebar' | 'bottom';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Display mode for DocPlayer.
|
|
23
|
-
*
|
|
24
|
-
* - `'video'` — Traditional video-style playback with play/pause, scrub bar,
|
|
25
|
-
* and time-based auto-advance. Default mode.
|
|
26
|
-
* - `'slideshow'` — PowerPoint-style presentation with prev/next navigation.
|
|
27
|
-
* Blocks are treated as static slides that only change on user click.
|
|
28
|
-
* No auto-advance, no scrub bar.
|
|
29
|
-
* - `'linear'` — Long-scrolling document view. Renders the full markdown as
|
|
30
|
-
* readable HTML with template-annotated sections shown as inline SVG cards.
|
|
31
|
-
* No audio, no timeline, no controls — just a scrollable page.
|
|
32
|
-
* - `'page'` — Plain semantic HTML preview matching what the
|
|
33
|
-
* `markdownDocToPlainHtml` export produces. No SquisqPlayer, no SVG
|
|
34
|
-
* cards — just `<h1>`/`<p>`/`<ul>` etc. inside a sandboxed iframe.
|
|
35
|
-
* Use when you want a WYSIWYG view of the simple HTML export.
|
|
36
|
-
* - `'narrate'` — Teleprompter/performance surface. DocPlayer does not
|
|
37
|
-
* implement this mode; it is owned by the editor package
|
|
38
|
-
* (`@bendyline/squisq-editor-react`), which renders its own
|
|
39
|
-
* voice-paced teleprompter view for it.
|
|
40
|
-
*/
|
|
41
|
-
export type DisplayMode = 'video' | 'slideshow' | 'linear' | 'page' | 'narrate';
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Caption display style.
|
|
45
|
-
*
|
|
46
|
-
* - `'standard'` — Traditional broadcast-style captions: small white text
|
|
47
|
-
* on a semi-transparent black badge at the top of the player.
|
|
48
|
-
* - `'social'` — Social media-style (Instagram/TikTok): large centered words
|
|
49
|
-
* showing 3-5 words at a time with the active word highlighted in the
|
|
50
|
-
* theme's primary color. Font and colors pulled from the active theme.
|
|
51
|
-
*/
|
|
52
|
-
export type CaptionStyle = 'standard' | 'social';
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Caption display mode — combines enable/disable with style selection.
|
|
56
|
-
* The CC button cycles through: off → standard → social → off.
|
|
57
|
-
*/
|
|
58
|
-
export type CaptionMode = 'off' | 'standard' | 'social';
|
|
59
|
-
|
|
60
|
-
/** Slide navigation actions for slideshow display mode */
|
|
61
|
-
export interface SlideNavActions {
|
|
62
|
-
/** Navigate to the next slide */
|
|
63
|
-
nextSlide: () => void;
|
|
64
|
-
/** Navigate to the previous slide */
|
|
65
|
-
prevSlide: () => void;
|
|
66
|
-
/** Navigate to a specific slide by index (0-based) */
|
|
67
|
-
goToSlide: (index: number) => void;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Playback state exposed to external control components */
|
|
71
|
-
export interface PlaybackState {
|
|
72
|
-
isPlaying: boolean;
|
|
73
|
-
currentTime: number;
|
|
74
|
-
totalDuration: number;
|
|
75
|
-
/** Whether the managed cover is the visual currently shown. */
|
|
76
|
-
isCoverVisible?: boolean;
|
|
77
|
-
currentBlockIndex: number;
|
|
78
|
-
totalBlocks: number;
|
|
79
|
-
docProgress: number;
|
|
80
|
-
hasCaptions: boolean;
|
|
81
|
-
captionsEnabled: boolean;
|
|
82
|
-
/** Current caption display mode (off, standard, social). */
|
|
83
|
-
captionMode: CaptionMode;
|
|
84
|
-
isFullscreen?: boolean;
|
|
85
|
-
/** Current audio segment index (0-based) */
|
|
86
|
-
currentSegmentIndex: number;
|
|
87
|
-
/** Current audio segment name (e.g., 'intro', 'history') */
|
|
88
|
-
currentSegmentName: string | null;
|
|
89
|
-
/** Current block data (for extracting image info, etc.) */
|
|
90
|
-
currentBlock: Block | null;
|
|
91
|
-
/** Optional display label for non-block slides, e.g. the managed cover. */
|
|
92
|
-
currentSlideLabel?: string;
|
|
93
|
-
/** Optional human-facing slide number, separate from internal nav index. */
|
|
94
|
-
currentSlideNumber?: number;
|
|
95
|
-
/** Optional human-facing slide total, separate from internal nav total. */
|
|
96
|
-
totalSlideNumber?: number;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/** Playback actions exposed to external control components */
|
|
100
|
-
export interface PlaybackActions {
|
|
101
|
-
toggle: () => void;
|
|
102
|
-
restart: () => void;
|
|
103
|
-
seekTo: (time: number) => void;
|
|
104
|
-
setCaptionsEnabled: (enabled: boolean) => void;
|
|
105
|
-
/** Cycle caption mode: off → standard → social → off */
|
|
106
|
-
cycleCaptionMode: () => void;
|
|
107
|
-
toggleFullscreen?: () => void;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** Block marker data for the progress bar */
|
|
111
|
-
export interface BlockMarker {
|
|
112
|
-
block: Block;
|
|
113
|
-
index: number;
|
|
114
|
-
position: number;
|
|
115
|
-
title: string;
|
|
116
|
-
/** True if this block is the first in a new audio segment (section boundary) */
|
|
117
|
-
isSectionStart: boolean;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ============================================
|
|
121
|
-
// Render-mode API (exposed on window for Playwright / debug)
|
|
122
|
-
// ============================================
|
|
123
|
-
|
|
124
|
-
/** Block metadata returned by SquisqRenderAPI.getBlocks() */
|
|
125
|
-
export interface RenderBlockInfo {
|
|
126
|
-
id: string;
|
|
127
|
-
template: string;
|
|
128
|
-
startTime: number;
|
|
129
|
-
duration: number;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** Audio segment info returned by SquisqRenderAPI.getAudioSegments() */
|
|
133
|
-
export interface RenderAudioSegmentInfo {
|
|
134
|
-
src: string;
|
|
135
|
-
name: string;
|
|
136
|
-
duration: number;
|
|
137
|
-
startTime: number;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** Caption phrase info returned by SquisqRenderAPI.getCaptions() */
|
|
141
|
-
export interface RenderCaptionInfo {
|
|
142
|
-
text: string;
|
|
143
|
-
startTime: number;
|
|
144
|
-
endTime: number;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/** Chapter marker returned by SquisqRenderAPI.getChapters() */
|
|
148
|
-
export interface RenderChapterInfo {
|
|
149
|
-
title: string;
|
|
150
|
-
startTime: number;
|
|
151
|
-
duration: number;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Instance-scoped API created in render mode and debug mode.
|
|
156
|
-
* React hosts receive it via `DocPlayer.onRenderAPIReady`; standalone hosts
|
|
157
|
-
* receive it from their mount handle.
|
|
158
|
-
*
|
|
159
|
-
* @example
|
|
160
|
-
* ```ts
|
|
161
|
-
* const handle = SquisqPlayer.getHandle(rootElement);
|
|
162
|
-
* const api = await handle?.renderAPI;
|
|
163
|
-
* await api?.seekTo(5.0);
|
|
164
|
-
* ```
|
|
165
|
-
*/
|
|
166
|
-
export interface SquisqRenderAPI {
|
|
167
|
-
seekTo: (time: number) => Promise<void>;
|
|
168
|
-
getDuration: () => number;
|
|
169
|
-
getBlocks: () => RenderBlockInfo[];
|
|
170
|
-
getAudioSegments: () => RenderAudioSegmentInfo[];
|
|
171
|
-
getCaptions: () => RenderCaptionInfo[];
|
|
172
|
-
getChapters: () => RenderChapterInfo[];
|
|
173
|
-
showCover: () => Promise<void>;
|
|
174
|
-
hideCover: () => Promise<void>;
|
|
175
|
-
hasCoverBlock: () => boolean;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/** Format time in seconds to MM:SS string */
|
|
179
|
-
export function formatTime(seconds: number): string {
|
|
180
|
-
const mins = Math.floor(seconds / 60);
|
|
181
|
-
const secs = Math.floor(seconds % 60);
|
|
182
|
-
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
183
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Animation Utilities (React)
|
|
3
|
-
*
|
|
4
|
-
* Re-exports the shared animation utilities for use in the React package.
|
|
5
|
-
* All animation logic is in shared/doc/utils.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export {
|
|
9
|
-
getAnimationStyle,
|
|
10
|
-
getDefaultAnimationDuration,
|
|
11
|
-
getTransitionClass,
|
|
12
|
-
getAnimationProgress,
|
|
13
|
-
} from '@bendyline/squisq/doc';
|
package/src/utils/fillStyle.tsx
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared fill / border helpers for the SVG layer renderers (shape, path,
|
|
3
|
-
* and the text-box background). Keeping the gradient `<defs>` markup and
|
|
4
|
-
* the dash-pattern math in one place means rect, circle, path, and text
|
|
5
|
-
* all paint fills and borders identically.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
LinearGradient,
|
|
10
|
-
BorderStyle,
|
|
11
|
-
ShapePattern,
|
|
12
|
-
ShapeFilter,
|
|
13
|
-
} from '@bendyline/squisq/schemas';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* SVG `stroke-dasharray` for a border style, scaled by stroke width so the
|
|
17
|
-
* pattern stays proportional. `solid`/undefined → no dashes.
|
|
18
|
-
*/
|
|
19
|
-
export function borderDashArray(
|
|
20
|
-
style: BorderStyle | undefined,
|
|
21
|
-
strokeWidth: number | undefined,
|
|
22
|
-
): string | undefined {
|
|
23
|
-
if (!style || style === 'solid') return undefined;
|
|
24
|
-
const w = Math.max(1, strokeWidth ?? 1);
|
|
25
|
-
if (style === 'dotted') return `${w} ${w * 2}`;
|
|
26
|
-
return `${w * 3} ${w * 2}`; // dashed
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Gradient line endpoints in objectBoundingBox units (0–1) for an angle in
|
|
31
|
-
* degrees: 0 = top→bottom, 90 = left→right, increasing clockwise.
|
|
32
|
-
*/
|
|
33
|
-
function gradientVector(angle = 0): { x1: number; y1: number; x2: number; y2: number } {
|
|
34
|
-
const a = (angle * Math.PI) / 180;
|
|
35
|
-
const dx = Math.sin(a);
|
|
36
|
-
const dy = Math.cos(a);
|
|
37
|
-
return { x1: 0.5 - dx / 2, y1: 0.5 - dy / 2, x2: 0.5 + dx / 2, y2: 0.5 + dy / 2 };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Stable id for a layer's gradient def. */
|
|
41
|
-
export function gradientDefId(layerId: string): string {
|
|
42
|
-
return `squisq-grad-${layerId}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Resolve a fill to an SVG `fill` value plus the gradient `<defs>` to
|
|
47
|
-
* render (or null). When a gradient is present it wins over the solid
|
|
48
|
-
* color; otherwise the color (or undefined) is returned verbatim.
|
|
49
|
-
*/
|
|
50
|
-
export function resolveFill(
|
|
51
|
-
layerId: string,
|
|
52
|
-
color: string | undefined,
|
|
53
|
-
gradient: LinearGradient | undefined,
|
|
54
|
-
pattern?: ShapePattern,
|
|
55
|
-
): { fill: string | undefined; def: JSX.Element | null } {
|
|
56
|
-
if (pattern) {
|
|
57
|
-
const id = `squisq-pattern-${layerId}`;
|
|
58
|
-
return { fill: `url(#${id})`, def: patternDef(id, pattern) };
|
|
59
|
-
}
|
|
60
|
-
if (gradient) {
|
|
61
|
-
const id = gradientDefId(layerId);
|
|
62
|
-
const v = gradientVector(gradient.angle);
|
|
63
|
-
return {
|
|
64
|
-
fill: `url(#${id})`,
|
|
65
|
-
def: (
|
|
66
|
-
<linearGradient id={id} x1={v.x1} y1={v.y1} x2={v.x2} y2={v.y2}>
|
|
67
|
-
<stop offset="0%" stopColor={gradient.from} />
|
|
68
|
-
<stop offset="100%" stopColor={gradient.to} />
|
|
69
|
-
</linearGradient>
|
|
70
|
-
),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
return { fill: color, def: null };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Native SVG `<pattern>` def for a repeating shape fill — fully vector,
|
|
78
|
-
* identical in the player and headless frame capture.
|
|
79
|
-
*/
|
|
80
|
-
function patternDef(id: string, pattern: ShapePattern): JSX.Element {
|
|
81
|
-
const size = pattern.size ?? 24;
|
|
82
|
-
const opacity = pattern.opacity ?? 1;
|
|
83
|
-
const color = pattern.color;
|
|
84
|
-
return (
|
|
85
|
-
<pattern
|
|
86
|
-
id={id}
|
|
87
|
-
width={size}
|
|
88
|
-
height={size}
|
|
89
|
-
patternUnits="userSpaceOnUse"
|
|
90
|
-
// Diagonal tiles rotate the whole pattern; dots/grid stay axis-aligned.
|
|
91
|
-
patternTransform={pattern.kind === 'diagonal' ? 'rotate(45)' : undefined}
|
|
92
|
-
>
|
|
93
|
-
{pattern.kind === 'dots' && (
|
|
94
|
-
<circle
|
|
95
|
-
cx={size / 2}
|
|
96
|
-
cy={size / 2}
|
|
97
|
-
r={Math.max(1, size / 12)}
|
|
98
|
-
fill={color}
|
|
99
|
-
opacity={opacity}
|
|
100
|
-
/>
|
|
101
|
-
)}
|
|
102
|
-
{pattern.kind === 'grid' && (
|
|
103
|
-
<path
|
|
104
|
-
d={`M ${size} 0 L 0 0 0 ${size}`}
|
|
105
|
-
fill="none"
|
|
106
|
-
stroke={color}
|
|
107
|
-
strokeWidth={1}
|
|
108
|
-
opacity={opacity}
|
|
109
|
-
/>
|
|
110
|
-
)}
|
|
111
|
-
{pattern.kind === 'diagonal' && (
|
|
112
|
-
<line x1={0} y1={0} x2={0} y2={size} stroke={color} strokeWidth={1} opacity={opacity} />
|
|
113
|
-
)}
|
|
114
|
-
</pattern>
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* SVG filter def for a shape's procedural filter (static film grain via
|
|
120
|
-
* feTurbulence). Returns the def plus the `filter` attribute value.
|
|
121
|
-
*/
|
|
122
|
-
export function resolveShapeFilter(
|
|
123
|
-
layerId: string,
|
|
124
|
-
filter: ShapeFilter | undefined,
|
|
125
|
-
): { filterAttr: string | undefined; def: JSX.Element | null } {
|
|
126
|
-
if (!filter || filter.type !== 'noise') return { filterAttr: undefined, def: null };
|
|
127
|
-
const id = `squisq-noise-${layerId}`;
|
|
128
|
-
const opacity = filter.opacity ?? 0.05;
|
|
129
|
-
return {
|
|
130
|
-
filterAttr: `url(#${id})`,
|
|
131
|
-
def: (
|
|
132
|
-
<filter id={id} x="0%" y="0%" width="100%" height="100%">
|
|
133
|
-
<feTurbulence
|
|
134
|
-
type="fractalNoise"
|
|
135
|
-
baseFrequency={filter.baseFrequency ?? 0.8}
|
|
136
|
-
numOctaves={2}
|
|
137
|
-
stitchTiles="stitch"
|
|
138
|
-
result="noise"
|
|
139
|
-
/>
|
|
140
|
-
<feColorMatrix in="noise" type="saturate" values="0" result="mono" />
|
|
141
|
-
<feComponentTransfer in="mono" result="faded">
|
|
142
|
-
<feFuncA type="linear" slope={opacity} intercept={0} />
|
|
143
|
-
</feComponentTransfer>
|
|
144
|
-
<feComposite in="faded" in2="SourceGraphic" operator="in" />
|
|
145
|
-
</filter>
|
|
146
|
-
),
|
|
147
|
-
};
|
|
148
|
-
}
|
package/src/utils/layerUtils.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared utilities for layer components.
|
|
3
|
-
*
|
|
4
|
-
* Resolves position values and anchor offsets used by all layer types.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Resolve a position value (number or percentage string) to pixels.
|
|
9
|
-
*/
|
|
10
|
-
export function resolveValue(value: number | string, dimension: number): number {
|
|
11
|
-
if (typeof value === 'number') {
|
|
12
|
-
return value;
|
|
13
|
-
}
|
|
14
|
-
if (value.endsWith('%')) {
|
|
15
|
-
const percent = parseFloat(value);
|
|
16
|
-
return (percent / 100) * dimension;
|
|
17
|
-
}
|
|
18
|
-
return parseFloat(value);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Get offset based on anchor point.
|
|
23
|
-
*/
|
|
24
|
-
export function getAnchorOffset(
|
|
25
|
-
anchor: string | undefined,
|
|
26
|
-
width: number,
|
|
27
|
-
height: number,
|
|
28
|
-
): { x: number; y: number } {
|
|
29
|
-
switch (anchor) {
|
|
30
|
-
case 'center':
|
|
31
|
-
return { x: -width / 2, y: -height / 2 };
|
|
32
|
-
case 'top-right':
|
|
33
|
-
return { x: -width, y: 0 };
|
|
34
|
-
case 'bottom-left':
|
|
35
|
-
return { x: 0, y: -height };
|
|
36
|
-
case 'bottom-right':
|
|
37
|
-
return { x: -width, y: -height };
|
|
38
|
-
case 'top-left':
|
|
39
|
-
default:
|
|
40
|
-
return { x: 0, y: 0 };
|
|
41
|
-
}
|
|
42
|
-
}
|