diff --git a/docs/css-blend-mode.md b/docs/css-blend-mode.md new file mode 100644 index 00000000..e709a7a8 --- /dev/null +++ b/docs/css-blend-mode.md @@ -0,0 +1,16 @@ +# CSS - Blend mode + +## Supported types + +``` +normal | multiply | screen | overlay | darken | lighten | color-dodge | color-burn | hard-light | soft-light | difference | exclusion | hue | saturation | color | luminosity +``` + +## Warning Lack of browser support on SVG element. + +Applying `mix-blend-mode` to SVG element is limited in some browsers (safari & mobile browsers) See - https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode#browser_compatibility + +### References + +- [`mix-blend-mode`](https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode) +- [`background-blend-mode`](https://developer.mozilla.org/en-US/docs/Web/CSS/background-blend-mode) diff --git a/docs/css-multiple-background.md b/docs/css-multiple-background.md new file mode 100644 index 00000000..f940998d --- /dev/null +++ b/docs/css-multiple-background.md @@ -0,0 +1,60 @@ +--- +title: "CSS How to handle multiple background fills" +version: 0.1.0 +revision: 1 +--- + +# How to handle multiple background fills + +## Definition of `"multiple background fills"` + +- one or none active solid fill +- one or more gradient fill above solid fill +- one or more image fill + +## Possible combinations + +single solid fill + +```css +._1 { + background: #fff; +} +._2 { + background-color: #fff; +} +``` + +single solid fill with single gradient fill + +```css +._1 { + background-color: #fff; + background-image: linear-gradient(to bottom, #fff, #fff); +} + +._2 { + background: #fff; + background-image: linear-gradient(to bottom, #fff, #fff); +} +``` + +no solid fill with single gradient fill + +```css +._1 { + background: linear-gradient(to bottom, #fff, #fff); +} + +._2 { + background-image: linear-gradient(to bottom, #fff, #fff); +} +``` + +no solid fill with multiple gradient fill + +```css +._1 { + background: linear-gradient(to bottom, #fff, #fff), linear-gradient(to bottom, #fff, #fff); +} +``` diff --git a/docs/css-text-gradient.md b/docs/css-text-gradient.md new file mode 100644 index 00000000..dac84d3e --- /dev/null +++ b/docs/css-text-gradient.md @@ -0,0 +1,26 @@ +--- +title: "CSS Gradient on text layer" +version: 0.1.0 +revision: 1 +--- + +# CSS - Gradient on Text Layer + +Applying a gradient to a text fill is quite different from simply giving a color to a text. +Yet hooray CSS, it is much more simple than other platforms (flutter, android, ...) + +**How to** + +```css +h1 { + font-size: 72px; + background: -webkit-linear-gradient(#eee, #333); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} +``` + +### References + +- https://cssgradient.io/blog/css-gradient-text/ +- https://github.com/gridaco/designto-code/issues/84 diff --git a/docs/css-text-vertical-align.md b/docs/css-text-vertical-align.md new file mode 100644 index 00000000..2151eded --- /dev/null +++ b/docs/css-text-vertical-align.md @@ -0,0 +1,11 @@ +--- +title: "CSS Vertically align text content" +version: 0.1.0 +revision: 1 +--- + +# Vertical align of text content + +### References + +- https://stackoverflow.com/questions/8865458/how-do-i-vertically-center-text-with-css diff --git a/docs/figma-rotation.md b/docs/figma-rotation.md index f6657af7..ed23d84a 100644 --- a/docs/figma-rotation.md +++ b/docs/figma-rotation.md @@ -12,6 +12,37 @@ revision: 1 > Figma rotation from [figma plugin docs](https://www.figma.com/plugin-docs/api/properties/nodes-rotation/#docsNav) +## [Note] The rotation value needs to be inverted on the client side. + +to rotate something to the clockwise direction, + +- figma: -n +- client: +n (css) + +the transform origin + +- figma: not specified (always top left) +- client: top left + +**so the conversion from figma to css will be like below** + +``` +# figma +{ + x: 100, + y: 100, + rotation: 10, +} + +# css +.rotate-n { + top: 100; + left: 100; + transform: rotate(-10deg); + transform-origin: top left; +} +``` + ## Transform? While figma and other major design tools has both transform value, and explicit rotation value (which can be calculated from transform value), The intuitive way to represent a rotation value is by using a `Rotation` token. Overall all figma node properties, the only two property that has impact to final transform (based on css) is `scale` and `rotation`. diff --git a/editor-packages/editor-canvas/canvas/canvas.tsx b/editor-packages/editor-canvas/canvas/canvas.tsx index 051517b0..595f8cad 100644 --- a/editor-packages/editor-canvas/canvas/canvas.tsx +++ b/editor-packages/editor-canvas/canvas/canvas.tsx @@ -320,6 +320,7 @@ export function Canvas({ ).map((h) => ({ id: h.id, xywh: [h.absoluteX, h.absoluteY, h.width, h.height], + rotation: h.rotation, })) : [] } diff --git a/editor-packages/editor-canvas/canvas/hud-surface.tsx b/editor-packages/editor-canvas/canvas/hud-surface.tsx index 05d21f9f..193414c9 100644 --- a/editor-packages/editor-canvas/canvas/hud-surface.tsx +++ b/editor-packages/editor-canvas/canvas/hud-surface.tsx @@ -22,6 +22,7 @@ export interface DisplayNodeMeta { absoluteY: number; width: number; height: number; + rotation: number; } export function HudSurface({ @@ -41,7 +42,7 @@ export function HudSurface({ }: { offset: XY; zoom: number; - highlights: { id: string; xywh: XYWH }[]; + highlights: { id: string; xywh: XYWH; rotation: number }[]; labelDisplayNodes: DisplayNodeMeta[]; selectedNodes: DisplayNodeMeta[]; hide: boolean; @@ -102,6 +103,7 @@ export function HudSurface({ key={h.id} type="xywhr" xywh={h.xywh} + rotation={h.rotation} zoom={zoom} width={2} /> @@ -121,6 +123,7 @@ export function HudSurface({ key={s.id} type="xywhr" xywh={xywh} + rotation={s.rotation} zoom={zoom} width={1} /> diff --git a/editor-packages/editor-canvas/overlay/hover-outline-hightlight.tsx b/editor-packages/editor-canvas/overlay/hover-outline-hightlight.tsx index 068e2266..b5b9b6db 100644 --- a/editor-packages/editor-canvas/overlay/hover-outline-hightlight.tsx +++ b/editor-packages/editor-canvas/overlay/hover-outline-hightlight.tsx @@ -2,46 +2,27 @@ import React from "react"; import { color_layer_highlight } from "../theme"; import { get_boinding_box } from "./math"; import { OulineSide } from "./outline-side"; +import { OverlayContainer } from "./overlay-container"; import type { OutlineProps } from "./types"; export function HoverOutlineHighlight({ width = 1, ...props }: OutlineProps) { - const { xywh, zoom } = props; + const { xywh, zoom, rotation } = props; const bbox = get_boinding_box({ xywh, scale: zoom }); const wh: [number, number] = [xywh[2], xywh[3]]; + const vprops = { + wh: wh, + zoom: props.zoom, + width: width, + box: bbox, + color: color_layer_highlight, + }; + return ( - <> - - - - - + + + + + + ); } diff --git a/editor-packages/editor-canvas/overlay/math.ts b/editor-packages/editor-canvas/overlay/math.ts index 67fc2190..9c391b42 100644 --- a/editor-packages/editor-canvas/overlay/math.ts +++ b/editor-packages/editor-canvas/overlay/math.ts @@ -6,6 +6,8 @@ export function get_boinding_box({ scale: number; }): [number, number, number, number] { const [x, y, w, h] = xywh; + + // return the bounding box in [number, number, number, number] form with givven x, y, w, h, rotation and scale. const [x1, y1, x2, y2] = [ x * scale, y * scale, diff --git a/editor-packages/editor-canvas/overlay/overlay-container.tsx b/editor-packages/editor-canvas/overlay/overlay-container.tsx new file mode 100644 index 00000000..73bbaf7b --- /dev/null +++ b/editor-packages/editor-canvas/overlay/overlay-container.tsx @@ -0,0 +1,30 @@ +import React from "react"; + +/** + * @default - TODO: rotation not supported + * @returns + */ +export function OverlayContainer({ + xywh, + rotation = 0, + children, +}: { + xywh: [number, number, number, number]; + rotation: number; + children: React.ReactNode; +}) { + // const [x, y, w, h] = xywh; + return ( +
+ {children} +
+ ); +} diff --git a/editor-packages/editor-canvas/overlay/readonly-select-hightlight.tsx b/editor-packages/editor-canvas/overlay/readonly-select-hightlight.tsx index 8dea6a5a..27ab07db 100644 --- a/editor-packages/editor-canvas/overlay/readonly-select-hightlight.tsx +++ b/editor-packages/editor-canvas/overlay/readonly-select-hightlight.tsx @@ -3,12 +3,13 @@ import type { OutlineProps } from "./types"; import { color_layer_readonly_highlight } from "../theme"; import { get_boinding_box } from "./math"; import { OulineSide } from "./outline-side"; +import { OverlayContainer } from "./overlay-container"; export function ReadonlySelectHightlight({ width = 1, ...props }: OutlineProps) { - const { xywh, zoom } = props; + const { xywh, zoom, rotation } = props; const bbox = get_boinding_box({ xywh, scale: zoom }); const wh: [number, number] = [xywh[2], xywh[3]]; @@ -16,12 +17,16 @@ export function ReadonlySelectHightlight({ const handle_size = 3; const dot_size = 4; + const sideprops = { + wh: wh, + zoom: props.zoom, + width: width, + box: bbox, + color: color_layer_readonly_highlight, + }; + return ( -
+ <> <> - - - - + + + + -
+ ); } diff --git a/externals/design-sdk b/externals/design-sdk index 1212ba4a..6c54a532 160000 --- a/externals/design-sdk +++ b/externals/design-sdk @@ -1 +1 @@ -Subproject commit 1212ba4aafa1c08d372faa3d15ab2f5b72eb16ac +Subproject commit 6c54a532cec8a4ca016a43a8d75461e0a961ff13 diff --git a/externals/reflect-core b/externals/reflect-core index c5dc2966..7e792b1f 160000 --- a/externals/reflect-core +++ b/externals/reflect-core @@ -1 +1 @@ -Subproject commit c5dc2966f333a1f4749a6aa3acec606a3dd0848d +Subproject commit 7e792b1fff930f3b1f49956b9ab2f9d40aa6f682 diff --git a/packages/builder-css-styles/border-radius/index.ts b/packages/builder-css-styles/border-radius/index.ts index c1566e76..4ed99418 100644 --- a/packages/builder-css-styles/border-radius/index.ts +++ b/packages/builder-css-styles/border-radius/index.ts @@ -60,10 +60,11 @@ export function borderRadius(r: BorderRadiusManifest): CSSProperties { "border-radius": px(r.all), }; } else { - console.warn("elliptical border radius not supported"); + // example - https://codepen.io/Mahe76/pen/ExZbdro return { - "border-radius": px(r.all.x), + "border-radius": `${px(r.all.x)} / ${px(r.all.y)}`, }; + // TODO: support short handed version - `50%` } } else { return { diff --git a/packages/builder-css-styles/border/index.ts b/packages/builder-css-styles/border/index.ts index 0d51b3c3..4576467d 100644 --- a/packages/builder-css-styles/border/index.ts +++ b/packages/builder-css-styles/border/index.ts @@ -33,5 +33,7 @@ export function border(border: Border): CSSProperties { } export function borderSide(borderSide: BorderSide): CSSProperty.Border { - return `solid ${px(borderSide.width)} ${color(borderSide.color)}`; + return `${borderSide.style ?? "solid"} ${px(borderSide.width)} ${color( + borderSide.color + )}`; } diff --git a/packages/builder-web-core/widgets-native/html-svg/index.ts b/packages/builder-web-core/widgets-native/html-svg/index.ts index e6abd37c..1315134d 100644 --- a/packages/builder-web-core/widgets-native/html-svg/index.ts +++ b/packages/builder-web-core/widgets-native/html-svg/index.ts @@ -200,6 +200,19 @@ export class SvgElement extends StylableJsxWidget { }; } + // @override + get finalStyle() { + const super_finalStyle = super.finalStyle; + return { + ...super_finalStyle, + // TODO: this is a hot fix of svg & path's constraint handling + // width & height for the svg must be preserved. (it wont' follow the constraints anyway.) + // it can also be 100% + width: css.length(this.width), + height: css.length(this.height), + }; + } + jsxConfig(): StylableJSXElementConfig | UnstylableJSXElementConfig { return { type: "tag-and-attr", diff --git a/packages/builder-web-core/widgets-native/html-text-element/index.ts b/packages/builder-web-core/widgets-native/html-text-element/index.ts index 8611aaee..384295d8 100644 --- a/packages/builder-web-core/widgets-native/html-text-element/index.ts +++ b/packages/builder-web-core/widgets-native/html-text-element/index.ts @@ -14,7 +14,7 @@ import { Dynamic } from "@reflect-ui/core/lib/_utility-types"; * You can select wich element to render with `elementPreference`. - choose between h1 ~ h6, p, span, etc. */ export class Text extends TextChildWidget { - _type: "Text"; + _type: "Text" = "Text"; // text properties data: Dynamic; @@ -73,16 +73,29 @@ export class Text extends TextChildWidget { "text-align": this.textAlign, "text-decoration": css.textDecoration(this.textStyle.decoration), "text-shadow": css.textShadow(this.textStyle.textShadow), + "text-transform": this.textStyle.textTransform, // ------------------------------------------ - "min-height": css.px(this.height), - // TODO: do not specify width when parent is a flex container. - // Also flex: 1 is required to make the text wrap. - width: css.px(this.width), + ...textWH({ width: this.width, height: this.height }), }; return textStyle; } + get finalStyle() { + const superFinalStyle = super.finalStyle; + // TODO: this is a dirty fix ------------------------------------------------ + // the text's width should not be overriden by the constraint's preference. + if (this.width === undefined) { + delete superFinalStyle["width"]; + } + if (this.height === undefined) { + delete superFinalStyle["height"]; + } + // -------------------------------------------------------------------------- + + return { ...superFinalStyle }; + } + jsxConfig(): StylableJSXElementConfig { return { type: "tag-and-attr", @@ -99,3 +112,12 @@ const __get_dedicated_element_tag = (t?: WebTextElement | undefined) => { return __default_element_tag; } }; + +function textWH({ width, height }: { width: number; height: number }) { + return { + // TODO: do not specify width when parent is a flex container. (set as 100%) + // Also flex-grow: 1 is required to make the text wrap. + width: css.px(width), + "min-height": css.px(height), + }; +} diff --git a/packages/designto-token/main.ts b/packages/designto-token/main.ts index c14a42c7..7d15a886 100644 --- a/packages/designto-token/main.ts +++ b/packages/designto-token/main.ts @@ -270,6 +270,21 @@ function handle_by_types( break; case nodes.ReflectSceneNodeType.text: + // FIXME: aberation handling (remove me if required) -------------------------------- + // FIXME: this is for giving extra space for text so it won't break line accidently. + // FIXME: consider applying this only to a preview build + // TODO: change logic to word count. + const wordcount = node.data.split(" ").length; + const txtlen = node.data.length; + if (wordcount <= 1) { + /* skip, since there is no word break */ + } else if (txtlen <= 6 && wordcount <= 2) { + node.width = node.width + 1; + } else if (txtlen < 30) { + node.width = node.width + 2; + } + // FIXME: --------------------------------------------------------------------------------- + tokenizedTarget = tokenizeText.fromText(node as nodes.ReflectTextNode); break; @@ -306,7 +321,13 @@ function handle_by_types( break; case nodes.ReflectSceneNodeType.ellipse: - tokenizedTarget = tokenizeContainer.fromEllipse(node); + if (node.arcData.startingAngle === 0 && node.arcData.innerRadius === 0) { + // a standard ellipse + tokenizedTarget = tokenizeContainer.fromEllipse(node); + } else { + // a customized ellipse, most likely to be part of a graphical element. + tokenizedTarget = tokenizeGraphics.fromAnyNode(node); + } break; case nodes.ReflectSceneNodeType.boolean_operation: @@ -314,11 +335,9 @@ function handle_by_types( break; case nodes.ReflectSceneNodeType.line: - // FIXME: this is a temporary fallback. line should be handled with unique handler. (using rect's handler instead.) - tokenizedTarget = tokenizeContainer.fromRectangle(node as any); + tokenizedTarget = tokenizeContainer.fromLine(node as any); + // tokenizedTarget = tokenizeDivider.fromLine(_line); break; - // const _line = node as nodes.ReflectLineNode; - // tokenizedTarget = tokenizeDivider.fromLine(_line); default: console.error(`${node["type"]} is not yet handled by "@designto/token"`); diff --git a/packages/designto-token/token-border/index.ts b/packages/designto-token/token-border/index.ts index de638e7e..23d7a5f4 100644 --- a/packages/designto-token/token-border/index.ts +++ b/packages/designto-token/token-border/index.ts @@ -1,9 +1,38 @@ import { retrievePrimaryColor } from "@design-sdk/core/utils"; -import { ReflectSceneNode } from "@design-sdk/figma-node"; +import type { + ReflectSceneNode, + ReflectLineNode, + IReflectGeometryMixin, +} from "@design-sdk/figma-node"; import { Figma } from "@design-sdk/figma-types"; /** remove figma dependency */ -import { Border } from "@reflect-ui/core"; +import { Border, BorderStyle } from "@reflect-ui/core"; import { roundNumber } from "@reflect-ui/uiutils"; +function fromLineNode(node: ReflectLineNode) { + const strokes = node.strokes; + if (!strokes || strokes.length === 0) { + return undefined; + } + // guard invisible borders + if (strokes.filter(visible).length > 0) { + const _p_stroke = strokes.find(visible); + + // generate the border, when it should exist + // if width is 0, then we don't want to generate a border + return node.strokeWeight + ? new Border({ + // it's a line so we only add border to a single side. + top: { + color: retrievePrimaryColor([_p_stroke]), + width: node.strokeWeight, // do not round number + // the dashed border support is incomplete. we cannot support dashed gap since the platform does not support it. + style: isDashed(node) ? BorderStyle.dashed : BorderStyle.solid, + }, + }) + : undefined; + } +} + function fromNode(node: ReflectSceneNode) { if ("strokes" in node) { if (!node.strokes || node.strokes.length === 0) { @@ -12,6 +41,8 @@ function fromNode(node: ReflectSceneNode) { if ("strokeWeight" in node) { return fromStrokes(node.strokes, { width: node.strokeWeight, + // the dashed border support is incomplete. we cannot support dashed gap since the platform does not support it. + dashed: isDashed(node), }); } } @@ -21,27 +52,47 @@ function fromStrokes( strokes: ReadonlyArray, { width, + dashed, }: { /** * a stroke height */ width: number; + dashed: boolean; } ) { // guard invisible borders - if (strokes.filter((s) => s.visible).length > 0) { + if (strokes.filter(visible).length > 0) { // generate the border, when it should exist // if width is 0, then we don't want to generate a border + // using the first stroke. since css3 standard does not support multiple borders (as well as flutter) + const _p_stroke = strokes.find(visible); return width ? Border.all({ - color: retrievePrimaryColor(strokes), + color: retrievePrimaryColor([_p_stroke]), width: roundNumber(width), + style: dashed ? BorderStyle.dashed : BorderStyle.solid, }) : undefined; } } +const visible = (s) => s.visible; + +/** + * + * returns if the node's border is dashed or not + * + * - when solid, the dashPattern is empty - `[]` + * - when dashed, the dashPattern will be like - `[6, 6]` + * @param s + * @returns + */ +const isDashed = (s: IReflectGeometryMixin): boolean => + s.dashPattern?.length > 0; + export const tokenizeBorder = { fromStrokes: fromStrokes, fromNode: fromNode, + fromLineNode: fromLineNode, }; diff --git a/packages/designto-token/token-container/index.ts b/packages/designto-token/token-container/index.ts index c721bf6c..d92dccc8 100644 --- a/packages/designto-token/token-container/index.ts +++ b/packages/designto-token/token-container/index.ts @@ -4,7 +4,7 @@ import { tokenizeBackground } from "../token-background"; import { BoxShape } from "@reflect-ui/core/lib/box-shape"; import { keyFromNode } from "../key"; import { tokenizeBorder } from "../token-border"; -import { BoxShadowManifest } from "@reflect-ui/core"; +import { BorderRadius, BoxShadowManifest } from "@reflect-ui/core"; function fromRectangle(rect: nodes.ReflectRectangleNode): core.Container { const container = new core.Container({ @@ -23,6 +23,20 @@ function fromRectangle(rect: nodes.ReflectRectangleNode): core.Container { return container; } +function fromLine(line: nodes.ReflectLineNode): core.Container { + const container = new core.Container({ + key: keyFromNode(line), + width: line.width, + height: 0, + boxShadow: line.shadows as BoxShadowManifest[], + border: tokenizeBorder.fromLineNode(line), + }); + + container.x = line.x; + container.y = line.y; + return container; +} + function fromEllipse(ellipse: nodes.ReflectEllipseNode): core.Container { const container = new core.Container({ key: keyFromNode(ellipse), @@ -30,7 +44,7 @@ function fromEllipse(ellipse: nodes.ReflectEllipseNode): core.Container { height: ellipse.height, boxShadow: ellipse.shadows as BoxShadowManifest[], border: tokenizeBorder.fromNode(ellipse), - borderRadius: { all: Math.max(ellipse.width, ellipse.height) / 2 }, + borderRadius: BorderRadius.all({ x: ellipse.width, y: ellipse.height }), // this is equivalant to css "50%" background: tokenizeBackground.fromFills(ellipse.fills), }); @@ -45,4 +59,5 @@ function fromEllipse(ellipse: nodes.ReflectEllipseNode): core.Container { export const tokenizeContainer = { fromRectangle: fromRectangle, fromEllipse: fromEllipse, + fromLine: fromLine, }; diff --git a/packages/designto-token/token-layout/index.ts b/packages/designto-token/token-layout/index.ts index daf2a61a..908617bc 100644 --- a/packages/designto-token/token-layout/index.ts +++ b/packages/designto-token/token-layout/index.ts @@ -1,5 +1,6 @@ import { nodes, ReflectSceneNodeType } from "@design-sdk/core"; import { layoutAlignToReflectMainAxisSize } from "@design-sdk/figma-node-conversion"; +import type { Constraints } from "@design-sdk/figma-types"; import * as core from "@reflect-ui/core"; import { Axis, @@ -249,7 +250,8 @@ function find_original(ogchildren: Array, of: Widget) { // target the unwrapped child c.id === (_unwrappedChild && _unwrappedChild.key.id) || // target the widget itself - some widgets are not wrapped, yet being converted to a container-like (e.g. maskier) - c.id === of.key.id + c.id === of.key.id || + c.id === of.key.id.split(".")[0] // {id}.positioned or {id}.scroll-wrap TODO: this logic can cause problem later on. ); if (!ogchild) { console.error( @@ -278,7 +280,7 @@ function stackChild({ container: nodes.ReflectSceneNode; wchild: core.Widget; }) { - const constraint = { + let constraint = { left: undefined, top: undefined, right: undefined, @@ -290,16 +292,11 @@ function stackChild({ const _unwrappedChild: IWHStyleWidget = unwrappedChild( child ) as IWHStyleWidget; - const wh = { + let wh = { width: _unwrappedChild.width, height: _unwrappedChild.height, }; - const _l = ogchild.x; - const _r = container.width - (ogchild.x + ogchild.width); - const _t = ogchild.y; - const _b = container.height - (ogchild.y + ogchild.height); - /** * "MIN": Left or Top * "MAX": Right or Bottom @@ -320,101 +317,25 @@ function stackChild({ ); // throw `${ogchild.toString()} has no constraints. this can happen when node under group item tokenization is incomplete. this is engine's error.`; } else { - switch (ogchild.constraints.horizontal) { - case "MIN": - constraint.left = _l; - break; - case "MAX": - constraint.right = _r; - break; - case "SCALE": /** scale fallbacks to stretch */ - case "STRETCH": - constraint.left = _l; - constraint.right = _r; - wh.width = undefined; // no fixed width - break; - case "CENTER": - const half_w = ogchild.width / 2; - const centerdiff = - // center of og - half_w + - ogchild.x - - // center of frame - container.width / 2; - constraint.left = { - type: "calc", - operations: { - type: "op", - left: { - type: "calc", - operations: { - type: "op", - left: "50%", - op: "+", - right: centerdiff, - }, - }, - op: "-", // this part is different - right: half_w, - }, - }; - // --- we can also specify the right, but left is enough. - // constraint.right = { - // type: "calc", - // operations: { - // left: { - // type: "calc", - // operations: { left: "50%", op: "+", right: centerdiff }, - // }, - // op: "+", // this part is different - // right: half, - // }, - // }; - break; - } - switch (ogchild.constraints.vertical) { - case "MIN": - constraint.top = _t; - break; - case "MAX": - constraint.bottom = _b; - break; - case "SCALE": /** scale fallbacks to stretch */ - case "STRETCH": - constraint.top = _t; - constraint.bottom = _b; - wh.height = undefined; - break; - case "CENTER": - const half_height = ogchild.height / 2; - const container_snapshot_center = container.height / 2; - const child_snapshot_center = half_height + ogchild.y; - - const centerdiff = - // center of og - child_snapshot_center - - // center of frame - container_snapshot_center; - - constraint.top = { - type: "calc", - operations: { - type: "op", - left: { - type: "calc", - operations: { - type: "op", - left: "50%", - op: "+", - right: centerdiff, - }, - }, - op: "-", // this part is different - right: half_height, - }, - }; - break; - } + const _l = ogchild.x; + const _r = container.width - (ogchild.x + ogchild.width); + const _t = ogchild.y; + const _b = container.height - (ogchild.y + ogchild.height); + + const res = handlePositioning({ + constraints: ogchild.constraints, + pos: { l: _l, t: _t, b: _b, r: _r, x: ogchild.x, y: ogchild.y }, + width: ogchild.width, + height: ogchild.height, + containerWidth: container.width, + containerHeight: container.height, + }); + + constraint = res.constraint; + wh = { + ...wh, + ...res.wh, + }; } // console.log("positioning based on constraints", { wh, constraint, child }); @@ -430,6 +351,139 @@ function stackChild({ }); } +/** + * calculates the position & constraints based on the input. + * @param + * @returns + */ +function handlePositioning({ + constraints, + pos, + width, + height, + containerWidth, + containerHeight, +}: { + constraints: Constraints; + pos: { l: number; r: number; t: number; b: number; x: number; y: number }; + width: number; + height: number; + containerWidth: number; + containerHeight: number; +}): { + constraint; + wh: { + width?: number; + height?: number; + }; +} { + const constraint = { + left: undefined, + top: undefined, + right: undefined, + bottom: undefined, + }; + const wh = { width, height }; + + switch (constraints.horizontal) { + case "MIN": + constraint.left = pos.l; + break; + case "MAX": + constraint.right = pos.r; + break; + case "SCALE": /** scale fallbacks to stretch */ + case "STRETCH": + constraint.left = pos.l; + constraint.right = pos.r; + wh.width = undefined; // no fixed width + break; + case "CENTER": + const half_w = width / 2; + const centerdiff = + // center of og + half_w + + pos.x - + // center of frame + containerWidth / 2; + constraint.left = { + type: "calc", + operations: { + type: "op", + left: { + type: "calc", + operations: { + type: "op", + left: "50%", + op: "+", + right: centerdiff, + }, + }, + op: "-", // this part is different + right: half_w, + }, + }; + // --- we can also specify the right, but left is enough. + // constraint.right = { + // type: "calc", + // operations: { + // left: { + // type: "calc", + // operations: { left: "50%", op: "+", right: centerdiff }, + // }, + // op: "+", // this part is different + // right: half, + // }, + // }; + break; + } + switch (constraints.vertical) { + case "MIN": + constraint.top = pos.t; + break; + case "MAX": + constraint.bottom = pos.b; + break; + case "SCALE": /** scale fallbacks to stretch */ + case "STRETCH": + constraint.top = pos.t; + constraint.bottom = pos.b; + wh.height = undefined; + break; + case "CENTER": + const half_height = height / 2; + const container_snapshot_center = containerHeight / 2; + const child_snapshot_center = half_height + pos.y; + + const centerdiff = + // center of og + child_snapshot_center - + // center of frame + container_snapshot_center; + + constraint.top = { + type: "calc", + operations: { + type: "op", + left: { + type: "calc", + operations: { + type: "op", + left: "50%", + op: "+", + right: centerdiff, + }, + }, + op: "-", // this part is different + right: half_height, + }, + }; + break; + } + + return { constraint, wh }; +} + function fromGroup( group: nodes.ReflectGroupNode, children: RuntimeChildrenInput, diff --git a/packages/designto-token/token-text/index.ts b/packages/designto-token/token-text/index.ts index 5f5db2fc..57bda4e7 100644 --- a/packages/designto-token/token-text/index.ts +++ b/packages/designto-token/token-text/index.ts @@ -47,6 +47,7 @@ export function fromText(node: nodes.ReflectTextNode): RenderedText { color: node.primaryColor, lineHeight: node.lineHeight, letterSpacing: node.letterSpacing, + textTransform: node.textCase, textShadow: node.shadows as TextShadowManifest[], }), ...wh, diff --git a/packages/designto-web/tokens-to-web-widget/compose-wrapped-with-rotation.ts b/packages/designto-web/tokens-to-web-widget/compose-wrapped-with-rotation.ts index 0f953325..110303b2 100644 --- a/packages/designto-web/tokens-to-web-widget/compose-wrapped-with-rotation.ts +++ b/packages/designto-web/tokens-to-web-widget/compose-wrapped-with-rotation.ts @@ -7,8 +7,19 @@ export function compose_wrapped_with_rotation( child_composer: Composer ) { const child = child_composer(widget.child); + + const r = widget.rotation; + // don't apply rotation if it's 0 + if (Math.abs(r) === 360 || Math.abs(r) === 360) { + return child; + } + child.extendStyle({ - transform: css.rotation(widget.rotation), + // rotation data needs to be inverted + transform: css.rotation(-widget.rotation), + // this is where the figma's rotation data is originated from. + // see docs/figma-rotation.md + "transform-origin": "top left", }); return child; }