@cntrl-site/sdk-nextjs 1.10.9 → 1.11.0-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.
Files changed (46) hide show
  1. package/cntrl-site-sdk-nextjs-1.9.125.tgz +0 -0
  2. package/lib/components/StructuredBlockItem/BlockCaption/BlockCaption.js +35 -0
  3. package/lib/components/StructuredBlockItem/StructuredBlockItem.js +17 -42
  4. package/lib/components/StructuredBlockItem/StructuredCode/StructuredCode.js +75 -0
  5. package/lib/components/StructuredBlockItem/StructuredDivider/StructuredDivider.js +29 -0
  6. package/lib/components/StructuredBlockItem/StructuredEmbed/StructuredVimeoEmbed.js +89 -0
  7. package/lib/components/StructuredBlockItem/StructuredEmbed/StructuredYoutubeEmbed.js +97 -0
  8. package/lib/components/StructuredBlockItem/StructuredEmbed/embedFrameStyles.js +36 -0
  9. package/lib/components/StructuredBlockItem/StructuredHeaderImage/StructuredHeaderImage.js +43 -0
  10. package/lib/components/StructuredBlockItem/StructuredImage/StructuredImage.js +65 -0
  11. package/lib/components/StructuredBlockItem/StructuredRichText/StructuredRichText.js +46 -0
  12. package/lib/components/StructuredBlockItem/StructuredVideo/StructuredVideo.js +74 -0
  13. package/lib/components/StructuredBlockItem/blockPlacement.js +41 -0
  14. package/lib/components/StructuredBlockItem/blocksMap.js +29 -0
  15. package/lib/components/StructuredBlockItem/textStylesRule.js +46 -0
  16. package/lib/components/StructuredContent/StructuredContent.js +24 -17
  17. package/lib/components/StructuredContent/StructuredHeader.js +95 -0
  18. package/lib/components/items/EmbedVideoItem/VimeoEmbed.js +2 -15
  19. package/lib/utils/RichTextConverter/RichTextConverter.js +52 -14
  20. package/lib/utils/getVimeoEmbedUrl.js +18 -0
  21. package/package.json +2 -2
  22. package/src/components/StructuredBlockItem/BlockCaption/BlockCaption.tsx +42 -0
  23. package/src/components/StructuredBlockItem/StructuredBlockItem.tsx +23 -50
  24. package/src/components/StructuredBlockItem/StructuredCode/StructuredCode.tsx +86 -0
  25. package/src/components/StructuredBlockItem/StructuredDivider/StructuredDivider.tsx +31 -0
  26. package/src/components/StructuredBlockItem/StructuredEmbed/StructuredVimeoEmbed.tsx +101 -0
  27. package/src/components/StructuredBlockItem/StructuredEmbed/StructuredYoutubeEmbed.tsx +106 -0
  28. package/src/components/StructuredBlockItem/StructuredEmbed/embedFrameStyles.ts +32 -0
  29. package/src/components/StructuredBlockItem/StructuredHeaderImage/StructuredHeaderImage.tsx +43 -0
  30. package/src/components/StructuredBlockItem/StructuredImage/StructuredImage.tsx +75 -0
  31. package/src/components/StructuredBlockItem/StructuredRichText/StructuredRichText.tsx +53 -0
  32. package/src/components/StructuredBlockItem/StructuredVideo/StructuredVideo.tsx +106 -0
  33. package/src/components/StructuredBlockItem/blockPlacement.ts +39 -0
  34. package/src/components/StructuredBlockItem/blocksMap.ts +30 -0
  35. package/src/components/StructuredBlockItem/textStylesRule.ts +44 -0
  36. package/src/components/StructuredContent/StructuredContent.test.tsx +217 -0
  37. package/src/components/StructuredContent/StructuredContent.tsx +25 -19
  38. package/src/components/StructuredContent/StructuredHeader.tsx +119 -0
  39. package/src/components/items/EmbedVideoItem/VimeoEmbed.tsx +2 -14
  40. package/src/utils/RichTextConverter/RichTextConverter.test.tsx +67 -0
  41. package/src/utils/RichTextConverter/RichTextConverter.tsx +77 -14
  42. package/src/utils/getVimeoEmbedUrl.ts +16 -0
  43. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  44. package/.idea/modules.xml +0 -8
  45. package/.idea/sdk-nextjs.iml +0 -12
  46. package/.idea/vcs.xml +0 -6
