/ ink / styles.ts
styles.ts
  1  import {
  2    LayoutAlign,
  3    LayoutDisplay,
  4    LayoutEdge,
  5    LayoutFlexDirection,
  6    LayoutGutter,
  7    LayoutJustify,
  8    type LayoutNode,
  9    LayoutOverflow,
 10    LayoutPositionType,
 11    LayoutWrap,
 12  } from './layout/node.js'
 13  import type { BorderStyle, BorderTextOptions } from './render-border.js'
 14  
 15  export type RGBColor = `rgb(${number},${number},${number})`
 16  export type HexColor = `#${string}`
 17  export type Ansi256Color = `ansi256(${number})`
 18  export type AnsiColor =
 19    | 'ansi:black'
 20    | 'ansi:red'
 21    | 'ansi:green'
 22    | 'ansi:yellow'
 23    | 'ansi:blue'
 24    | 'ansi:magenta'
 25    | 'ansi:cyan'
 26    | 'ansi:white'
 27    | 'ansi:blackBright'
 28    | 'ansi:redBright'
 29    | 'ansi:greenBright'
 30    | 'ansi:yellowBright'
 31    | 'ansi:blueBright'
 32    | 'ansi:magentaBright'
 33    | 'ansi:cyanBright'
 34    | 'ansi:whiteBright'
 35  
 36  /** Raw color value - not a theme key */
 37  export type Color = RGBColor | HexColor | Ansi256Color | AnsiColor
 38  
 39  /**
 40   * Structured text styling properties.
 41   * Used to style text without relying on ANSI string transforms.
 42   * Colors are raw values - theme resolution happens at the component layer.
 43   */
 44  export type TextStyles = {
 45    readonly color?: Color
 46    readonly backgroundColor?: Color
 47    readonly dim?: boolean
 48    readonly bold?: boolean
 49    readonly italic?: boolean
 50    readonly underline?: boolean
 51    readonly strikethrough?: boolean
 52    readonly inverse?: boolean
 53  }
 54  
 55  export type Styles = {
 56    readonly textWrap?:
 57      | 'wrap'
 58      | 'wrap-trim'
 59      | 'end'
 60      | 'middle'
 61      | 'truncate-end'
 62      | 'truncate'
 63      | 'truncate-middle'
 64      | 'truncate-start'
 65  
 66    readonly position?: 'absolute' | 'relative'
 67    readonly top?: number | `${number}%`
 68    readonly bottom?: number | `${number}%`
 69    readonly left?: number | `${number}%`
 70    readonly right?: number | `${number}%`
 71  
 72    /**
 73     * Size of the gap between an element's columns.
 74     */
 75    readonly columnGap?: number
 76  
 77    /**
 78     * Size of the gap between element's rows.
 79     */
 80    readonly rowGap?: number
 81  
 82    /**
 83     * Size of the gap between an element's columns and rows. Shorthand for `columnGap` and `rowGap`.
 84     */
 85    readonly gap?: number
 86  
 87    /**
 88     * Margin on all sides. Equivalent to setting `marginTop`, `marginBottom`, `marginLeft` and `marginRight`.
 89     */
 90    readonly margin?: number
 91  
 92    /**
 93     * Horizontal margin. Equivalent to setting `marginLeft` and `marginRight`.
 94     */
 95    readonly marginX?: number
 96  
 97    /**
 98     * Vertical margin. Equivalent to setting `marginTop` and `marginBottom`.
 99     */
100    readonly marginY?: number
101  
102    /**
103     * Top margin.
104     */
105    readonly marginTop?: number
106  
107    /**
108     * Bottom margin.
109     */
110    readonly marginBottom?: number
111  
112    /**
113     * Left margin.
114     */
115    readonly marginLeft?: number
116  
117    /**
118     * Right margin.
119     */
120    readonly marginRight?: number
121  
122    /**
123     * Padding on all sides. Equivalent to setting `paddingTop`, `paddingBottom`, `paddingLeft` and `paddingRight`.
124     */
125    readonly padding?: number
126  
127    /**
128     * Horizontal padding. Equivalent to setting `paddingLeft` and `paddingRight`.
129     */
130    readonly paddingX?: number
131  
132    /**
133     * Vertical padding. Equivalent to setting `paddingTop` and `paddingBottom`.
134     */
135    readonly paddingY?: number
136  
137    /**
138     * Top padding.
139     */
140    readonly paddingTop?: number
141  
142    /**
143     * Bottom padding.
144     */
145    readonly paddingBottom?: number
146  
147    /**
148     * Left padding.
149     */
150    readonly paddingLeft?: number
151  
152    /**
153     * Right padding.
154     */
155    readonly paddingRight?: number
156  
157    /**
158     * This property defines the ability for a flex item to grow if necessary.
159     * See [flex-grow](https://css-tricks.com/almanac/properties/f/flex-grow/).
160     */
161    readonly flexGrow?: number
162  
163    /**
164     * It specifies the “flex shrink factor”, which determines how much the flex item will shrink relative to the rest of the flex items in the flex container when there isn’t enough space on the row.
165     * See [flex-shrink](https://css-tricks.com/almanac/properties/f/flex-shrink/).
166     */
167    readonly flexShrink?: number
168  
169    /**
170     * It establishes the main-axis, thus defining the direction flex items are placed in the flex container.
171     * See [flex-direction](https://css-tricks.com/almanac/properties/f/flex-direction/).
172     */
173    readonly flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse'
174  
175    /**
176     * It specifies the initial size of the flex item, before any available space is distributed according to the flex factors.
177     * See [flex-basis](https://css-tricks.com/almanac/properties/f/flex-basis/).
178     */
179    readonly flexBasis?: number | string
180  
181    /**
182     * It defines whether the flex items are forced in a single line or can be flowed into multiple lines. If set to multiple lines, it also defines the cross-axis which determines the direction new lines are stacked in.
183     * See [flex-wrap](https://css-tricks.com/almanac/properties/f/flex-wrap/).
184     */
185    readonly flexWrap?: 'nowrap' | 'wrap' | 'wrap-reverse'
186  
187    /**
188     * The align-items property defines the default behavior for how items are laid out along the cross axis (perpendicular to the main axis).
189     * See [align-items](https://css-tricks.com/almanac/properties/a/align-items/).
190     */
191    readonly alignItems?: 'flex-start' | 'center' | 'flex-end' | 'stretch'
192  
193    /**
194     * It makes possible to override the align-items value for specific flex items.
195     * See [align-self](https://css-tricks.com/almanac/properties/a/align-self/).
196     */
197    readonly alignSelf?: 'flex-start' | 'center' | 'flex-end' | 'auto'
198  
199    /**
200     * It defines the alignment along the main axis.
201     * See [justify-content](https://css-tricks.com/almanac/properties/j/justify-content/).
202     */
203    readonly justifyContent?:
204      | 'flex-start'
205      | 'flex-end'
206      | 'space-between'
207      | 'space-around'
208      | 'space-evenly'
209      | 'center'
210  
211    /**
212     * Width of the element in spaces.
213     * You can also set it in percent, which will calculate the width based on the width of parent element.
214     */
215    readonly width?: number | string
216  
217    /**
218     * Height of the element in lines (rows).
219     * You can also set it in percent, which will calculate the height based on the height of parent element.
220     */
221    readonly height?: number | string
222  
223    /**
224     * Sets a minimum width of the element.
225     */
226    readonly minWidth?: number | string
227  
228    /**
229     * Sets a minimum height of the element.
230     */
231    readonly minHeight?: number | string
232  
233    /**
234     * Sets a maximum width of the element.
235     */
236    readonly maxWidth?: number | string
237  
238    /**
239     * Sets a maximum height of the element.
240     */
241    readonly maxHeight?: number | string
242  
243    /**
244     * Set this property to `none` to hide the element.
245     */
246    readonly display?: 'flex' | 'none'
247  
248    /**
249     * Add a border with a specified style.
250     * If `borderStyle` is `undefined` (which it is by default), no border will be added.
251     */
252    readonly borderStyle?: BorderStyle
253  
254    /**
255     * Determines whether top border is visible.
256     *
257     * @default true
258     */
259    readonly borderTop?: boolean
260  
261    /**
262     * Determines whether bottom border is visible.
263     *
264     * @default true
265     */
266    readonly borderBottom?: boolean
267  
268    /**
269     * Determines whether left border is visible.
270     *
271     * @default true
272     */
273    readonly borderLeft?: boolean
274  
275    /**
276     * Determines whether right border is visible.
277     *
278     * @default true
279     */
280    readonly borderRight?: boolean
281  
282    /**
283     * Change border color.
284     * Shorthand for setting `borderTopColor`, `borderRightColor`, `borderBottomColor` and `borderLeftColor`.
285     */
286    readonly borderColor?: Color
287  
288    /**
289     * Change top border color.
290     * Accepts raw color values (rgb, hex, ansi).
291     */
292    readonly borderTopColor?: Color
293  
294    /**
295     * Change bottom border color.
296     * Accepts raw color values (rgb, hex, ansi).
297     */
298    readonly borderBottomColor?: Color
299  
300    /**
301     * Change left border color.
302     * Accepts raw color values (rgb, hex, ansi).
303     */
304    readonly borderLeftColor?: Color
305  
306    /**
307     * Change right border color.
308     * Accepts raw color values (rgb, hex, ansi).
309     */
310    readonly borderRightColor?: Color
311  
312    /**
313     * Dim the border color.
314     * Shorthand for setting `borderTopDimColor`, `borderBottomDimColor`, `borderLeftDimColor` and `borderRightDimColor`.
315     *
316     * @default false
317     */
318    readonly borderDimColor?: boolean
319  
320    /**
321     * Dim the top border color.
322     *
323     * @default false
324     */
325    readonly borderTopDimColor?: boolean
326  
327    /**
328     * Dim the bottom border color.
329     *
330     * @default false
331     */
332    readonly borderBottomDimColor?: boolean
333  
334    /**
335     * Dim the left border color.
336     *
337     * @default false
338     */
339    readonly borderLeftDimColor?: boolean
340  
341    /**
342     * Dim the right border color.
343     *
344     * @default false
345     */
346    readonly borderRightDimColor?: boolean
347  
348    /**
349     * Add text within the border. Only applies to top or bottom borders.
350     */
351    readonly borderText?: BorderTextOptions
352  
353    /**
354     * Background color for the box. Fills the interior with background-colored
355     * spaces and is inherited by child text nodes as their default background.
356     */
357    readonly backgroundColor?: Color
358  
359    /**
360     * Fill the box's interior (padding included) with spaces before
361     * rendering children, so nothing behind it shows through. Like
362     * `backgroundColor` but without emitting any SGR — the terminal's
363     * default background is used. Useful for absolute-positioned overlays
364     * where Box padding/gaps would otherwise be transparent.
365     */
366    readonly opaque?: boolean
367  
368    /**
369     * Behavior for an element's overflow in both directions.
370     * 'scroll' constrains the container's size (children do not expand it)
371     * and enables scrollTop-based virtualized scrolling at render time.
372     *
373     * @default 'visible'
374     */
375    readonly overflow?: 'visible' | 'hidden' | 'scroll'
376  
377    /**
378     * Behavior for an element's overflow in horizontal direction.
379     *
380     * @default 'visible'
381     */
382    readonly overflowX?: 'visible' | 'hidden' | 'scroll'
383  
384    /**
385     * Behavior for an element's overflow in vertical direction.
386     *
387     * @default 'visible'
388     */
389    readonly overflowY?: 'visible' | 'hidden' | 'scroll'
390  
391    /**
392     * Exclude this box's cells from text selection in fullscreen mode.
393     * Cells inside this region are skipped by both the selection highlight
394     * and the copied text — useful for fencing off gutters (line numbers,
395     * diff sigils) so click-drag over a diff yields clean copyable code.
396     * Only affects alt-screen text selection; no-op otherwise.
397     *
398     * `'from-left-edge'` extends the exclusion from column 0 to the box's
399     * right edge for every row it occupies — this covers any upstream
400     * indentation (tool message prefix, tree lines) so a multi-row drag
401     * doesn't pick up leading whitespace from middle rows.
402     */
403    readonly noSelect?: boolean | 'from-left-edge'
404  }
405  
406  const applyPositionStyles = (node: LayoutNode, style: Styles): void => {
407    if ('position' in style) {
408      node.setPositionType(
409        style.position === 'absolute'
410          ? LayoutPositionType.Absolute
411          : LayoutPositionType.Relative,
412      )
413    }
414    if ('top' in style) applyPositionEdge(node, 'top', style.top)
415    if ('bottom' in style) applyPositionEdge(node, 'bottom', style.bottom)
416    if ('left' in style) applyPositionEdge(node, 'left', style.left)
417    if ('right' in style) applyPositionEdge(node, 'right', style.right)
418  }
419  
420  function applyPositionEdge(
421    node: LayoutNode,
422    edge: 'top' | 'bottom' | 'left' | 'right',
423    v: number | `${number}%` | undefined,
424  ): void {
425    if (typeof v === 'string') {
426      node.setPositionPercent(edge, Number.parseInt(v, 10))
427    } else if (typeof v === 'number') {
428      node.setPosition(edge, v)
429    } else {
430      node.setPosition(edge, Number.NaN)
431    }
432  }
433  
434  const applyOverflowStyles = (node: LayoutNode, style: Styles): void => {
435    // Yoga's Overflow controls whether children expand the container.
436    // 'hidden' and 'scroll' both prevent expansion; 'scroll' additionally
437    // signals that the renderer should apply scrollTop translation.
438    // overflowX/Y are render-time concerns; for layout we use the union.
439    const y = style.overflowY ?? style.overflow
440    const x = style.overflowX ?? style.overflow
441    if (y === 'scroll' || x === 'scroll') {
442      node.setOverflow(LayoutOverflow.Scroll)
443    } else if (y === 'hidden' || x === 'hidden') {
444      node.setOverflow(LayoutOverflow.Hidden)
445    } else if (
446      'overflow' in style ||
447      'overflowX' in style ||
448      'overflowY' in style
449    ) {
450      node.setOverflow(LayoutOverflow.Visible)
451    }
452  }
453  
454  const applyMarginStyles = (node: LayoutNode, style: Styles): void => {
455    if ('margin' in style) {
456      node.setMargin(LayoutEdge.All, style.margin ?? 0)
457    }
458  
459    if ('marginX' in style) {
460      node.setMargin(LayoutEdge.Horizontal, style.marginX ?? 0)
461    }
462  
463    if ('marginY' in style) {
464      node.setMargin(LayoutEdge.Vertical, style.marginY ?? 0)
465    }
466  
467    if ('marginLeft' in style) {
468      node.setMargin(LayoutEdge.Start, style.marginLeft || 0)
469    }
470  
471    if ('marginRight' in style) {
472      node.setMargin(LayoutEdge.End, style.marginRight || 0)
473    }
474  
475    if ('marginTop' in style) {
476      node.setMargin(LayoutEdge.Top, style.marginTop || 0)
477    }
478  
479    if ('marginBottom' in style) {
480      node.setMargin(LayoutEdge.Bottom, style.marginBottom || 0)
481    }
482  }
483  
484  const applyPaddingStyles = (node: LayoutNode, style: Styles): void => {
485    if ('padding' in style) {
486      node.setPadding(LayoutEdge.All, style.padding ?? 0)
487    }
488  
489    if ('paddingX' in style) {
490      node.setPadding(LayoutEdge.Horizontal, style.paddingX ?? 0)
491    }
492  
493    if ('paddingY' in style) {
494      node.setPadding(LayoutEdge.Vertical, style.paddingY ?? 0)
495    }
496  
497    if ('paddingLeft' in style) {
498      node.setPadding(LayoutEdge.Left, style.paddingLeft || 0)
499    }
500  
501    if ('paddingRight' in style) {
502      node.setPadding(LayoutEdge.Right, style.paddingRight || 0)
503    }
504  
505    if ('paddingTop' in style) {
506      node.setPadding(LayoutEdge.Top, style.paddingTop || 0)
507    }
508  
509    if ('paddingBottom' in style) {
510      node.setPadding(LayoutEdge.Bottom, style.paddingBottom || 0)
511    }
512  }
513  
514  const applyFlexStyles = (node: LayoutNode, style: Styles): void => {
515    if ('flexGrow' in style) {
516      node.setFlexGrow(style.flexGrow ?? 0)
517    }
518  
519    if ('flexShrink' in style) {
520      node.setFlexShrink(
521        typeof style.flexShrink === 'number' ? style.flexShrink : 1,
522      )
523    }
524  
525    if ('flexWrap' in style) {
526      if (style.flexWrap === 'nowrap') {
527        node.setFlexWrap(LayoutWrap.NoWrap)
528      }
529  
530      if (style.flexWrap === 'wrap') {
531        node.setFlexWrap(LayoutWrap.Wrap)
532      }
533  
534      if (style.flexWrap === 'wrap-reverse') {
535        node.setFlexWrap(LayoutWrap.WrapReverse)
536      }
537    }
538  
539    if ('flexDirection' in style) {
540      if (style.flexDirection === 'row') {
541        node.setFlexDirection(LayoutFlexDirection.Row)
542      }
543  
544      if (style.flexDirection === 'row-reverse') {
545        node.setFlexDirection(LayoutFlexDirection.RowReverse)
546      }
547  
548      if (style.flexDirection === 'column') {
549        node.setFlexDirection(LayoutFlexDirection.Column)
550      }
551  
552      if (style.flexDirection === 'column-reverse') {
553        node.setFlexDirection(LayoutFlexDirection.ColumnReverse)
554      }
555    }
556  
557    if ('flexBasis' in style) {
558      if (typeof style.flexBasis === 'number') {
559        node.setFlexBasis(style.flexBasis)
560      } else if (typeof style.flexBasis === 'string') {
561        node.setFlexBasisPercent(Number.parseInt(style.flexBasis, 10))
562      } else {
563        node.setFlexBasis(Number.NaN)
564      }
565    }
566  
567    if ('alignItems' in style) {
568      if (style.alignItems === 'stretch' || !style.alignItems) {
569        node.setAlignItems(LayoutAlign.Stretch)
570      }
571  
572      if (style.alignItems === 'flex-start') {
573        node.setAlignItems(LayoutAlign.FlexStart)
574      }
575  
576      if (style.alignItems === 'center') {
577        node.setAlignItems(LayoutAlign.Center)
578      }
579  
580      if (style.alignItems === 'flex-end') {
581        node.setAlignItems(LayoutAlign.FlexEnd)
582      }
583    }
584  
585    if ('alignSelf' in style) {
586      if (style.alignSelf === 'auto' || !style.alignSelf) {
587        node.setAlignSelf(LayoutAlign.Auto)
588      }
589  
590      if (style.alignSelf === 'flex-start') {
591        node.setAlignSelf(LayoutAlign.FlexStart)
592      }
593  
594      if (style.alignSelf === 'center') {
595        node.setAlignSelf(LayoutAlign.Center)
596      }
597  
598      if (style.alignSelf === 'flex-end') {
599        node.setAlignSelf(LayoutAlign.FlexEnd)
600      }
601    }
602  
603    if ('justifyContent' in style) {
604      if (style.justifyContent === 'flex-start' || !style.justifyContent) {
605        node.setJustifyContent(LayoutJustify.FlexStart)
606      }
607  
608      if (style.justifyContent === 'center') {
609        node.setJustifyContent(LayoutJustify.Center)
610      }
611  
612      if (style.justifyContent === 'flex-end') {
613        node.setJustifyContent(LayoutJustify.FlexEnd)
614      }
615  
616      if (style.justifyContent === 'space-between') {
617        node.setJustifyContent(LayoutJustify.SpaceBetween)
618      }
619  
620      if (style.justifyContent === 'space-around') {
621        node.setJustifyContent(LayoutJustify.SpaceAround)
622      }
623  
624      if (style.justifyContent === 'space-evenly') {
625        node.setJustifyContent(LayoutJustify.SpaceEvenly)
626      }
627    }
628  }
629  
630  const applyDimensionStyles = (node: LayoutNode, style: Styles): void => {
631    if ('width' in style) {
632      if (typeof style.width === 'number') {
633        node.setWidth(style.width)
634      } else if (typeof style.width === 'string') {
635        node.setWidthPercent(Number.parseInt(style.width, 10))
636      } else {
637        node.setWidthAuto()
638      }
639    }
640  
641    if ('height' in style) {
642      if (typeof style.height === 'number') {
643        node.setHeight(style.height)
644      } else if (typeof style.height === 'string') {
645        node.setHeightPercent(Number.parseInt(style.height, 10))
646      } else {
647        node.setHeightAuto()
648      }
649    }
650  
651    if ('minWidth' in style) {
652      if (typeof style.minWidth === 'string') {
653        node.setMinWidthPercent(Number.parseInt(style.minWidth, 10))
654      } else {
655        node.setMinWidth(style.minWidth ?? 0)
656      }
657    }
658  
659    if ('minHeight' in style) {
660      if (typeof style.minHeight === 'string') {
661        node.setMinHeightPercent(Number.parseInt(style.minHeight, 10))
662      } else {
663        node.setMinHeight(style.minHeight ?? 0)
664      }
665    }
666  
667    if ('maxWidth' in style) {
668      if (typeof style.maxWidth === 'string') {
669        node.setMaxWidthPercent(Number.parseInt(style.maxWidth, 10))
670      } else {
671        node.setMaxWidth(style.maxWidth ?? 0)
672      }
673    }
674  
675    if ('maxHeight' in style) {
676      if (typeof style.maxHeight === 'string') {
677        node.setMaxHeightPercent(Number.parseInt(style.maxHeight, 10))
678      } else {
679        node.setMaxHeight(style.maxHeight ?? 0)
680      }
681    }
682  }
683  
684  const applyDisplayStyles = (node: LayoutNode, style: Styles): void => {
685    if ('display' in style) {
686      node.setDisplay(
687        style.display === 'flex' ? LayoutDisplay.Flex : LayoutDisplay.None,
688      )
689    }
690  }
691  
692  const applyBorderStyles = (
693    node: LayoutNode,
694    style: Styles,
695    resolvedStyle?: Styles,
696  ): void => {
697    // resolvedStyle is the full current style (already set on the DOM node).
698    // style may be a diff with only changed properties. For border side props,
699    // we need the resolved value because `borderStyle` in a diff may not include
700    // unchanged border side values (e.g. borderTop stays false but isn't in the diff).
701    const resolved = resolvedStyle ?? style
702  
703    if ('borderStyle' in style) {
704      const borderWidth = style.borderStyle ? 1 : 0
705  
706      node.setBorder(
707        LayoutEdge.Top,
708        resolved.borderTop !== false ? borderWidth : 0,
709      )
710      node.setBorder(
711        LayoutEdge.Bottom,
712        resolved.borderBottom !== false ? borderWidth : 0,
713      )
714      node.setBorder(
715        LayoutEdge.Left,
716        resolved.borderLeft !== false ? borderWidth : 0,
717      )
718      node.setBorder(
719        LayoutEdge.Right,
720        resolved.borderRight !== false ? borderWidth : 0,
721      )
722    } else {
723      // Handle individual border property changes (when only borderX changes without borderStyle).
724      // Skip undefined values — they mean the prop was removed or never set,
725      // not that a border should be enabled.
726      if ('borderTop' in style && style.borderTop !== undefined) {
727        node.setBorder(LayoutEdge.Top, style.borderTop === false ? 0 : 1)
728      }
729      if ('borderBottom' in style && style.borderBottom !== undefined) {
730        node.setBorder(LayoutEdge.Bottom, style.borderBottom === false ? 0 : 1)
731      }
732      if ('borderLeft' in style && style.borderLeft !== undefined) {
733        node.setBorder(LayoutEdge.Left, style.borderLeft === false ? 0 : 1)
734      }
735      if ('borderRight' in style && style.borderRight !== undefined) {
736        node.setBorder(LayoutEdge.Right, style.borderRight === false ? 0 : 1)
737      }
738    }
739  }
740  
741  const applyGapStyles = (node: LayoutNode, style: Styles): void => {
742    if ('gap' in style) {
743      node.setGap(LayoutGutter.All, style.gap ?? 0)
744    }
745  
746    if ('columnGap' in style) {
747      node.setGap(LayoutGutter.Column, style.columnGap ?? 0)
748    }
749  
750    if ('rowGap' in style) {
751      node.setGap(LayoutGutter.Row, style.rowGap ?? 0)
752    }
753  }
754  
755  const styles = (
756    node: LayoutNode,
757    style: Styles = {},
758    resolvedStyle?: Styles,
759  ): void => {
760    applyPositionStyles(node, style)
761    applyOverflowStyles(node, style)
762    applyMarginStyles(node, style)
763    applyPaddingStyles(node, style)
764    applyFlexStyles(node, style)
765    applyDimensionStyles(node, style)
766    applyDisplayStyles(node, style)
767    applyBorderStyles(node, style, resolvedStyle)
768    applyGapStyles(node, style)
769  }
770  
771  export default styles