diff --git a/packages/editor/src/editor/Canvas.tsx b/packages/editor/src/editor/Canvas.tsx index fb69f53..15b50d9 100644 --- a/packages/editor/src/editor/Canvas.tsx +++ b/packages/editor/src/editor/Canvas.tsx @@ -165,7 +165,12 @@ export function Canvas(props: CanvasProps) { if (targetId && !props.selectionIds.includes(targetId)) { // goView-ish: right click selects the item. - props.onSelectSingle(targetId); + // Ctrl/Cmd keeps multi-select parity (add to selection instead of replacing). + if ((e as React.MouseEvent).ctrlKey || (e as React.MouseEvent).metaKey) { + props.onToggleSelect(targetId); + } else { + props.onSelectSingle(targetId); + } } setCtx({ clientX: e.clientX, clientY: e.clientY, worldX: p.x, worldY: p.y, targetId }); diff --git a/packages/sdk/src/core/widgets/iframe.ts b/packages/sdk/src/core/widgets/iframe.ts index 52e95cf..b800a6f 100644 --- a/packages/sdk/src/core/widgets/iframe.ts +++ b/packages/sdk/src/core/widgets/iframe.ts @@ -1,4 +1,5 @@ import type { IframeWidgetNode } from '../schema'; +import { pickUrlLike } from './urlLike'; /** * goView iframe option shape varies across versions. @@ -16,23 +17,8 @@ export interface GoViewIframeOption { */ export type LegacyIframeOption = GoViewIframeOption; -function asString(v: unknown): string { - if (typeof v === 'string') return v; - if (!v) return ''; - - // Some goView widgets store data as { value: '...' } or { url: '...' }. - if (typeof v === 'object') { - const obj = v as Record; - if (typeof obj.value === 'string') return obj.value; - if (typeof obj.url === 'string') return obj.url; - if (typeof obj.src === 'string') return obj.src; - } - - return ''; -} - function pickSrc(option: GoViewIframeOption): string { - return asString(option.dataset) || asString(option.src) || asString(option.url); + return pickUrlLike(option.dataset) || pickUrlLike(option.src) || pickUrlLike(option.url); } export function convertGoViewIframeOptionToNodeProps(option: GoViewIframeOption): IframeWidgetNode['props'] { diff --git a/packages/sdk/src/core/widgets/image.ts b/packages/sdk/src/core/widgets/image.ts index 3bdbcee..cf69920 100644 --- a/packages/sdk/src/core/widgets/image.ts +++ b/packages/sdk/src/core/widgets/image.ts @@ -1,4 +1,5 @@ import type { ImageWidgetNode } from '../schema'; +import { pickUrlLike } from './urlLike'; /** * goView Image option shape varies across versions. We keep this intentionally @@ -24,25 +25,16 @@ export interface GoViewImageOption { borderRadius?: number; } +function pickSrc(option: GoViewImageOption): string { + return pickUrlLike(option.dataset) || pickUrlLike(option.src) || pickUrlLike(option.url); +} + function asString(v: unknown): string { if (typeof v === 'string') return v; if (!v) return ''; - - // Some goView widgets store data as { value: '...' } or { url: '...' }. - if (typeof v === 'object') { - const obj = v as Record; - if (typeof obj.value === 'string') return obj.value; - if (typeof obj.url === 'string') return obj.url; - if (typeof obj.src === 'string') return obj.src; - } - return ''; } -function pickSrc(option: GoViewImageOption): string { - return asString(option.dataset) || asString(option.src) || asString(option.url); -} - function pickFit(option: GoViewImageOption): ImageWidgetNode['props']['fit'] | undefined { const raw = asString(option.fit) || asString(option.objectFit); if (!raw) return undefined; diff --git a/packages/sdk/src/core/widgets/urlLike.ts b/packages/sdk/src/core/widgets/urlLike.ts new file mode 100644 index 0000000..db47cdf --- /dev/null +++ b/packages/sdk/src/core/widgets/urlLike.ts @@ -0,0 +1,48 @@ +/** + * Small helper for permissive imports (goView / low-code exports). + * + * Many widgets store their URL-ish source in slightly different shapes: + * - string + * - { value: string } + * - { url: string } + * - { src: string } + * - { dataset: string | { value/url/src } } + * - nested objects under `data` / `config` / `options` + */ +export function pickUrlLike(input: unknown, maxDepth = 3): string { + return pickUrlLikeInner(input, maxDepth); +} + +function pickUrlLikeInner(input: unknown, depth: number): string { + if (typeof input === 'string') return input; + if (!input) return ''; + + if (Array.isArray(input)) { + for (const item of input) { + const v = pickUrlLikeInner(item, depth - 1); + if (v) return v; + } + return ''; + } + + if (typeof input !== 'object') return ''; + + const obj = input as Record; + + // Common direct keys. + for (const key of ['value', 'url', 'src', 'href', 'link', 'path', 'iframeUrl', 'videoUrl', 'mp4']) { + const v = obj[key]; + if (typeof v === 'string' && v) return v; + } + + if (depth <= 0) return ''; + + // Common nesting keys. + for (const key of ['dataset', 'data', 'config', 'option', 'options', 'props']) { + const v = obj[key]; + const nested = pickUrlLikeInner(v, depth - 1); + if (nested) return nested; + } + + return ''; +} diff --git a/packages/sdk/src/core/widgets/video.ts b/packages/sdk/src/core/widgets/video.ts index 2809b11..8da7679 100644 --- a/packages/sdk/src/core/widgets/video.ts +++ b/packages/sdk/src/core/widgets/video.ts @@ -1,4 +1,5 @@ import type { VideoWidgetNode } from '../schema'; +import { pickUrlLike } from './urlLike'; /** * goView video option shape varies across versions. @@ -23,24 +24,16 @@ export interface GoViewVideoOption { */ export type LegacyVideoOption = GoViewVideoOption; +function pickSrc(option: GoViewVideoOption): string { + return pickUrlLike(option.dataset) || pickUrlLike(option.src) || pickUrlLike(option.url); +} + function asString(v: unknown): string { if (typeof v === 'string') return v; if (!v) return ''; - - if (typeof v === 'object') { - const obj = v as Record; - if (typeof obj.value === 'string') return obj.value; - if (typeof obj.url === 'string') return obj.url; - if (typeof obj.src === 'string') return obj.src; - } - return ''; } -function pickSrc(option: GoViewVideoOption): string { - return asString(option.dataset) || asString(option.src) || asString(option.url); -} - function pickFit(option: GoViewVideoOption): VideoWidgetNode['props']['fit'] | undefined { const raw = asString(option.fit) || asString(option.objectFit); if (!raw) return undefined;