@cntrl-site/sdk-nextjs 1.9.138 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ <component name="ProjectCodeStyleConfiguration">
2
+ <state>
3
+ <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
4
+ </state>
5
+ </component>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/sdk-nextjs.iml" filepath="$PROJECT_DIR$/.idea/sdk-nextjs.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/temp" />
6
+ <excludeFolder url="file://$MODULE_DIR$/.tmp" />
7
+ <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
+ </content>
9
+ <orderEntry type="inheritedJdk" />
10
+ <orderEntry type="sourceFolder" forTests="false" />
11
+ </component>
12
+ </module>
package/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
@@ -22,6 +22,7 @@ const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
22
22
  const RichTextGeometryController_1 = require("../../../ItemGeometry/RichTextGeometryController");
23
23
  const RichTextConverter_1 = require("../../../utils/RichTextConverter/RichTextConverter");
24
24
  const getHeadingTag_1 = require("../../../utils/getHeadingTag");
25
+ const detectLang_1 = require("../../../utils/detectLang");
25
26
  const RichTextItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
26
27
  var _a, _b, _c, _d, _e;
27
28
  const reactId = (0, react_1.useId)();
@@ -29,7 +30,8 @@ const RichTextItem = ({ item, sectionId, onResize, interactionCtrl, onVisibility
29
30
  const [ref, setRef] = (0, react_1.useState)(null);
30
31
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
31
32
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
32
- const { blur: itemBlur, wordSpacing: itemWordSpacing, letterSpacing: itemLetterSpacing, color: itemColor, fontSize, lineHeight, xSizing } = (0, useRichTextItemValues_1.useRichTextItemValues)(item, sectionId);
33
+ const { blur: itemBlur, wordSpacing: itemWordSpacing, letterSpacing: itemLetterSpacing, color: itemColor, fontSize, lineHeight, xSizing, columns, columnGutter, hyphens } = (0, useRichTextItemValues_1.useRichTextItemValues)(item, sectionId);
34
+ const columnsEnabled = (columns !== null && columns !== void 0 ? columns : 1) > 1;
33
35
  const layoutValues = [item.area, item.layoutParams];
34
36
  const exemplary = (0, useExemplary_1.useExemplary)();
35
37
  const { layoutId } = (0, useCurrentLayout_1.useCurrentLayout)();
@@ -58,11 +60,16 @@ const RichTextItem = ({ item, sectionId, onResize, interactionCtrl, onVisibility
58
60
  const isInteractive = colorAlpha !== 0 || hasVisibleRangeColors;
59
61
  const [content, styles] = (0, useRichTextItem_1.useRichTextItem)(item);
60
62
  const Wrapper = ((_e = (0, getHeadingTag_1.getHeadingTag)(item)) !== null && _e !== void 0 ? _e : 'div');
63
+ const lang = (0, react_1.useMemo)(() => (0, detectLang_1.detectLang)(item.commonParams.text), [item.commonParams.text]);
61
64
  (0, react_1.useEffect)(() => {
62
65
  onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(isInteractive);
63
66
  }, [isInteractive, onVisibilityChange]);
64
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Wrapper, { ref: setRef, className: `rich-text-wrapper-${item.id}${layoutId ? '' : ` ${RichTextConverter_1.RICH_TEXT_LAYOUT_PENDING_CLASS}`}`, style: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), (textColor ? { color: `${textColor.fmt('rgba')}` } : {})), (angle !== undefined ? { transform: `rotate(${angle}deg)` } : {})), (letterSpacing !== undefined ? { letterSpacing: `${letterSpacing * exemplary}px` } : {})), (wordSpacing !== undefined ? { wordSpacing: `${wordSpacing * exemplary}px` } : {})), (fontSize !== undefined ? { fontSize: `${Math.round(fontSize * exemplary)}px` } : {})), (lineHeight !== undefined ? { lineHeight: `${lineHeight * exemplary}px` } : {})), { willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset', transition }), children: content }), (0, jsx_runtime_1.jsxs)(style_1.default, { id: id, children: [styles, `${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([area, layoutParams]) => {
67
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Wrapper, { lang: lang, ref: setRef, className: `rich-text-wrapper-${item.id}${layoutId ? '' : ` ${RichTextConverter_1.RICH_TEXT_LAYOUT_PENDING_CLASS}`}`, style: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), (textColor ? { color: `${textColor.fmt('rgba')}` } : {})), (angle !== undefined ? { transform: `rotate(${angle}deg)` } : {})), (letterSpacing !== undefined ? { letterSpacing: `${letterSpacing * exemplary}px` } : {})), (wordSpacing !== undefined ? { wordSpacing: `${wordSpacing * exemplary}px` } : {})), (fontSize !== undefined ? { fontSize: `${Math.round(fontSize * exemplary)}px` } : {})), (lineHeight !== undefined ? { lineHeight: `${lineHeight * exemplary}px` } : {})), (columnsEnabled ? { columnCount: columns, columnFill: 'balance' } : {})), (columnsEnabled && columnGutter !== undefined ? { columnGap: `${Math.round(columnGutter * exemplary)}px` } : {})), (hyphens !== undefined ? { hyphens, WebkitHyphens: hyphens } : {})), { willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset', transition }), children: content }), (0, jsx_runtime_1.jsxs)(style_1.default, { id: id, children: [styles, `${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([area, layoutParams]) => {
68
+ var _a, _b, _c;
65
69
  const color = color_1.CntrlColor.parse(layoutParams.color);
70
+ const columns = (_a = layoutParams.columns) !== null && _a !== void 0 ? _a : 1;
71
+ const columnsEnabled = columns > 1;
72
+ const hyphens = (_b = layoutParams.hyphens) !== null && _b !== void 0 ? _b : sdk_1.Hyphens.None;
66
73
  return (`
67
74
  .rich-text-wrapper-${item.id} {
68
75
  margin: 0;
@@ -79,6 +86,17 @@ const RichTextItem = ({ item, sectionId, onResize, interactionCtrl, onVisibility
79
86
  transform: rotate(${area.angle}deg);
80
87
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
81
88
  text-transform: ${layoutParams.textTransform};
89
+ column-count: ${columnsEnabled ? columns : 'unset'};
90
+ column-gap: ${columnsEnabled ? `${((_c = layoutParams.columnGutter) !== null && _c !== void 0 ? _c : 0) * 100}vw` : 'unset'};
91
+ column-fill: ${columnsEnabled ? 'balance' : 'unset'};
92
+ hyphens: ${hyphens};
93
+ -webkit-hyphens: ${hyphens};
94
+ hyphenate-limit-chars: 6 3 2;
95
+ hyphenate-limit-lines: 2;
96
+ hyphenate-limit-last: always;
97
+ hyphenate-limit-zone: 8%;
98
+ -webkit-hyphenate-limit-before: 3;
99
+ -webkit-hyphenate-limit-after: 2;
82
100
  ${layoutParams.blur !== 0 ? 'will-change: transform;' : ''}
83
101
  }
84
102
  @supports not (color: oklch(42% 0.3 90 / 1)) {
@@ -34,6 +34,9 @@ const useRichTextItemValues = (item, sectionId) => {
34
34
  const fontSize = layoutId ? item.layoutParams[layoutId].fontSize : undefined;
35
35
  const lineHeight = layoutId ? item.layoutParams[layoutId].lineHeight : undefined;
36
36
  const xSizing = layoutId ? item.layoutParams[layoutId].sizing : undefined;
37
- return { blur, letterSpacing, wordSpacing, color, fontSize, lineHeight, xSizing };
37
+ const columns = layoutId ? item.layoutParams[layoutId].columns : undefined;
38
+ const columnGutter = layoutId ? item.layoutParams[layoutId].columnGutter : undefined;
39
+ const hyphens = layoutId ? item.layoutParams[layoutId].hyphens : undefined;
40
+ return { blur, letterSpacing, wordSpacing, color, fontSize, lineHeight, xSizing, columns, columnGutter, hyphens };
38
41
  };
39
42
  exports.useRichTextItemValues = useRichTextItemValues;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectLang = void 0;
7
+ const franc_min_1 = __importDefault(require("franc-min"));
8
+ // Maps the ISO 639-3 codes franc-min can detect to their ISO 639-1
9
+ // equivalent, since `lang` in the browser is conventionally two letters.
10
+ // Codes with no ISO 639-1 equivalent are passed through as-is — a bare
11
+ // ISO 639-3 code is still a valid BCP 47 language tag.
12
+ const ISO_639_3_TO_1 = {
13
+ arb: 'ar',
14
+ azj: 'az',
15
+ bel: 'be',
16
+ bos: 'bs',
17
+ bul: 'bg',
18
+ ces: 'cs',
19
+ deu: 'de',
20
+ eng: 'en',
21
+ fas: 'fa',
22
+ fra: 'fr',
23
+ hau: 'ha',
24
+ hin: 'hi',
25
+ hrv: 'hr',
26
+ hun: 'hu',
27
+ ibo: 'ig',
28
+ ind: 'id',
29
+ ita: 'it',
30
+ jav: 'jv',
31
+ kaz: 'kk',
32
+ kin: 'rw',
33
+ lin: 'ln',
34
+ mar: 'mr',
35
+ nep: 'ne',
36
+ nld: 'nl',
37
+ nya: 'ny',
38
+ pol: 'pl',
39
+ por: 'pt',
40
+ ron: 'ro',
41
+ run: 'rn',
42
+ rus: 'ru',
43
+ som: 'so',
44
+ spa: 'es',
45
+ srp: 'sr',
46
+ sun: 'su',
47
+ swe: 'sv',
48
+ swh: 'sw',
49
+ tgl: 'tl',
50
+ tur: 'tr',
51
+ ukr: 'uk',
52
+ urd: 'ur',
53
+ uzn: 'uz',
54
+ vie: 'vi',
55
+ yor: 'yo',
56
+ zlm: 'ms',
57
+ zul: 'zu'
58
+ };
59
+ // Below this many characters franc-min's own trigram model is unreliable
60
+ // (headings, labels, short captions) — leave `lang` unset so it inherits
61
+ // from the document instead of guessing wrong.
62
+ const MIN_DETECTABLE_LENGTH = 20;
63
+ function detectLang(text) {
64
+ var _a;
65
+ if (!text)
66
+ return undefined;
67
+ const trimmed = text.trim();
68
+ if (trimmed.length < MIN_DETECTABLE_LENGTH)
69
+ return undefined;
70
+ const code3 = (0, franc_min_1.default)(trimmed, { minLength: MIN_DETECTABLE_LENGTH });
71
+ if (code3 === 'und')
72
+ return undefined;
73
+ return (_a = ISO_639_3_TO_1[code3]) !== null && _a !== void 0 ? _a : code3;
74
+ }
75
+ exports.detectLang = detectLang;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cntrl-site/sdk-nextjs",
3
- "version": "1.9.138",
3
+ "version": "1.10.0",
4
4
  "description": "SDK for Next.js",
5
5
  "author": "arsen@momdesign.nyc",
6
6
  "license": "MIT",
@@ -33,9 +33,10 @@
33
33
  "@cntrl-site/color": "^1.0.0",
34
34
  "@cntrl-site/components": "^1.0.85",
35
35
  "@cntrl-site/effects": "^1.4.2",
36
- "@cntrl-site/sdk": "^1.28.9",
36
+ "@cntrl-site/sdk": "^1.28.10",
37
37
  "@types/vimeo__player": "^2.18.0",
38
38
  "@vimeo/player": "^2.25.0",
39
+ "franc-min": "^5.0.0",
39
40
  "html-react-parser": "^3.0.1",
40
41
  "resize-observer-polyfill": "^1.5.1",
41
42
  "styled-jsx": "^5.0.2"
@@ -1,6 +1,6 @@
1
1
  import { FC, useEffect, useId, useMemo, useState } from 'react';
2
2
  import { CntrlColor } from '@cntrl-site/color';
3
- import { getLayoutStyles, RichTextItem as TRichTextItem } from '@cntrl-site/sdk';
3
+ import { getLayoutStyles, Hyphens, RichTextItem as TRichTextItem } from '@cntrl-site/sdk';
4
4
  import JSXStyle from 'styled-jsx/style';
5
5
  import { ItemProps } from '../Item';
6
6
  import { useRichTextItem } from './useRichTextItem';
@@ -16,6 +16,7 @@ import { useItemGeometry } from '../../../ItemGeometry/useItemGeometry';
16
16
  import { RichTextGeometryController } from '../../../ItemGeometry/RichTextGeometryController';
17
17
  import { RICH_TEXT_LAYOUT_PENDING_CLASS } from '../../../utils/RichTextConverter/RichTextConverter';
18
18
  import { getHeadingTag } from '../../../utils/getHeadingTag';
19
+ import { detectLang } from '../../../utils/detectLang';
19
20
 
20
21
  export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
21
22
  const reactId = useId();
@@ -30,8 +31,12 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
30
31
  color: itemColor,
31
32
  fontSize,
32
33
  lineHeight,
33
- xSizing
34
+ xSizing,
35
+ columns,
36
+ columnGutter,
37
+ hyphens
34
38
  } = useRichTextItemValues(item, sectionId);
39
+ const columnsEnabled = (columns ?? 1) > 1;
35
40
  const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
36
41
  const exemplary = useExemplary();
37
42
  const { layoutId } = useCurrentLayout();
@@ -59,6 +64,7 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
59
64
  const isInteractive = colorAlpha !== 0 || hasVisibleRangeColors;
60
65
  const [content, styles] = useRichTextItem(item);
61
66
  const Wrapper = (getHeadingTag(item) ?? 'div') as 'div';
67
+ const lang = useMemo(() => detectLang(item.commonParams.text), [item.commonParams.text]);
62
68
  useEffect(() => {
63
69
  onVisibilityChange?.(isInteractive);
64
70
  }, [isInteractive, onVisibilityChange]);
@@ -66,6 +72,7 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
66
72
  return (
67
73
  <>
68
74
  <Wrapper
75
+ lang={lang}
69
76
  ref={setRef}
70
77
  className={`rich-text-wrapper-${item.id}${layoutId ? '' : ` ${RICH_TEXT_LAYOUT_PENDING_CLASS}`}`}
71
78
  style={{
@@ -76,6 +83,9 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
76
83
  ...(wordSpacing !== undefined ? { wordSpacing: `${wordSpacing as number * exemplary}px` } : {}),
77
84
  ...(fontSize !== undefined ? { fontSize: `${Math.round(fontSize * exemplary)}px` } : {}),
78
85
  ...(lineHeight !== undefined ? { lineHeight: `${lineHeight * exemplary}px` } : {}),
86
+ ...(columnsEnabled ? { columnCount: columns, columnFill: 'balance' as const } : {}),
87
+ ...(columnsEnabled && columnGutter !== undefined ? { columnGap: `${Math.round(columnGutter * exemplary)}px` } : {}),
88
+ ...(hyphens !== undefined ? { hyphens, WebkitHyphens: hyphens } : {}),
79
89
  willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset',
80
90
  transition
81
91
  }}
@@ -86,6 +96,9 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
86
96
  {styles}
87
97
  {`${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
88
98
  const color = CntrlColor.parse(layoutParams.color);
99
+ const columns = layoutParams.columns ?? 1;
100
+ const columnsEnabled = columns > 1;
101
+ const hyphens = layoutParams.hyphens ?? Hyphens.None;
89
102
  return (`
90
103
  .rich-text-wrapper-${item.id} {
91
104
  margin: 0;
@@ -102,6 +115,17 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
102
115
  transform: rotate(${area.angle}deg);
103
116
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
104
117
  text-transform: ${layoutParams.textTransform};
118
+ column-count: ${columnsEnabled ? columns : 'unset'};
119
+ column-gap: ${columnsEnabled ? `${(layoutParams.columnGutter ?? 0) * 100}vw` : 'unset'};
120
+ column-fill: ${columnsEnabled ? 'balance' : 'unset'};
121
+ hyphens: ${hyphens};
122
+ -webkit-hyphens: ${hyphens};
123
+ hyphenate-limit-chars: 6 3 2;
124
+ hyphenate-limit-lines: 2;
125
+ hyphenate-limit-last: always;
126
+ hyphenate-limit-zone: 8%;
127
+ -webkit-hyphenate-limit-before: 3;
128
+ -webkit-hyphenate-limit-after: 2;
105
129
  ${layoutParams.blur !== 0 ? 'will-change: transform;' : ''}
106
130
  }
107
131
  @supports not (color: oklch(42% 0.3 90 / 1)) {
@@ -61,6 +61,9 @@ export const useRichTextItemValues = (item: RichTextItem, sectionId: string) =>
61
61
  const fontSize = layoutId ? item.layoutParams[layoutId].fontSize : undefined;
62
62
  const lineHeight = layoutId ? item.layoutParams[layoutId].lineHeight : undefined;
63
63
  const xSizing = layoutId ? item.layoutParams[layoutId].sizing : undefined;
64
+ const columns = layoutId ? item.layoutParams[layoutId].columns : undefined;
65
+ const columnGutter = layoutId ? item.layoutParams[layoutId].columnGutter : undefined;
66
+ const hyphens = layoutId ? item.layoutParams[layoutId].hyphens : undefined;
64
67
 
65
- return { blur, letterSpacing, wordSpacing, color, fontSize, lineHeight, xSizing };
68
+ return { blur, letterSpacing, wordSpacing, color, fontSize, lineHeight, xSizing, columns, columnGutter, hyphens };
66
69
  };
@@ -5,3 +5,8 @@ declare module '@cntrl-site/components/utils' {
5
5
  export function getPositionStyles(position: any): Record<string, any>;
6
6
  export function getImageRect(rect: any): Record<string, any>;
7
7
  }
8
+
9
+ declare module 'franc-min' {
10
+ function franc(text: string, options?: { minLength?: number; only?: string[]; blacklist?: string[] }): string;
11
+ export = franc;
12
+ }
@@ -0,0 +1,67 @@
1
+ import franc from 'franc-min';
2
+
3
+ // Maps the ISO 639-3 codes franc-min can detect to their ISO 639-1
4
+ // equivalent, since `lang` in the browser is conventionally two letters.
5
+ // Codes with no ISO 639-1 equivalent are passed through as-is — a bare
6
+ // ISO 639-3 code is still a valid BCP 47 language tag.
7
+ const ISO_639_3_TO_1: Record<string, string> = {
8
+ arb: 'ar',
9
+ azj: 'az',
10
+ bel: 'be',
11
+ bos: 'bs',
12
+ bul: 'bg',
13
+ ces: 'cs',
14
+ deu: 'de',
15
+ eng: 'en',
16
+ fas: 'fa',
17
+ fra: 'fr',
18
+ hau: 'ha',
19
+ hin: 'hi',
20
+ hrv: 'hr',
21
+ hun: 'hu',
22
+ ibo: 'ig',
23
+ ind: 'id',
24
+ ita: 'it',
25
+ jav: 'jv',
26
+ kaz: 'kk',
27
+ kin: 'rw',
28
+ lin: 'ln',
29
+ mar: 'mr',
30
+ nep: 'ne',
31
+ nld: 'nl',
32
+ nya: 'ny',
33
+ pol: 'pl',
34
+ por: 'pt',
35
+ ron: 'ro',
36
+ run: 'rn',
37
+ rus: 'ru',
38
+ som: 'so',
39
+ spa: 'es',
40
+ srp: 'sr',
41
+ sun: 'su',
42
+ swe: 'sv',
43
+ swh: 'sw',
44
+ tgl: 'tl',
45
+ tur: 'tr',
46
+ ukr: 'uk',
47
+ urd: 'ur',
48
+ uzn: 'uz',
49
+ vie: 'vi',
50
+ yor: 'yo',
51
+ zlm: 'ms',
52
+ zul: 'zu'
53
+ };
54
+
55
+ // Below this many characters franc-min's own trigram model is unreliable
56
+ // (headings, labels, short captions) — leave `lang` unset so it inherits
57
+ // from the document instead of guessing wrong.
58
+ const MIN_DETECTABLE_LENGTH = 20;
59
+
60
+ export function detectLang(text: string | undefined): string | undefined {
61
+ if (!text) return undefined;
62
+ const trimmed = text.trim();
63
+ if (trimmed.length < MIN_DETECTABLE_LENGTH) return undefined;
64
+ const code3 = franc(trimmed, { minLength: MIN_DETECTABLE_LENGTH });
65
+ if (code3 === 'und') return undefined;
66
+ return ISO_639_3_TO_1[code3] ?? code3;
67
+ }