diff --git a/packages/builder-web-core/widgets-native/flex/index.ts b/packages/builder-web-core/widgets-native/flex/index.ts index d0bfe3bc..ab4b9863 100644 --- a/packages/builder-web-core/widgets-native/flex/index.ts +++ b/packages/builder-web-core/widgets-native/flex/index.ts @@ -23,6 +23,7 @@ import { IFlexManifest } from "@reflect-ui/core/lib/flex/flex.manifest"; import * as css from "@web-builder/styles"; import { CssMinHeightMixin } from "../_utility"; +type FlexWrap = "nowrap" | "wrap" | "wrap-reverse"; export class Flex extends MultiChildWidget implements CssMinHeightMixin { readonly _type: "row" | "column"; @@ -45,6 +46,7 @@ export class Flex extends MultiChildWidget implements CssMinHeightMixin { borderRadius?: BorderRadiusManifest; border?: Border; minHeight?: DimensionLength; + flexWrap?: FlexWrap; constructor( p: IFlexManifest & { @@ -64,6 +66,7 @@ export class Flex extends MultiChildWidget implements CssMinHeightMixin { overflow?: CSSProperty.Overflow; borderRadius?: BorderRadiusManifest; border?: Border; + flexWrap?: FlexWrap; } ) { super(p); @@ -92,6 +95,7 @@ export class Flex extends MultiChildWidget implements CssMinHeightMixin { // css only this.overflow = p.overflow; this.minHeight = p.minHeight; + this.flexWrap = p.flexWrap; } jsxConfig(): StylableJSXElementConfig { @@ -109,6 +113,7 @@ export class Flex extends MultiChildWidget implements CssMinHeightMixin { "align-items": this.crossAxisAlignment, overflow: this.overflow, flex: this.flex, + "flex-wrap": this.flexWrap, gap: this.itemSpacing && css.px(this.itemSpacing), "box-shadow": css.boxshadow(this.boxShadow), ...css.border(this.border), diff --git a/packages/design-sdk b/packages/design-sdk index 621006b8..65baeae6 160000 --- a/packages/design-sdk +++ b/packages/design-sdk @@ -1 +1 @@ -Subproject commit 621006b8cabcea4e736bdab9e9fcb0ce10e0f118 +Subproject commit 65baeae62346a6c7389c776e88f04f24b27f3703 diff --git a/packages/designto-code/package.json b/packages/designto-code/package.json index d7ef658d..4e34c84d 100644 --- a/packages/designto-code/package.json +++ b/packages/designto-code/package.json @@ -10,7 +10,7 @@ "@designto/web": "0.0.0", "@design-sdk/universal": "0.0.0", "@reflect-ui/detection": "0.1.1", - "@flutter-builder/flutter": "^2.5.0-f5" + "@flutter-builder/flutter": "^2.5.0-f7" }, "files": [ "README.md", diff --git a/packages/designto-flutter/dart-ui/dart-ui-clip.ts b/packages/designto-flutter/dart-ui/dart-ui-clip.ts new file mode 100644 index 00000000..a13aa787 --- /dev/null +++ b/packages/designto-flutter/dart-ui/dart-ui-clip.ts @@ -0,0 +1,16 @@ +import { Clip } from "@reflect-ui/core"; +import * as flutter from "@flutter-builder/flutter"; +export function clip(clip: Clip): flutter.Clip { + switch (clip) { + case Clip.none: + return flutter.Clip.none; + case Clip.hardEdge: + return flutter.Clip.hardEdge; + case Clip.antiAlias: + return flutter.Clip.antiAlias; + case Clip.antiAliasWithSaveLayer: + return flutter.Clip.antiAliasWithSaveLayer; + default: + throw new Error(`Unknown clip: ${clip}`); + } +} diff --git a/packages/designto-flutter/dart-ui/index.ts b/packages/designto-flutter/dart-ui/index.ts index fdc4535f..77c54469 100644 --- a/packages/designto-flutter/dart-ui/index.ts +++ b/packages/designto-flutter/dart-ui/index.ts @@ -1,4 +1,5 @@ +export * from "./dart-ui-clip"; +export * from "./dart-ui-color"; export * from "./dart-ui-offset"; export * from "./dart-ui-radius"; export * from "./dart-ui-text-align"; -export * from "./dart-ui-color"; diff --git a/packages/designto-flutter/painting/index.ts b/packages/designto-flutter/painting/index.ts index 0dcb3e0a..a71c2c4d 100644 --- a/packages/designto-flutter/painting/index.ts +++ b/packages/designto-flutter/painting/index.ts @@ -1,3 +1,4 @@ +export * from "./painting-axis"; export * from "./painting-alignment"; export * from "./painting-border-radius"; export * from "./painting-edge-insets"; diff --git a/packages/designto-flutter/painting/painting-axis.ts b/packages/designto-flutter/painting/painting-axis.ts new file mode 100644 index 00000000..1bbf4957 --- /dev/null +++ b/packages/designto-flutter/painting/painting-axis.ts @@ -0,0 +1,11 @@ +import { Axis } from "@reflect-ui/core"; +import * as flutter from "@flutter-builder/flutter"; + +export function axis(a: Axis): flutter.Axis { + switch (a) { + case Axis.horizontal: + return flutter.Axis.horizontal; + case Axis.vertical: + return flutter.Axis.vertical; + } +} diff --git a/packages/designto-flutter/rendering/index.ts b/packages/designto-flutter/rendering/index.ts index 41e9e480..86238df4 100644 --- a/packages/designto-flutter/rendering/index.ts +++ b/packages/designto-flutter/rendering/index.ts @@ -1,3 +1,5 @@ export * from "./rendering-cross-axis-alignment"; export * from "./rendering-main-axis-alignment"; export * from "./rendering-main-axis-size"; +export * from "./rendering-wrap-alignment"; +export * from "./rendering-wrap-cross-alignment"; diff --git a/packages/designto-flutter/rendering/rendering-wrap-alignment.ts b/packages/designto-flutter/rendering/rendering-wrap-alignment.ts new file mode 100644 index 00000000..ce09da36 --- /dev/null +++ b/packages/designto-flutter/rendering/rendering-wrap-alignment.ts @@ -0,0 +1,26 @@ +import { WrapAlignment } from "@flutter-builder/flutter"; +import { WrapAlignment as ReflectWrapAlignment } from "@reflect-ui/core/lib"; +import { do_explicitly_specify, FlutterPropConversionConfig } from "../_"; + +export function wrapAlignment( + wrapAlignment: ReflectWrapAlignment, + config?: FlutterPropConversionConfig +): WrapAlignment { + switch (wrapAlignment) { + case ReflectWrapAlignment.start: + // WrapAlignment.start is default value for Wrap + return do_explicitly_specify(config, WrapAlignment.start); + case ReflectWrapAlignment.start: + return WrapAlignment.center; + case ReflectWrapAlignment.center: + return WrapAlignment.end; + case ReflectWrapAlignment.spaceBetween: + return WrapAlignment.spaceBetween; + case ReflectWrapAlignment.spaceAround: + return WrapAlignment.spaceAround; + case ReflectWrapAlignment.spaceEvenly: + return WrapAlignment.spaceEvenly; + default: + throw new Error(`Unknown wrapAlignment: ${wrapAlignment}`); + } +} diff --git a/packages/designto-flutter/rendering/rendering-wrap-cross-alignment.ts b/packages/designto-flutter/rendering/rendering-wrap-cross-alignment.ts new file mode 100644 index 00000000..c728e768 --- /dev/null +++ b/packages/designto-flutter/rendering/rendering-wrap-cross-alignment.ts @@ -0,0 +1,24 @@ +import { WrapCrossAlignment } from "@flutter-builder/flutter"; +import * as core from "@reflect-ui/core/lib"; +import { do_explicitly_specify, FlutterPropConversionConfig } from "../_"; +/** + * @param wrapCrossAxisAlignment + */ +export function wrapCrossAxisAlignment( + wrapCrossAxisAlignment: core.WrapCrossAlignment, + config?: FlutterPropConversionConfig +): WrapCrossAlignment { + switch (wrapCrossAxisAlignment) { + case core.WrapCrossAlignment.start: + // WrapCrossAlignment.start is default value + return do_explicitly_specify(config, WrapCrossAlignment.start); + case core.WrapCrossAlignment.end: + return WrapCrossAlignment.end; + case core.WrapCrossAlignment.center: + return WrapCrossAlignment.start; + default: + throw new Error( + `"${wrapCrossAxisAlignment}" is not a valid wrapCrossAxisAlignment value` + ); + } +} diff --git a/packages/designto-flutter/tokens-to-flutter-widget/index.ts b/packages/designto-flutter/tokens-to-flutter-widget/index.ts index 1c4c29fd..b3ca2e32 100644 --- a/packages/designto-flutter/tokens-to-flutter-widget/index.ts +++ b/packages/designto-flutter/tokens-to-flutter-widget/index.ts @@ -82,6 +82,22 @@ function compose(widget: core.Widget, context: { is_root: boolean }) { children: children, // key: _key, }); + } else if (widget instanceof core.Wrap) { + thisFlutterWidget = new flutter.Wrap({ + ...default_props_for_layout, + direction: painting.axis(widget.direction), + alignment: rendering.wrapAlignment(widget.alignment), + spacing: widget.spacing, + runAlignment: rendering.wrapAlignment(widget.runAlignment), + runSpacing: widget.runSpacing, + crossAxisAlignment: rendering.wrapCrossAxisAlignment( + widget.crossAxisAlignment + ), + verticalDirection: painting.verticalDirection(widget.verticalDirection), + clipBehavior: dartui.clip(widget.clipBehavior), + children: handleChildren(widget.children), + key: undefined, + }); } else if (widget instanceof core.Flex) { // FIXME: FLEX not supported yet. // thisFlutterWidget = new flutter.Flex({ @@ -95,7 +111,7 @@ function compose(widget: core.Widget, context: { is_root: boolean }) { const _remove_overflow_if_root_overflow = { clipBehavior: context.is_root ? undefined - : (widget as core.Stack).clipBehavior, + : dartui.clip((widget as core.Stack).clipBehavior), }; const children = handle_flutter_case_no_size_stack_children( diff --git a/packages/designto-token/config/config.ts b/packages/designto-token/config/config.ts index 96cd51ad..0f5ded28 100644 --- a/packages/designto-token/config/config.ts +++ b/packages/designto-token/config/config.ts @@ -1,3 +1,47 @@ +/** + * A tokenizer config interface for extending tokenizer's behavior. + * + * The default tokenizer config is: @see {default_tokenizer_config} + */ export interface TokenizerConfig { sanitizer_ignore_masking_node: boolean; + + /** + * @default false - flags support enabled by default. + */ + disable_flags_support?: boolean; + + /** + * stops the tokenizer when max depth is reached relative to starter (root) node. + * @default "infinite" + */ + max_depth?: number | "infinite"; + + /** + * stops the current tokenizer (not the root tokenizing proc) when max depth is reached relative to current node. + * @default "infinite" + */ + max_each_depth?: number | "infinite"; + + /** + * (a.k.a should_stop) a middleware function that will be called before the each tokenization. if returns `true`, the tokenization will stop and return the current value. + */ + should_break?: (node: any) => boolean; + + /** + * a middleware function that will be called before the each tokenization. if returns `true`, the tokenization will be skipped. + */ + should_skip?: (node: any) => boolean; + + /** + * ignore a flag feature with a explicit gate. + * if ignore_flag set to true, the flag will be ignored even if this function returns false. + * It only affects when `true` returned as a result. + */ + should_ignore_flag?: (node: any) => boolean; + + /** + * A function that is called in between every tokenization process. (on shot for on node) + */ + process_listener?: (node: any) => void; } diff --git a/packages/designto-token/config/default-config.ts b/packages/designto-token/config/default-config.ts index a0c823ef..0c649b06 100644 --- a/packages/designto-token/config/default-config.ts +++ b/packages/designto-token/config/default-config.ts @@ -1,6 +1,12 @@ import { TokenizerConfig } from "./config"; -export const default_tokenizer_config: TokenizerConfig = { +export const default_tokenizer_config: TokenizerConfig = { // temporarily ignoring since masking is not supported yet. sanitizer_ignore_masking_node: true, + disable_flags_support: false, + max_depth: "infinite", + max_each_depth: "infinite", + should_break: () => false, + should_skip: () => false, + process_listener: () => {}, }; diff --git a/packages/designto-token/main.ts b/packages/designto-token/main.ts index c05d581b..d282876c 100644 --- a/packages/designto-token/main.ts +++ b/packages/designto-token/main.ts @@ -10,7 +10,7 @@ import { array } from "@reflect-ui/uiutils"; import { detectIf } from "@reflect-ui/detection"; import { byY, byYX } from "@designto/sanitized/sort-by-y-z"; import ignore_masking_pipline from "@designto/sanitized/ignore-masking-nodes"; -import { default_tokenizer_config } from "./config"; +import { default_tokenizer_config, TokenizerConfig } from "./config"; import { containsMasking, hasBackgroundBlurType, @@ -32,15 +32,21 @@ export type { Widget }; export type RuntimeChildrenInput = Array; +let __dangerous_current_config: TokenizerConfig = null; + /** * ENTRY POINT MAIN FUCTION * Main function for converting reflect design node tree to reflect widget token tree */ -export function tokenize(node: nodes.ReflectSceneNode): Widget { +export function tokenize( + node: nodes.ReflectSceneNode, + config: TokenizerConfig = default_tokenizer_config +): Widget { if (!node) { throw "A valid design node should be passed in order to tokenize it into a reflect widget."; } - return rootHandler(node); + __dangerous_current_config = config; + return rootHandler(node, config); } /** @@ -48,8 +54,11 @@ export function tokenize(node: nodes.ReflectSceneNode): Widget { * @param node * @returns */ -function rootHandler(node: nodes.ReflectSceneNode): Widget { - return dynamicGenerator(node) as Widget; +function rootHandler( + node: nodes.ReflectSceneNode, + config: TokenizerConfig +): Widget { + return dynamicGenerator(node, config) as Widget; } /** @@ -58,7 +67,8 @@ function rootHandler(node: nodes.ReflectSceneNode): Widget { * @returns */ function dynamicGenerator( - node: SingleOrArray + node: SingleOrArray, + config: TokenizerConfig ): SingleOrArray { if (isNotEmptyArray(node)) { const widgets: Array = []; @@ -66,13 +76,9 @@ function dynamicGenerator( node // .reverse() // .sort(byY) - .filter( - ignore_masking_pipline( - default_tokenizer_config.sanitizer_ignore_masking_node - ) - ) + .filter(ignore_masking_pipline(config.sanitizer_ignore_masking_node)) .forEach((node, index) => { - widgets.push(handleNode(node)); + widgets.push(handleNode(node, config)); }); // filter empty widgets (safe checker logic) @@ -81,7 +87,7 @@ function dynamicGenerator( return finalWidgets; } else { node = node as nodes.ReflectSceneNode; - const finalWidget = handleNode(node); + const finalWidget = handleNode(node, config); return finalWidget; } } @@ -91,17 +97,27 @@ function dynamicGenerator( * @param nodes * @returns */ -export function handleChildren(nodes: RuntimeChildrenInput): Array { +export function handleChildren( + nodes: RuntimeChildrenInput, + config: TokenizerConfig | "dangerously_use_current" +): Array { return nodes.map((n) => { if (n instanceof Widget) { return n; } else { - return dynamicGenerator(n) as Widget; + config = + config === "dangerously_use_current" + ? __dangerous_current_config + : config; + return dynamicGenerator(n, config) as Widget; } }); } -function handleNode(node: nodes.ReflectSceneNode): Widget { +function handleNode( + node: nodes.ReflectSceneNode, + config: TokenizerConfig +): Widget { if (!node.type) { console.error( "cannot handle unknown type of node. node.type was undefined or null" @@ -147,12 +163,18 @@ function handleNode(node: nodes.ReflectSceneNode): Widget { let tokenizedTarget: Widget = null; if (containsMasking(node)) { tokenizedTarget = tokenizeMasking.fromMultichild( - node as MaskingItemContainingNode + node as MaskingItemContainingNode, + config ); } if (!tokenizedTarget) { - tokenizedTarget = flags_handling_gate(node); + if ( + !config.disable_flags_support && + config.should_ignore_flag?.(node) !== true + ) { + tokenizedTarget = flags_handling_gate(node); + } } // // ------------------------------------------------------------------------- @@ -163,7 +185,7 @@ function handleNode(node: nodes.ReflectSceneNode): Widget { // ------------------------------------------------------------------------- if (!tokenizedTarget) { // if none handled by above gates, handle by type. this is the default tokenizer. - tokenizedTarget = handle_by_types(node); + tokenizedTarget = handle_by_types(node, config); } // // ------------------------------------------------------------------------- @@ -209,7 +231,10 @@ function post_wrap(node: nodes.ReflectSceneNode, tokenizedTarget: Widget) { return tokenizedTarget; } -function handle_by_types(node: nodes.ReflectSceneNode): Widget { +function handle_by_types( + node: nodes.ReflectSceneNode, + config: TokenizerConfig +): Widget { let tokenizedTarget: Widget; switch (node.type as string) { case nodes.ReflectSceneNodeType.rectangle: @@ -224,9 +249,14 @@ function handle_by_types(node: nodes.ReflectSceneNode): Widget { case nodes.ReflectSceneNodeType.frame: const _frame = node as nodes.ReflectFrameNode; - tokenizedTarget = tokenizeLayout.fromFrame(_frame, _frame.children, { - is_root: node.isRoot, - }); + tokenizedTarget = tokenizeLayout.fromFrame( + _frame, + _frame.children, + { + is_root: node.isRoot, + }, + config + ); break; case nodes.ReflectSceneNodeType.vector: @@ -244,7 +274,12 @@ function handle_by_types(node: nodes.ReflectSceneNode): Widget { case nodes.ReflectSceneNodeType.group: const _group = node as nodes.ReflectGroupNode; - tokenizedTarget = tokenizeLayout.fromGroup(_group, _group.children); + tokenizedTarget = tokenizeLayout.fromGroup( + _group, + _group.children, + undefined, + config + ); break; case nodes.ReflectSceneNodeType.ellipse: diff --git a/packages/designto-token/token-flags-gate/index.ts b/packages/designto-token/token-flags-gate/index.ts index 5c0a1644..4125c2da 100644 --- a/packages/designto-token/token-flags-gate/index.ts +++ b/packages/designto-token/token-flags-gate/index.ts @@ -1,6 +1,7 @@ import { parse } from "@code-features/flags"; import type { ReflectSceneNode } from "@design-sdk/figma"; -import { tokenizeGraphics } from ".."; +import { tokenize_flagged_artwork } from "./token-artwork"; +import { tokenize_flagged_wrap } from "./token-wrap"; export default function (node: ReflectSceneNode) { const flags = parse(node.name); @@ -8,7 +9,7 @@ export default function (node: ReflectSceneNode) { } function handle_with_flags(node, flags) { - if ( + const artwork_flag_alias = flags["artwork"] || flags["export-as"] || flags["export-as-png"] || @@ -17,8 +18,13 @@ function handle_with_flags(node, flags) { flags["export-as-jpg"] || flags["export-as-webp"] || flags["export-as-jpeg"] || - flags["export-as-gif"] - ) { - return tokenizeGraphics.fromAnyNode(node); + flags["export-as-gif"]; + if (artwork_flag_alias) { + return tokenize_flagged_artwork(node, artwork_flag_alias); + } + + const wrap_flag_alias = flags["wrap"] || flags["as-wrap"] || flags["is-wrap"]; + if (wrap_flag_alias) { + return tokenize_flagged_wrap(node, wrap_flag_alias); } } diff --git a/packages/designto-token/token-flags-gate/token-artwork/index.ts b/packages/designto-token/token-flags-gate/token-artwork/index.ts new file mode 100644 index 00000000..9984250b --- /dev/null +++ b/packages/designto-token/token-flags-gate/token-artwork/index.ts @@ -0,0 +1,10 @@ +import { ArtworkFlag } from "@code-features/flags/--artwork"; +import { ReflectSceneNode } from "@design-sdk/figma-node"; +import { tokenizeGraphics } from "../../token-graphics"; + +export function tokenize_flagged_artwork( + node: ReflectSceneNode, + flag: ArtworkFlag +) { + return tokenizeGraphics.fromAnyNode(node); +} diff --git a/packages/designto-token/token-flags-gate/token-wrap/index.ts b/packages/designto-token/token-flags-gate/token-wrap/index.ts new file mode 100644 index 00000000..27d2149b --- /dev/null +++ b/packages/designto-token/token-flags-gate/token-wrap/index.ts @@ -0,0 +1,140 @@ +/// +/// + +import type { + ReflectFrameNode, + ReflectSceneNode, +} from "@design-sdk/figma-node"; +import { Column, Flex, Row, Wrap } from "@reflect-ui/core"; +import type { AsWrapFlag } from "@code-features/flags/--as-wrap"; +import { keyFromNode } from "../../key"; +import { tokenize } from "../.."; +import { default_tokenizer_config } from "../../config"; +import { handleChildren } from "../../main"; +import { unwrappedChild } from "../../token-layout"; + +// type InputLayout = Array | Array; + +/** + * + * from + * ``` + * row[ + * col [1, 2, 3], + * col [4, 5, 6], + * col [7, 8, 9] + * ] + * ``` + * + * to + * ``` + * wrap[1, 2, 3, 4, 5, 6, 7, 8, 9] + * ``` + */ +export function tokenize_flagged_wrap( + node: ReflectSceneNode, + flag: AsWrapFlag +) { + const validated = validate_input(node as any); + if (validated.error === false) { + // console.log("validated as wrap", validated); + node = validated.wrap_root; + return new Wrap({ + key: keyFromNode(validated.wrap_root), + width: validated.wrap_root.width, + height: validated.wrap_root.height, + runSpacing: validated.runSpacing, + spacing: validated.spacing, + // flex: validated.wrap_root.layoutGrow, + // mainAxisSize: _mainaxissize, + // crossAxisAlignment: frame.crossAxisAlignment, + // mainAxisAlignment: frame.mainAxisAlignment, + // verticalDirection: VerticalDirection.down, + boxShadow: validated.wrap_root.primaryShadow, + padding: validated.wrap_root.padding, + // background: _background, + borderRadius: validated.wrap_root.cornerRadius, + // border: _border, + children: handleChildren( + validated.wrap_children, + "dangerously_use_current" + ), + }); + } else { + throw new Error(validated.error); + } +} + +/** + * validate if layer casted as wrap can be actually tokenized to wrap. + * + * 1. the root should be a column or row + * 2. the children should be columns or rows + * @param input + */ +function validate_input( + node: ReflectFrameNode +): + | { + error: false; + wrap_root: ReflectFrameNode; + wrap_children: Array; + spacing: number; + runSpacing: number; + } + | { error: string } { + if (node.type !== "FRAME") { + return { error: "wrap target is not a frame" }; + } + + const __fully_tokenized = tokenize(node, { + ...default_tokenizer_config, + // we don't want to use flags feature for the root, since if this not enabled, it will cause infinite loop. + should_ignore_flag: (flag) => flag.id == node.id, + max_depth: 1, // the root = 0, first level children = 1 + }); + + // 1. the root should be a column or row + const tokenized_node: Column | Row = unwrappedChild(__fully_tokenized) as any; + if (tokenized_node instanceof Column || __fully_tokenized instanceof Row) { + // if children len = 0, it's ok. all logic passed. + if (tokenized_node.children.length === 0) { + return { + wrap_root: node as ReflectFrameNode, + wrap_children: [], + error: false, + runSpacing: node.itemSpacing, + spacing: 0, + }; + } + // 2. the children should be columns or rows + const first_sample_child = unwrappedChild( + tokenized_node.children[0] + ) as Flex; + const childrentype = first_sample_child._type; + const has_mixed_chilren = tokenized_node.children.some((c) => { + unwrappedChild(c)._type !== childrentype; + }); + if (has_mixed_chilren) { + return { + error: `mixed children error : wrap can only have columns or rows as children`, + }; + } + + const depth2_children = [].concat.apply( + [], + node.children.map((cr) => cr.children) + ); + + return { + wrap_root: node as ReflectFrameNode, + wrap_children: depth2_children, + error: false, + runSpacing: node.itemSpacing, + spacing: first_sample_child.itemSpacing, + }; + } + return { + error: `invalid root type error : the targetted wrap "${node.name}" must be able to interpret as a column or row originally. but got "${__fully_tokenized._type}" instead.`, + }; +} diff --git a/packages/designto-token/token-layout/index.ts b/packages/designto-token/token-layout/index.ts index 38fe3211..2f07d346 100644 --- a/packages/designto-token/token-layout/index.ts +++ b/packages/designto-token/token-layout/index.ts @@ -25,6 +25,7 @@ import { } from "@reflect-ui/core"; import { Background } from "@reflect-ui/core/lib/background"; import { IFlexManifest } from "@reflect-ui/core/lib/flex/flex.manifest"; +import { TokenizerConfig } from "../config"; import { keyFromNode } from "../key"; import { handleChildren, RuntimeChildrenInput } from "../main"; import { tokenizeBackground } from "../token-background"; @@ -44,9 +45,15 @@ type OriginalChildrenReference = Array; function fromFrame( frame: nodes.ReflectFrameNode, children: RuntimeChildrenInput, - context: RuntimeLayoutContext + context: RuntimeLayoutContext, + config: TokenizerConfig ): core.LayoutRepresntatives { - const innerlayout = flexOrStackFromFrame(frame, children, context.references); + const innerlayout = flex_or_stack_from_frame( + frame, + children, + context.references, + config + ); const is_overflow_scrollable = isOverflowingAndShouldBeScrollable(frame); if (context.is_root) { @@ -72,12 +79,13 @@ function fromFrame( } } -function flexOrStackFromFrame( +function flex_or_stack_from_frame( frame: nodes.ReflectFrameNode, children: RuntimeChildrenInput, - references?: OriginalChildrenReference + references: OriginalChildrenReference, + config: TokenizerConfig ) { - const wchildren = handleChildren(children); + const wchildren = handleChildren(children, config); const _key = keyFromNode(frame); const _background = tokenizeBackground.fromFills(frame.fills); @@ -380,9 +388,10 @@ function stackChild({ function fromGroup( group: nodes.ReflectGroupNode, children: RuntimeChildrenInput, - references?: OriginalChildrenReference + references: OriginalChildrenReference, + config: TokenizerConfig ): core.LayoutRepresntatives { - const wchildren = handleChildren(children); + const wchildren = handleChildren(children, config); const stack_children = wchildren.map((c) => { if (c instanceof Positioned) { @@ -432,7 +441,7 @@ function isOverflowingAndShouldBeScrollable(frame: nodes.ReflectFrameNode) { ); } -function unwrappedChild(maybeWrapped: Widget): Widget { +export function unwrappedChild(maybeWrapped: Widget): Widget { const wrapped = maybeWrapped instanceof Rotation || maybeWrapped instanceof Blurred || @@ -449,16 +458,18 @@ function unwrappedChild(maybeWrapped: Widget): Widget { function fromFrameOrGroup( node: nodes.ReflectFrameNode | nodes.ReflectGroupNode, children: RuntimeChildrenInput, - context: RuntimeLayoutContext + context: RuntimeLayoutContext, + config: TokenizerConfig ) { if (node.type === ReflectSceneNodeType.frame) { - return fromFrame(node as nodes.ReflectFrameNode, children, context); + return fromFrame(node as nodes.ReflectFrameNode, children, context, config); } if (node.type === ReflectSceneNodeType.group) { return fromGroup( node as nodes.ReflectGroupNode, children, - context.references + context.references, + config ); } diff --git a/packages/designto-token/token-masking/index.ts b/packages/designto-token/token-masking/index.ts index 61bdf1e7..f2e40610 100644 --- a/packages/designto-token/token-masking/index.ts +++ b/packages/designto-token/token-masking/index.ts @@ -13,6 +13,7 @@ import { } from "@design-sdk/figma-node"; import { BorderRadius, ClipPath, ClipRRect, WidgetKey } from "@reflect-ui/core"; import { tokenizeGraphics } from ".."; +import { TokenizerConfig } from "../config"; import { containsMasking, ismaskier } from "../detection"; import { keyFromNode } from "../key"; import { tokenizeLayout } from "../token-layout"; @@ -42,7 +43,10 @@ type MaskingSplits = target: ReflectSceneNode[]; }; -function fromMultichild(node: MaskingItemContainingNode) { +function fromMultichild( + node: MaskingItemContainingNode, + config: TokenizerConfig +) { const hierarchy_items = node.children; if (containsMasking(node)) { // TODO: should we handle the case where that multiple maskier existing in same hierarchy? @@ -92,7 +96,8 @@ function fromMultichild(node: MaskingItemContainingNode) { maskitee, { is_root: cloned_container.isRoot, - } + }, + config ); const raw_maskier_key = keyFromNode(maskier); // we do not override key for clipped because maskier it self is not being nested, but being converted as a container-like. @@ -149,10 +154,15 @@ function fromMultichild(node: MaskingItemContainingNode) { // maskings clipped, // 2 (order matters) ]; - const container = tokenizeLayout.fromFrameOrGroup(node, children, { - is_root: node.isRoot, // probably not needed - who uses masking directly under root frame? - references: hierarchy_items, - }); + const container = tokenizeLayout.fromFrameOrGroup( + node, + children, + { + is_root: node.isRoot, // probably not needed - who uses masking directly under root frame? + references: hierarchy_items, + }, + config + ); /* ---- dev logging - preserve. console.log(`masking transform gate of ${container.key.originName}`, { diff --git a/packages/designto-web/tokens-to-web-widget/compose-wrap.ts b/packages/designto-web/tokens-to-web-widget/compose-wrap.ts new file mode 100644 index 00000000..926b014b --- /dev/null +++ b/packages/designto-web/tokens-to-web-widget/compose-wrap.ts @@ -0,0 +1,47 @@ +import * as web from "@web-builder/core"; +import * as core from "@reflect-ui/core"; +import { keyFromWidget } from "@web-builder/core"; + +export function compose_wrap( + wrap: core.Wrap, + children: web.WidgetTree[] +): web.Flex { + return new web.Flex({ + ...wrap, + direction: wrap.direction, + mainAxisAlignment: wrap_alignment_to_main_axis_alignment(wrap.alignment), + crossAxisAlignment: wrap_cross_alignment_to_cross_axis_alignment( + wrap.crossAxisAlignment + ), + itemSpacing: wrap.spacing, + flexWrap: "wrap", + children: children, + key: keyFromWidget(wrap), + }); +} + +const wrap_alignment_to_main_axis_alignment = ( + wrapalignment: core.WrapAlignment +): core.MainAxisAlignment => { + switch (wrapalignment) { + case core.WrapAlignment.start: + return core.MainAxisAlignment.start; + case core.WrapAlignment.end: + return core.MainAxisAlignment.end; + case core.WrapAlignment.center: + return core.MainAxisAlignment.center; + } +}; + +const wrap_cross_alignment_to_cross_axis_alignment = ( + wrapcrossalignment: core.WrapCrossAlignment +) => { + switch (wrapcrossalignment) { + case core.WrapCrossAlignment.start: + return core.CrossAxisAlignment.start; + case core.WrapCrossAlignment.end: + return core.CrossAxisAlignment.end; + case core.WrapCrossAlignment.center: + return core.CrossAxisAlignment.center; + } +}; diff --git a/packages/designto-web/tokens-to-web-widget/index.ts b/packages/designto-web/tokens-to-web-widget/index.ts index 5170e1d1..a69d797a 100644 --- a/packages/designto-web/tokens-to-web-widget/index.ts +++ b/packages/designto-web/tokens-to-web-widget/index.ts @@ -6,6 +6,7 @@ import { keyFromWidget } from "@web-builder/core"; import { MainImageRepository } from "@design-sdk/core/assets-repository"; import * as css from "@web-builder/styles"; import { Axis, Stack } from "@reflect-ui/core"; +import { compose_wrap } from "./compose-wrap"; export function buildWebWidgetFromTokens(widget: core.Widget): WidgetTree { const composed = compose(widget, { @@ -43,6 +44,9 @@ function compose(widget: core.Widget, context: { is_root: boolean }) { const _key = keyFromWidget(widget); let thisWebWidget: WidgetTree; + // ------------------------------------ + // region layouts + // ------------------------------------ if (widget instanceof core.Column) { thisWebWidget = new web.Column({ ...default_props_for_layout, @@ -55,6 +59,8 @@ function compose(widget: core.Widget, context: { is_root: boolean }) { children: handleChildren(widget.children), key: _key, }); + } else if (widget instanceof core.Wrap) { + thisWebWidget = compose_wrap(widget, handleChildren(widget.children)); } else if (widget instanceof core.Flex) { thisWebWidget = new web.Flex({ ...widget, @@ -101,7 +107,9 @@ function compose(widget: core.Widget, context: { is_root: boolean }) { right: widget.right, bottom: widget.bottom, }; - } else if (widget instanceof core.Opacity) { + } + // ENGREGION layouts ------------------------------------------------------------------------ + else if (widget instanceof core.Opacity) { thisWebWidget = handleChild(widget.child); thisWebWidget.extendStyle({ opacity: css.opacity(widget.opacity), diff --git a/packages/reflect-core b/packages/reflect-core index 88cdc5b6..6615b348 160000 --- a/packages/reflect-core +++ b/packages/reflect-core @@ -1 +1 @@ -Subproject commit 88cdc5b63199790a656c306c3dc716f934de0a6b +Subproject commit 6615b3486d6bfe8b5bc16133e59e25fb2bf2e690 diff --git a/packages/support-flags/--artwork/index.ts b/packages/support-flags/--artwork/index.ts index 310cd9c7..ce5c02e2 100644 --- a/packages/support-flags/--artwork/index.ts +++ b/packages/support-flags/--artwork/index.ts @@ -1,6 +1,6 @@ -export const artwork_flag_key = "artwork"; +export const flag_key__artwork = "artwork"; export interface ArtworkFlag { - flag: typeof artwork_flag_key; + flag: typeof flag_key__artwork; value?: boolean; } diff --git a/packages/support-flags/--as-br/README.md b/packages/support-flags/--as-br/README.md new file mode 100644 index 00000000..840be114 --- /dev/null +++ b/packages/support-flags/--as-br/README.md @@ -0,0 +1,3 @@ +# As Break + +## Interprets the flagged element as break (`
`). diff --git a/packages/support-flags/--as-size/README.md b/packages/support-flags/--as-size/README.md new file mode 100644 index 00000000..c203dcad --- /dev/null +++ b/packages/support-flags/--as-size/README.md @@ -0,0 +1,8 @@ +# Mark as size + +## Will be + +``` +element: --as-size +is: Sized() | Container() | Padding() +``` diff --git a/packages/support-flags/--as-wrap/index.ts b/packages/support-flags/--as-wrap/index.ts new file mode 100644 index 00000000..77366b8b --- /dev/null +++ b/packages/support-flags/--as-wrap/index.ts @@ -0,0 +1,5 @@ +export const flag_key__as_wrap = "as-wrap"; +export interface AsWrapFlag { + flag: typeof flag_key__as_wrap; + value?: boolean; +} diff --git a/packages/support-flags/index.ts b/packages/support-flags/index.ts index bd3abf4d..384b51c2 100644 --- a/packages/support-flags/index.ts +++ b/packages/support-flags/index.ts @@ -1,6 +1,7 @@ import { parse as parseflags } from "@design-sdk/flags/parsing-strategy-dashdash"; -import { ArtworkFlag, artwork_flag_key } from "./--artwork"; +import { flag_key__artwork } from "./--artwork"; +import { flag_key__as_wrap } from "./--as-wrap"; export * from "./types"; @@ -8,7 +9,11 @@ export function parse(name: string) { try { return parseflags(name, [ { - name: artwork_flag_key, + name: flag_key__artwork, + type: "bool", + }, + { + name: flag_key__as_wrap, type: "bool", }, //. TODO: add other flags under here. diff --git a/yarn.lock b/yarn.lock index 77bf396b..8084c61c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1506,10 +1506,10 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== -"@flutter-builder/flutter@^2.5.0-f5": - version "2.5.0-f5" - resolved "https://registry.yarnpkg.com/@flutter-builder/flutter/-/flutter-2.5.0-f5.tgz#a0bd7a08ce5fede37d1321234bdca2275f21eb6b" - integrity sha512-PSZzzKMfILttfOh6/btrOUD+d73mmfjoB2kg3VRmnWUufevH8wGkd+4UQv4d0ShCu/uN15ak9GmA26P87zDjHw== +"@flutter-builder/flutter@^2.5.0-f7": + version "2.5.0-f7" + resolved "https://registry.yarnpkg.com/@flutter-builder/flutter/-/flutter-2.5.0-f7.tgz#03ba57d70697a79459e0609bef670812b0928397" + integrity sha512-PTFQrKRquPcC/KA+aDC6Pf7TUlMQL4+s4Z117JWLfDXcdxXgU0H5rs7mge7XlH3+dOsVKqWCbUC9XbUpk8S9TQ== dependencies: "@abraham/reflection" "^0.8.0" dart-style "^1.3.2-dev" @@ -2872,6 +2872,11 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@types/assert@^1.5.6": + version "1.5.6" + resolved "https://registry.yarnpkg.com/@types/assert/-/assert-1.5.6.tgz#a8b5a94ce5fb8f4ba65fdc37fc9507609114189e" + integrity sha512-Y7gDJiIqb9qKUHfBQYOWGngUpLORtirAVPuj/CWJrU2C6ZM4/y3XLwuwfGMF8s7QzW746LQZx23m0+1FSgjfug== + "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": version "7.1.16" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.16.tgz#bc12c74b7d65e82d29876b5d0baf5c625ac58702" @@ -3922,15 +3927,7 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -assert@^1.1.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" - integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== - dependencies: - object-assign "^4.1.1" - util "0.10.3" - -assert@^2.0.0: +assert@2.0.0, assert@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32" integrity sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A== @@ -3940,6 +3937,14 @@ assert@^2.0.0: object-is "^1.0.1" util "^0.12.0" +assert@^1.1.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== + dependencies: + object-assign "^4.1.1" + util "0.10.3" + assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"