@@ -0,0 +1,74 @@
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.StructuredVideo = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const react_1 = require("react");
9
+ const style_1 = __importDefault(require("styled-jsx/style"));
10
+ const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
11
+ const useLayoutContext_1 = require("../../useLayoutContext");
12
+ /**
13
+ * A video at its own proportions, playing on its own, once pointed at, or once clicked, as the
14
+ * layout says; its cover shows until it first plays. It takes the 16:9 of a video until its own
15
+ * proportions are known.
16
+ */
17
+ const StructuredVideo = ({ block }) => {
18
+ const reactId = (0, react_1.useId)();
19
+ const layoutId = (0, useLayoutContext_1.useLayoutContext)();
20
+ const [ref, setRef] = (0, react_1.useState)(null);
21
+ const [video, setVideo] = (0, react_1.useState)(null);
22
+ const [isLoaded, setIsLoaded] = (0, react_1.useState)(false);
23
+ const [isPlaying, setIsPlaying] = (0, react_1.useState)(false);
24
+ const [hasPlayed, setHasPlayed] = (0, react_1.useState)(false);
25
+ (0, useItemGeometry_1.useItemGeometry)(block.id, ref);
26
+ const { url, coverUrl } = block.commonParams;
27
+ const layoutParams = layoutId ? block.layoutParams[layoutId] : undefined;
28
+ const play = layoutParams === null || layoutParams === void 0 ? void 0 : layoutParams.play;
29
+ const start = () => {
30
+ if (!video)
31
+ return;
32
+ setHasPlayed(true);
33
+ video.play();
34
+ };
35
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: setRef, className: `structured-video-${block.id}${isLoaded ? '' : ` structured-video-loading-${block.id}`}`, children: [layoutParams && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("video", { ref: setVideo, className: `structured-video-el-${block.id}`, src: url, autoPlay: play === 'auto', muted: layoutParams.muted, controls: layoutParams.controls, loop: true, playsInline: true, preload: "auto", onLoadedMetadata: () => setIsLoaded(true), onPlay: () => {
36
+ setIsPlaying(true);
37
+ setHasPlayed(true);
38
+ }, onPause: () => setIsPlaying(false), onMouseEnter: () => {
39
+ if (play === 'on-hover')
40
+ start();
41
+ }, onMouseLeave: () => {
42
+ if (play === 'on-hover')
43
+ video === null || video === void 0 ? void 0 : video.pause();
44
+ } }, layoutId), coverUrl && play !== 'auto' && !hasPlayed && ((0, jsx_runtime_1.jsx)("img", { className: `structured-video-cover-${block.id}`, src: coverUrl, alt: "", onMouseEnter: () => {
45
+ if (play === 'on-hover')
46
+ start();
47
+ }, onClick: start })), play === 'on-click' && !layoutParams.controls && ((0, jsx_runtime_1.jsx)("div", { className: `structured-video-overlay-${block.id}`, onClick: () => (isPlaying ? video === null || video === void 0 ? void 0 : video.pause() : start()) }))] })), (0, jsx_runtime_1.jsx)(style_1.default, { id: `${reactId}-video-${block.id}`, children: `
48
+ .structured-video-${block.id} {
49
+ position: relative;
50
+ width: 100%;
51
+ }
52
+ .structured-video-loading-${block.id} {
53
+ aspect-ratio: 16 / 9;
54
+ }
55
+ .structured-video-el-${block.id} {
56
+ display: block;
57
+ width: 100%;
58
+ height: auto;
59
+ }
60
+ .structured-video-cover-${block.id},
61
+ .structured-video-overlay-${block.id} {
62
+ position: absolute;
63
+ top: 0;
64
+ left: 0;
65
+ width: 100%;
66
+ height: 100%;
67
+ cursor: pointer;
68
+ }
69
+ .structured-video-cover-${block.id} {
70
+ object-fit: cover;
71
+ }
72
+ ` })] }));
73
+ };
74
+ exports.StructuredVideo = StructuredVideo;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.blockPlacement = void 0;
4
+ const useSizing_1 = require("../items/useSizing");
5
+ /**
6
+ * Where a block sits in a column of blocks, as CSS declarations: on the content grid where it is
7
+ * drawn from a preset, the area then carrying its `left` and its inner paddings; anywhere else sized
8
+ * and aligned by itself.
9
+ */
10
+ function blockPlacement(area, layoutParams) {
11
+ var _a, _b, _c, _d, _e;
12
+ const sizing = (0, useSizing_1.parseSizing)('sizing' in layoutParams ? layoutParams.sizing : undefined);
13
+ const height = sizing.y === 'manual' && area.height !== undefined ? `${area.height * 100}vw` : 'auto';
14
+ if (area.left !== undefined) {
15
+ return `
16
+ align-self: flex-start;
17
+ left: ${area.left * 100}vw;
18
+ width: ${((_a = area.width) !== null && _a !== void 0 ? _a : 1) * 100}vw;
19
+ height: ${height};
20
+ padding-top: ${((_b = area.innerPaddingTop) !== null && _b !== void 0 ? _b : 0) * 100}vw;
21
+ padding-bottom: ${((_c = area.innerPaddingBottom) !== null && _c !== void 0 ? _c : 0) * 100}vw;
22
+ `;
23
+ }
24
+ return `
25
+ align-self: ${getAlignSelf((_d = area.alignment) !== null && _d !== void 0 ? _d : 'center')};
26
+ left: ${((_e = area.horizontalOffset) !== null && _e !== void 0 ? _e : 0) * 100}vw;
27
+ width: ${sizing.x === 'manual' && area.width ? `${area.width * 100}vw` : 'max-content'};
28
+ height: ${height};
29
+ `;
30
+ }
31
+ exports.blockPlacement = blockPlacement;
32
+ function getAlignSelf(alignment) {
33
+ switch (alignment) {
34
+ case 'left':
35
+ return 'flex-start';
36
+ case 'right':
37
+ return 'flex-end';
38
+ default:
39
+ return 'center';
40
+ }
41
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.blocksMap = void 0;
4
+ const sdk_1 = require("@cntrl-site/sdk");
5
+ const StructuredComponent_1 = require("./StructuredComponent/StructuredComponent");
6
+ const StructuredRichText_1 = require("./StructuredRichText/StructuredRichText");
7
+ const StructuredImage_1 = require("./StructuredImage/StructuredImage");
8
+ const StructuredHeaderImage_1 = require("./StructuredHeaderImage/StructuredHeaderImage");
9
+ const StructuredVideo_1 = require("./StructuredVideo/StructuredVideo");
10
+ const StructuredDivider_1 = require("./StructuredDivider/StructuredDivider");
11
+ const StructuredCode_1 = require("./StructuredCode/StructuredCode");
12
+ const StructuredVimeoEmbed_1 = require("./StructuredEmbed/StructuredVimeoEmbed");
13
+ const StructuredYoutubeEmbed_1 = require("./StructuredEmbed/StructuredYoutubeEmbed");
14
+ const noop = () => null;
15
+ exports.blocksMap = {
16
+ [sdk_1.StructuredBlockType.Component]: StructuredComponent_1.StructuredComponent,
17
+ [sdk_1.StructuredBlockType.RichText]: StructuredRichText_1.StructuredRichText,
18
+ [sdk_1.StructuredBlockType.Quote]: StructuredRichText_1.StructuredRichText,
19
+ [sdk_1.StructuredBlockType.Date]: StructuredRichText_1.StructuredRichText,
20
+ [sdk_1.StructuredBlockType.Image]: StructuredImage_1.StructuredImage,
21
+ [sdk_1.StructuredBlockType.HeaderImage]: StructuredHeaderImage_1.StructuredHeaderImage,
22
+ [sdk_1.StructuredBlockType.Video]: StructuredVideo_1.StructuredVideo,
23
+ [sdk_1.StructuredBlockType.Divider]: StructuredDivider_1.StructuredDivider,
24
+ [sdk_1.StructuredBlockType.Code]: StructuredCode_1.StructuredCode,
25
+ [sdk_1.StructuredBlockType.VimeoEmbed]: StructuredVimeoEmbed_1.StructuredVimeoEmbed,
26
+ [sdk_1.StructuredBlockType.YoutubeEmbed]: StructuredYoutubeEmbed_1.StructuredYoutubeEmbed,
27
+ // a section's header is drawn by its section, never in the stack
28
+ [sdk_1.StructuredBlockType.Header]: noop
29
+ };
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.colorRule = exports.textStylesRule = void 0;
4
+ const color_1 = require("@cntrl-site/color");
5
+ const getFontFamilyValue_1 = require("../../utils/getFontFamilyValue");
6
+ /** A block's text styles as a CSS rule for `selector`, its lengths in viewport units as the block's own. */
7
+ function textStylesRule(selector, styles) {
8
+ const color = color_1.CntrlColor.parse(styles.color);
9
+ return `
10
+ ${selector} {
11
+ font-family: ${(0, getFontFamilyValue_1.getFontFamilyValue)(styles.typeFace)};
12
+ font-size: ${styles.fontSize * 100}vw;
13
+ line-height: ${styles.lineHeight * 100}vw;
14
+ letter-spacing: ${styles.letterSpacing * 100}vw;
15
+ word-spacing: ${styles.wordSpacing * 100}vw;
16
+ font-weight: ${styles.fontWeight};
17
+ font-style: ${styles.fontStyle || 'normal'};
18
+ font-variant: ${styles.fontVariant};
19
+ text-transform: ${styles.textTransform};
20
+ vertical-align: ${styles.verticalAlign};
21
+ text-align: ${styles.textAlign};
22
+ color: ${color.fmt('oklch')};
23
+ }
24
+ @supports not (color: oklch(42% 0.3 90 / 1)) {
25
+ ${selector} {
26
+ color: ${color.fmt('rgba')};
27
+ }
28
+ }
29
+ `;
30
+ }
31
+ exports.textStylesRule = textStylesRule;
32
+ /** A colour of the palette as CSS for `property`, with a fallback where oklch is not supported. */
33
+ function colorRule(selector, property, value) {
34
+ const color = color_1.CntrlColor.parse(value);
35
+ return `
36
+ ${selector} {
37
+ ${property}: ${color.fmt('oklch')};
38
+ }
39
+ @supports not (color: oklch(42% 0.3 90 / 1)) {
40
+ ${selector} {
41
+ ${property}: ${color.fmt('rgba')};
42
+ }
43
+ }
44
+ `;
45
+ }
46
+ exports.colorRule = colorRule;
@@ -10,28 +10,35 @@ const react_1 = require("react");
10
10
  const style_1 = __importDefault(require("styled-jsx/style"));
11
11
  const useCntrlContext_1 = require("../../provider/useCntrlContext");
12
12
  const StructuredBlockItem_1 = require("../StructuredBlockItem/StructuredBlockItem");
13
+ const StructuredHeader_1 = require("./StructuredHeader");
14
+ /**
15
+ * A section's blocks, one under another across the section's whole width: a block drawn from a
16
+ * preset is placed on the content grid by its own area. A content-based section's header comes
17
+ * first, drawing the blocks it points at, which the stack leaves out.
18
+ */
13
19
  const StructuredContent = ({ section }) => {
14
20
  const reactId = (0, react_1.useId)();
15
21
  const id = `${reactId}-structured-content-${section.id}`;
16
22
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
17
- const structuredContentLength = section.structuredContent.length;
18
- const defaultWidthRecord = section.type === 'content-based' ? section.structuredContentSettings.defaultWidth : {};
19
- const layoutValues = [section.structuredContentSettings.paddingBottom, defaultWidthRecord];
20
- if (structuredContentLength === 0)
23
+ const header = section.type === 'content-based' ? section.header : undefined;
24
+ const headerElementIds = header ? Object.values(header.commonParams) : [];
25
+ const readingWidth = section.type === 'content-based' ? section.structuredContentSettings.defaultWidth : {};
26
+ const layoutValues = [section.structuredContentSettings.paddingBottom];
27
+ if (section.structuredContent.length === 0 && !header)
21
28
  return null;
22
- return ((0, jsx_runtime_1.jsxs)("div", { className: `structured-content-${section.id}`, children: [section.structuredContent.map(block => ((0, jsx_runtime_1.jsx)(StructuredBlockItem_1.StructuredBlockItem, { block: block, maxWidthMap: defaultWidthRecord }, block.id))), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
23
- ${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([paddingBottom, defaultWidth]) => {
24
- return (`
25
- .structured-content-${section.id} {
26
- padding-bottom: ${paddingBottom * 100}vw;
27
- max-width: ${defaultWidth < 1 ? `${defaultWidth * 100}vw` : 'unset'};
28
- display: flex;
29
- flex-direction: column;
30
- margin: 0 auto;
31
- width: 100%;
32
- }
33
- `);
34
- })}
29
+ return ((0, jsx_runtime_1.jsxs)("div", { className: `structured-content-${section.id}`, children: [header && (0, jsx_runtime_1.jsx)(StructuredHeader_1.StructuredHeader, { header: header, blocks: section.structuredContent, readingWidth: readingWidth }), section.structuredContent
30
+ .filter(block => !headerElementIds.includes(block.id))
31
+ .map(block => (0, jsx_runtime_1.jsx)(StructuredBlockItem_1.StructuredBlockItem, { block: block, readingWidth: readingWidth }, block.id)), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
32
+ .structured-content-${section.id} {
33
+ display: flex;
34
+ flex-direction: column;
35
+ width: 100%;
36
+ }
37
+ ${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([paddingBottom]) => (`
38
+ .structured-content-${section.id} {
39
+ padding-bottom: ${(paddingBottom !== null && paddingBottom !== void 0 ? paddingBottom : 0) * 100}vw;
40
+ }
41
+ `))}
35
42
  ` })] }));
36
43
  };
37
44
  exports.StructuredContent = StructuredContent;
@@ -0,0 +1,95 @@
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.StructuredHeader = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const sdk_1 = require("@cntrl-site/sdk");
9
+ const react_1 = require("react");
10
+ const style_1 = __importDefault(require("styled-jsx/style"));
11
+ const useCntrlContext_1 = require("../../provider/useCntrlContext");
12
+ const blockPlacement_1 = require("../StructuredBlockItem/blockPlacement");
13
+ const blocksMap_1 = require("../StructuredBlockItem/blocksMap");
14
+ const TEXT_KINDS = ['title', 'subtitle', 'date'];
15
+ /**
16
+ * A content-based section's header, drawn first. Variant A draws the key image full width with the
17
+ * other elements on its bottom edge, the image covering a share of the viewport's height if set to;
18
+ * variant B flows the shown elements in order, each placed as in the stack. The variant, the order
19
+ * and what is shown are a layout's, so every element is drawn once and arranged per layout.
20
+ */
21
+ const StructuredHeader = ({ header, blocks, readingWidth }) => {
22
+ const reactId = (0, react_1.useId)();
23
+ const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
24
+ const elementOf = (kind) => blocks.find(block => block.id === header.commonParams[kind]);
25
+ const image = elementOf('image');
26
+ const hasImage = (image === null || image === void 0 ? void 0 : image.type) === sdk_1.StructuredBlockType.HeaderImage && Boolean(image.commonParams.url);
27
+ const texts = TEXT_KINDS.flatMap((kind) => {
28
+ const block = elementOf(kind);
29
+ return block ? [{ kind, block }] : [];
30
+ });
31
+ const headerClass = `structured-header-${header.id}`;
32
+ const stackClass = `structured-header-stack-${header.id}`;
33
+ const elementClass = (block) => `structured-header-element-${block.id}`;
34
+ const renderElement = (block) => {
35
+ const BlockComponent = blocksMap_1.blocksMap[block.type];
36
+ return ((0, jsx_runtime_1.jsx)("div", { className: elementClass(block), children: (0, jsx_runtime_1.jsx)(BlockComponent, { block: block, readingWidth: readingWidth }) }, block.id));
37
+ };
38
+ const elementStyles = (kind, block) => (0, sdk_1.getLayoutStyles)(layouts, [header.layoutParams, block.area, block.layoutParams], ([layoutParams, area, blockLayoutParams]) => {
39
+ const isBackdrop = kind === 'image' && layoutParams.variant === 'A';
40
+ const isHidden = !isBackdrop && layoutParams.hiddenElements.includes(kind);
41
+ const isCover = isBackdrop && layoutParams.imageFit === 'cover';
42
+ return (`
43
+ .${elementClass(block)} {
44
+ display: ${isHidden ? 'none' : 'block'};
45
+ position: relative;
46
+ order: ${layoutParams.order.indexOf(kind)};
47
+ ${isBackdrop
48
+ ? `width: 100%; left: 0; height: ${isCover ? '100%' : 'auto'}; padding: 0;`
49
+ : (0, blockPlacement_1.blockPlacement)(area, blockLayoutParams)}
50
+ }
51
+ ${isBackdrop ? `
52
+ .${elementClass(block)} .structured-caption-${block.id} {
53
+ display: none;
54
+ }
55
+ ` : ''}
56
+ ${isCover ? `
57
+ .${elementClass(block)} .structured-header-image-${block.id},
58
+ .${elementClass(block)} .structured-header-picture-${block.id} {
59
+ height: 100%;
60
+ }
61
+ .${elementClass(block)} .structured-header-picture-${block.id} {
62
+ object-fit: cover;
63
+ }
64
+ ` : ''}
65
+ `);
66
+ });
67
+ return ((0, jsx_runtime_1.jsxs)("div", { className: headerClass, children: [image && renderElement(image), (0, jsx_runtime_1.jsx)("div", { className: stackClass, children: texts.map(({ block }) => renderElement(block)) }), (0, jsx_runtime_1.jsx)(style_1.default, { id: `${reactId}-header-${header.id}`, children: `
68
+ .${headerClass} {
69
+ position: relative;
70
+ width: 100%;
71
+ }
72
+ ${(0, sdk_1.getLayoutStyles)(layouts, [header.layoutParams, header.area], ([layoutParams, area]) => {
73
+ var _a;
74
+ const isA = layoutParams.variant === 'A';
75
+ const isCover = isA && layoutParams.imageFit === 'cover';
76
+ return (`
77
+ .${headerClass} {
78
+ display: ${isA ? 'block' : 'flex'};
79
+ flex-direction: column;
80
+ z-index: ${(_a = area === null || area === void 0 ? void 0 : area.zIndex) !== null && _a !== void 0 ? _a : 0};
81
+ height: ${isCover ? `${layoutParams.coverHeight * 100}vh` : 'auto'};
82
+ height: ${isCover ? `${layoutParams.coverHeight * 100}svh` : 'auto'};
83
+ }
84
+ .${stackClass} {
85
+ ${isA
86
+ ? `display: flex; flex-direction: column; position: ${hasImage ? 'absolute' : 'relative'}; left: 0; right: 0; bottom: 0;`
87
+ : 'display: contents;'}
88
+ }
89
+ `);
90
+ })}
91
+ ${image ? elementStyles('image', image) : ''}
92
+ ${texts.map(({ kind, block }) => elementStyles(kind, block)).join('\n')}
93
+ ` })] }));
94
+ };
95
+ exports.StructuredHeader = StructuredHeader;
@@ -26,6 +26,7 @@ const useEmbedVideoItem_1 = require("./useEmbedVideoItem");
26
26
  const LinkWrapper_1 = require("../LinkWrapper");
27
27
  const useLayoutContext_1 = require("../../useLayoutContext");
28
28
  const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
29
+ const getVimeoEmbedUrl_1 = require("../../../utils/getVimeoEmbedUrl");
29
30
  const VimeoEmbedItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
30
31
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
31
32
  const reactId = (0, react_1.useId)();
@@ -50,21 +51,7 @@ const VimeoEmbedItem = ({ item, sectionId, onResize, interactionCtrl, onVisibili
50
51
  const radius = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_d = frameStateParams === null || frameStateParams === void 0 ? void 0 : frameStateParams.styles) === null || _d === void 0 ? void 0 : _d.radius, itemRadius);
51
52
  (0, useRegisterResize_1.useRegisterResize)(ref, onResize);
52
53
  (0, useItemGeometry_1.useItemGeometry)(item.id, ref);
53
- const validUrl = (0, react_1.useMemo)(() => {
54
- if (!layoutParams)
55
- return url;
56
- const validURL = new URL(url);
57
- validURL.searchParams.append('controls', String(layoutParams.controls));
58
- validURL.searchParams.append('autoplay', String(layoutParams.play === 'auto'));
59
- validURL.searchParams.append('muted', String(layoutParams.muted));
60
- validURL.searchParams.append('loop', String(layoutParams.loop));
61
- validURL.searchParams.append('pip', String(layoutParams.pictureInPicture));
62
- validURL.searchParams.append('title', '0');
63
- validURL.searchParams.append('byline', '0');
64
- validURL.searchParams.append('portrait', '0');
65
- validURL.searchParams.append('autopause', 'false');
66
- return validURL.href;
67
- }, [url, layoutParams]);
54
+ const validUrl = (0, react_1.useMemo)(() => (layoutParams ? (0, getVimeoEmbedUrl_1.getVimeoEmbedUrl)(url, layoutParams) : url), [url, layoutParams]);
68
55
  (0, react_1.useEffect)(() => {
69
56
  if (!vimeoPlayer || !imgRef || !layoutParams)
70
57
  return;
@@ -13,10 +13,42 @@ exports.FontStyles = {
13
13
  };
14
14
  exports.RICH_TEXT_LAYOUT_PENDING_CLASS = 'rich-text-layout-pending';
15
15
  const SCALING_STYLES = new Set(['FONTSIZE', 'LINEHEIGHT', 'LETTERSPACING', 'WORDSPACING']);
16
+ const LIST_ITEM_CLASSES = {
17
+ 'unordered-list-item': 'rt-unordered-list-item',
18
+ 'ordered-list-item': 'rt-ordered-list-item'
19
+ };
20
+ // a list item's marker hangs before its first line; a numbered run restarts after anything else
21
+ const LIST_STYLES = `
22
+ .rt-unordered-list-item,
23
+ .rt-ordered-list-item {
24
+ padding-left: 1.5em;
25
+ text-indent: -1.5em;
26
+ }
27
+ .rt-unordered-list-item::before,
28
+ .rt-ordered-list-item::before {
29
+ display: inline-block;
30
+ width: 1.5em;
31
+ text-indent: 0;
32
+ }
33
+ .rt-unordered-list-item::before {
34
+ content: '•';
35
+ }
36
+ .rt-paragraph,
37
+ .rt-unordered-list-item {
38
+ counter-reset: cntrl-list;
39
+ }
40
+ .rt-ordered-list-item {
41
+ counter-increment: cntrl-list;
42
+ }
43
+ .rt-ordered-list-item::before {
44
+ content: counter(cntrl-list) '.';
45
+ }
46
+ `;
16
47
  class RichTextConverter {
17
- toHtml(richText, layouts) {
18
- var _a, _b, _c;
48
+ toHtml(richText, layouts, { isScaledToViewport = true } = {}) {
49
+ var _a, _b, _c, _d;
19
50
  const { text, blocks = [] } = richText.commonParams;
51
+ const hasLists = blocks.some(block => block.type in LIST_ITEM_CLASSES);
20
52
  const root = [];
21
53
  const styleRules = layouts.reduce((rec, layout) => {
22
54
  rec[layout.id] = [];
@@ -32,17 +64,20 @@ class RichTextConverter {
32
64
  const block = blocks[blockIndex];
33
65
  const content = text.slice(block.start, block.end + 1);
34
66
  const entities = (_a = block.entities.sort((a, b) => a.start - b.start)) !== null && _a !== void 0 ? _a : [];
67
+ const typeClass = hasLists ? ` ${(_b = LIST_ITEM_CLASSES[block.type]) !== null && _b !== void 0 ? _b : 'rt-paragraph'}` : '';
35
68
  if (content.length === 1) {
36
69
  const id = `rt_${richText.id}_br_${blockIndex}`;
37
- root.push((0, jsx_runtime_1.jsx)("div", { className: id, children: (0, jsx_runtime_1.jsx)("br", {}) }, id));
70
+ root.push((0, jsx_runtime_1.jsx)("div", { className: `${id}${typeClass}`, children: (0, jsx_runtime_1.jsx)("br", {}) }, id));
38
71
  layouts.forEach(l => {
39
72
  const lhForLayout = currentLineHeight[l.id];
40
73
  if (lhForLayout === undefined)
41
74
  return;
42
75
  const lineHeightStyle = { name: 'LINEHEIGHT', value: lhForLayout };
43
- const lh = RichTextConverter.fromRangeStylesToInline(lineHeightStyle, l.exemplary, true);
44
- const lhPending = RichTextConverter.fromRangeStylesToInline(lineHeightStyle, l.exemplary, false);
76
+ const lh = RichTextConverter.fromRangeStylesToInline(lineHeightStyle, l.exemplary, isScaledToViewport);
45
77
  styleRules[l.id].push(`.rt_${richText.id}_br_${blockIndex} {${lh}}`);
78
+ if (!isScaledToViewport)
79
+ return;
80
+ const lhPending = RichTextConverter.fromRangeStylesToInline(lineHeightStyle, l.exemplary, false);
46
81
  styleRules[l.id].push(`.${exports.RICH_TEXT_LAYOUT_PENDING_CLASS} .rt_${richText.id}_br_${blockIndex} {${lhPending}}`);
47
82
  });
48
83
  continue;
@@ -73,7 +108,7 @@ class RichTextConverter {
73
108
  `);
74
109
  });
75
110
  const item = group[0];
76
- const entitiesGroups = (_b = this.groupEntities(entities, item.styles)) !== null && _b !== void 0 ? _b : [];
111
+ const entitiesGroups = (_c = this.groupEntities(entities, item.styles)) !== null && _c !== void 0 ? _c : [];
77
112
  let offset = 0;
78
113
  for (const entity of entitiesGroups) {
79
114
  const entityKids = [];
@@ -103,34 +138,36 @@ class RichTextConverter {
103
138
  }
104
139
  for (const item of group) {
105
140
  const { exemplary } = layouts.find(l => l.id === item.layout);
106
- const entitiesGroups = (_c = this.groupEntities(entities, item.styles)) !== null && _c !== void 0 ? _c : [];
141
+ const entitiesGroups = (_d = this.groupEntities(entities, item.styles)) !== null && _d !== void 0 ? _d : [];
107
142
  for (const entitiesGroup of entitiesGroups) {
108
143
  if (!entitiesGroup.stylesGroup)
109
144
  continue;
110
145
  for (const styleGroup of entitiesGroup.stylesGroup) {
111
146
  const lineHeight = styleGroup.styles.find(s => s.name === 'LINEHEIGHT');
112
147
  const color = styleGroup.styles.find(s => s.name === 'COLOR');
148
+ const highlight = styleGroup.styles.find(s => s.name === 'HIGHLIGHT');
113
149
  if (lineHeight === null || lineHeight === void 0 ? void 0 : lineHeight.value) {
114
150
  currentLineHeight[item.layout] = lineHeight.value;
115
151
  }
116
152
  styleRules[item.layout].push(`
117
153
  .${blockClass} .s-${styleGroup.start}-${styleGroup.end} {
118
- ${styleGroup.styles.map(s => RichTextConverter.fromRangeStylesToInline(s, exemplary, true)).join('\n')}
154
+ ${styleGroup.styles.map(s => RichTextConverter.fromRangeStylesToInline(s, exemplary, isScaledToViewport)).join('\n')}
119
155
  }
120
156
  `);
121
157
  const lengthStyles = styleGroup.styles.filter(s => SCALING_STYLES.has(s.name));
122
- if (lengthStyles.length !== 0) {
158
+ if (isScaledToViewport && lengthStyles.length !== 0) {
123
159
  styleRules[item.layout].push(`
124
160
  .${exports.RICH_TEXT_LAYOUT_PENDING_CLASS} .${blockClass} .s-${styleGroup.start}-${styleGroup.end} {
125
161
  ${lengthStyles.map(s => RichTextConverter.fromRangeStylesToInline(s, exemplary, false)).join('\n')}
126
162
  }
127
163
  `);
128
164
  }
129
- if (color) {
165
+ if ((color === null || color === void 0 ? void 0 : color.value) || (highlight === null || highlight === void 0 ? void 0 : highlight.value)) {
130
166
  styleRules[item.layout].push(`
131
167
  @supports not (color: oklch(42% 0.3 90 / 1)) {
132
168
  .${blockClass} .s-${styleGroup.start}-${styleGroup.end} {
133
- color: ${color_1.CntrlColor.parse(getResolvedValue(color.value, 'COLOR')).fmt('rgba')};
169
+ ${(color === null || color === void 0 ? void 0 : color.value) ? `color: ${color_1.CntrlColor.parse(color.value).fmt('rgba')};` : ''}
170
+ ${(highlight === null || highlight === void 0 ? void 0 : highlight.value) ? `background-color: ${color_1.CntrlColor.parse(highlight.value).fmt('rgba')};` : ''}
134
171
  }
135
172
  }
136
173
  `);
@@ -138,14 +175,14 @@ class RichTextConverter {
138
175
  }
139
176
  }
140
177
  }
141
- root.push((0, jsx_runtime_1.jsx)("div", { className: blockClass, children: kids }, blockClass));
178
+ root.push((0, jsx_runtime_1.jsx)("div", { className: `${blockClass}${typeClass}`, children: kids }, blockClass));
142
179
  }
143
180
  }
144
181
  const styles = layouts.map(l => `
145
182
  ${(0, sdk_1.getLayoutMediaQuery)(l.id, layouts)} {
146
183
  ${styleRules[l.id].join('\n')}
147
184
  }
148
- `).join('\n');
185
+ `).join('\n') + (hasLists ? LIST_STYLES : '');
149
186
  return [
150
187
  root,
151
188
  styles
@@ -234,6 +271,7 @@ class RichTextConverter {
234
271
  const { value, name } = draftStyle;
235
272
  const map = {
236
273
  COLOR: { color: getResolvedValue(value, name) },
274
+ HIGHLIGHT: { 'background-color': getResolvedValue(value, name) },
237
275
  TYPEFACE: { 'font-family': `${(0, getFontFamilyValue_1.getFontFamilyValue)(value)}` },
238
276
  FONTSTYLE: value ? Object.assign({}, exports.FontStyles[value]) : {},
239
277
  FONTWEIGHT: { 'font-weight': value },
@@ -270,7 +308,7 @@ function getScaledValue(value, exemplary, isLayoutDefined) {
270
308
  return !isLayoutDefined ? `${ratio * 100}vw` : `${ratio * exemplary}px`;
271
309
  }
272
310
  function getResolvedValue(value, name) {
273
- if (name !== 'COLOR')
311
+ if (name !== 'COLOR' && name !== 'HIGHLIGHT')
274
312
  return value;
275
313
  return value ? color_1.CntrlColor.parse(value).toCss() : value;
276
314
  }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getVimeoEmbedUrl = void 0;
4
+ /** The player's url with how the layout plays the video, and none of Vimeo's own title, byline or portrait. */
5
+ function getVimeoEmbedUrl(url, layoutParams) {
6
+ const validURL = new URL(url);
7
+ validURL.searchParams.append('controls', String(layoutParams.controls));
8
+ validURL.searchParams.append('autoplay', String(layoutParams.play === 'auto'));
9
+ validURL.searchParams.append('muted', String(layoutParams.muted));
10
+ validURL.searchParams.append('loop', String(layoutParams.loop));
11
+ validURL.searchParams.append('pip', String(layoutParams.pictureInPicture));
12
+ validURL.searchParams.append('title', '0');
13
+ validURL.searchParams.append('byline', '0');
14
+ validURL.searchParams.append('portrait', '0');
15
+ validURL.searchParams.append('autopause', 'false');
16
+ return validURL.href;
17
+ }
18
+ exports.getVimeoEmbedUrl = getVimeoEmbedUrl;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cntrl-site/sdk-nextjs",
3
- "version": "1.10.9",
3
+ "version": "1.11.0-0",
4
4
  "description": "SDK for Next.js",
5
5
  "author": "arsen@momdesign.nyc",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "@cntrl-site/color": "^1.0.0",
34
34
  "@cntrl-site/components": "^1.0.95",
35
35
  "@cntrl-site/effects": "^1.4.2",
36
- "@cntrl-site/sdk": "^1.28.10",
36
+ "@cntrl-site/sdk": "^1.29.0-0",
37
37
  "@types/vimeo__player": "^2.18.0",
38
38
  "@vimeo/player": "^2.25.0",
39
39
  "franc-min": "^5.0.0",
@@ -0,0 +1,42 @@
1
+ import { getLayoutStyles, ImageStructuredBlock } from '@cntrl-site/sdk';
2
+ import { FC, useId } from 'react';
3
+ import JSXStyle from 'styled-jsx/style';
4
+ import { useCntrlContext } from '../../../provider/useCntrlContext';
5
+ import { textStylesRule } from '../textStylesRule';
6
+
7
+ interface Props {
8
+ blockId: string;
9
+ caption: string;
10
+ layoutParams: ImageStructuredBlock['layoutParams'];
11
+ readingWidth: Record<string, number>;
12
+ }
13
+
14
+ const CAPTION_GAP_PX = 5;
15
+
16
+ /** The caption under an image: as wide as the reading column, or as the image where it is narrower, and centered under it. */
17
+ export const BlockCaption: FC<Props> = ({ blockId, caption, layoutParams, readingWidth }) => {
18
+ const reactId = useId();
19
+ const { layouts } = useCntrlContext();
20
+ const layoutValues: Record<string, any>[] = [layoutParams, readingWidth];
21
+ return (
22
+ <>
23
+ <div className={`structured-caption-${blockId}`}>{caption}</div>
24
+ <JSXStyle id={`${reactId}-caption-${blockId}`}>{`
25
+ .structured-caption-${blockId} {
26
+ margin-left: auto;
27
+ margin-right: auto;
28
+ white-space: pre-wrap;
29
+ overflow-wrap: break-word;
30
+ }
31
+ ${getLayoutStyles(layouts, layoutValues, ([params, width], exemplary) => (`
32
+ .structured-caption-${blockId} {
33
+ width: ${width !== undefined ? `min(${width * 100}vw, 100%)` : '100%'};
34
+ margin-top: ${CAPTION_GAP_PX / exemplary * 100}vw;
35
+ }
36
+ ${params?.captionStyles ? textStylesRule(`.structured-caption-${blockId}`, params.captionStyles) : ''}
37
+ `))}
38
+ `}
39
+ </JSXStyle>
40
+ </>
41
+ );
42
+ };