@webstudio-is/react-sdk 0.197.0 → 0.199.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.
package/lib/index.js CHANGED
@@ -77,438 +77,10 @@ import {
77
77
  import {
78
78
  ROOT_INSTANCE_ID,
79
79
  createScope,
80
- parseComponentName
80
+ parseComponentName,
81
+ descendantComponent,
82
+ rootComponent
81
83
  } from "@webstudio-is/sdk";
82
-
83
- // src/core-components.ts
84
- import {
85
- ContentBlockIcon,
86
- ListViewIcon,
87
- PaintBrushIcon,
88
- SettingsIcon,
89
- AddTemplateInstanceIcon
90
- } from "@webstudio-is/icons/svg";
91
- import { html } from "@webstudio-is/sdk/normalize.css";
92
- var rootComponent = "ws:root";
93
- var rootMeta = {
94
- category: "hidden",
95
- type: "container",
96
- label: "Global Root",
97
- icon: SettingsIcon,
98
- presetStyle: {
99
- html
100
- }
101
- };
102
- var rootPropsMeta = {
103
- props: {}
104
- };
105
- var portalComponent = "Slot";
106
- var collectionComponent = "ws:collection";
107
- var collectionMeta = {
108
- category: "data",
109
- order: 2,
110
- type: "container",
111
- label: "Collection",
112
- icon: ListViewIcon,
113
- stylable: false,
114
- template: [
115
- {
116
- type: "instance",
117
- component: collectionComponent,
118
- props: [
119
- {
120
- name: "data",
121
- type: "json",
122
- value: [
123
- "Collection Item 1",
124
- "Collection Item 2",
125
- "Collection Item 3"
126
- ]
127
- },
128
- {
129
- name: "item",
130
- type: "parameter",
131
- variableName: "collectionItem",
132
- variableAlias: "Collection Item"
133
- }
134
- ],
135
- children: [
136
- {
137
- type: "instance",
138
- component: "Box",
139
- children: [
140
- {
141
- type: "instance",
142
- component: "Text",
143
- children: [{ type: "expression", value: "collectionItem" }]
144
- }
145
- ]
146
- }
147
- ]
148
- }
149
- ]
150
- };
151
- var collectionPropsMeta = {
152
- props: {
153
- data: {
154
- required: true,
155
- control: "json",
156
- type: "json"
157
- }
158
- },
159
- initialProps: ["data"]
160
- };
161
- var descendantComponent = "ws:descendant";
162
- var descendantMeta = {
163
- category: "internal",
164
- type: "control",
165
- label: "Descendant",
166
- icon: PaintBrushIcon,
167
- constraints: {
168
- relation: "parent",
169
- component: { $in: ["HtmlEmbed", "MarkdownEmbed"] }
170
- }
171
- };
172
- var descendantPropsMeta = {
173
- props: {
174
- selector: {
175
- required: true,
176
- type: "string",
177
- control: "select",
178
- options: [
179
- " p",
180
- " h1",
181
- " h2",
182
- " h3",
183
- " h4",
184
- " h5",
185
- " h6",
186
- " :where(strong, b)",
187
- " :where(em, i)",
188
- " a",
189
- " img",
190
- " blockquote",
191
- " code",
192
- " :where(ul, ol)",
193
- " li",
194
- " hr"
195
- ]
196
- }
197
- },
198
- initialProps: ["selector"]
199
- };
200
- var blockComponent = "ws:block";
201
- var blockTemplateComponent = "ws:block-template";
202
- var blockTemplateMeta = {
203
- category: "hidden",
204
- type: "container",
205
- icon: AddTemplateInstanceIcon,
206
- stylable: false,
207
- constraints: {
208
- relation: "parent",
209
- component: { $eq: blockComponent }
210
- }
211
- };
212
- var blockTemplatePropsMeta = {
213
- props: {},
214
- initialProps: []
215
- };
216
- var blockMeta = {
217
- category: "typography",
218
- type: "container",
219
- label: "Content Block",
220
- icon: ContentBlockIcon,
221
- constraints: [
222
- {
223
- relation: "ancestor",
224
- component: { $nin: [collectionComponent, blockComponent] }
225
- },
226
- {
227
- relation: "child",
228
- component: { $eq: blockTemplateComponent }
229
- }
230
- ],
231
- stylable: false,
232
- template: [
233
- {
234
- type: "instance",
235
- component: blockComponent,
236
- props: [],
237
- children: [
238
- {
239
- component: blockTemplateComponent,
240
- type: "instance",
241
- label: "Templates",
242
- children: [
243
- {
244
- component: "Paragraph",
245
- type: "instance",
246
- children: []
247
- },
248
- {
249
- component: "Heading",
250
- type: "instance",
251
- label: "Heading 1",
252
- props: [
253
- {
254
- name: "tag",
255
- type: "string",
256
- value: "h1"
257
- }
258
- ],
259
- children: []
260
- },
261
- {
262
- component: "Heading",
263
- type: "instance",
264
- label: "Heading 2",
265
- props: [
266
- {
267
- name: "tag",
268
- type: "string",
269
- value: "h2"
270
- }
271
- ],
272
- children: []
273
- },
274
- {
275
- component: "Heading",
276
- type: "instance",
277
- label: "Heading 3",
278
- props: [
279
- {
280
- name: "tag",
281
- type: "string",
282
- value: "h3"
283
- }
284
- ],
285
- children: []
286
- },
287
- {
288
- component: "Heading",
289
- type: "instance",
290
- label: "Heading 4",
291
- props: [
292
- {
293
- name: "tag",
294
- type: "string",
295
- value: "h4"
296
- }
297
- ],
298
- children: []
299
- },
300
- {
301
- component: "Heading",
302
- type: "instance",
303
- label: "Heading 5",
304
- props: [
305
- {
306
- name: "tag",
307
- type: "string",
308
- value: "h5"
309
- }
310
- ],
311
- children: []
312
- },
313
- {
314
- component: "Heading",
315
- type: "instance",
316
- label: "Heading 6",
317
- props: [
318
- {
319
- name: "tag",
320
- type: "string",
321
- value: "h6"
322
- }
323
- ],
324
- children: []
325
- },
326
- {
327
- component: "List",
328
- type: "instance",
329
- label: "List (Unordered)",
330
- children: [
331
- {
332
- component: "ListItem",
333
- type: "instance",
334
- children: []
335
- }
336
- ]
337
- },
338
- {
339
- component: "List",
340
- type: "instance",
341
- label: "List (Ordered)",
342
- props: [
343
- {
344
- name: "ordered",
345
- type: "boolean",
346
- value: true
347
- }
348
- ],
349
- children: [
350
- {
351
- component: "ListItem",
352
- type: "instance",
353
- children: []
354
- }
355
- ]
356
- },
357
- {
358
- component: "Link",
359
- type: "instance",
360
- children: []
361
- },
362
- {
363
- component: "Image",
364
- type: "instance",
365
- styles: [
366
- {
367
- property: "marginRight",
368
- value: {
369
- type: "keyword",
370
- value: "auto"
371
- }
372
- },
373
- {
374
- property: "marginLeft",
375
- value: {
376
- type: "keyword",
377
- value: "auto"
378
- }
379
- },
380
- {
381
- property: "width",
382
- value: {
383
- type: "unit",
384
- unit: "%",
385
- value: 100
386
- }
387
- },
388
- {
389
- property: "height",
390
- value: {
391
- type: "keyword",
392
- value: "auto"
393
- }
394
- }
395
- ],
396
- children: []
397
- },
398
- {
399
- component: "Separator",
400
- type: "instance",
401
- children: []
402
- },
403
- {
404
- component: "Blockquote",
405
- type: "instance",
406
- children: []
407
- },
408
- {
409
- component: "HtmlEmbed",
410
- type: "instance",
411
- children: []
412
- },
413
- {
414
- component: "CodeText",
415
- type: "instance",
416
- children: []
417
- }
418
- ]
419
- },
420
- {
421
- type: "instance",
422
- component: "Paragraph",
423
- children: [
424
- {
425
- type: "text",
426
- value: "The Content Block component designates regions on the page where pre-styled instances can be inserted in "
427
- },
428
- {
429
- type: "instance",
430
- component: "RichTextLink",
431
- children: [
432
- {
433
- type: "text",
434
- value: "Content mode"
435
- }
436
- ],
437
- props: [
438
- {
439
- type: "string",
440
- name: "href",
441
- value: "https://wstd.us/content-block"
442
- }
443
- ]
444
- },
445
- {
446
- type: "text",
447
- value: "."
448
- }
449
- ]
450
- },
451
- {
452
- type: "instance",
453
- component: "List",
454
- children: [
455
- {
456
- type: "instance",
457
- component: "ListItem",
458
- children: [
459
- {
460
- type: "text",
461
- value: "In Content mode, you can edit any direct child instances that were pre-added to the Content Block, as well as add new instances predefined in Templates."
462
- }
463
- ]
464
- },
465
- {
466
- type: "instance",
467
- component: "ListItem",
468
- children: [
469
- {
470
- type: "text",
471
- value: "To predefine instances for insertion in Content mode, switch to Design mode and add them to the Templates container."
472
- }
473
- ]
474
- },
475
- {
476
- type: "instance",
477
- component: "ListItem",
478
- children: [
479
- {
480
- type: "text",
481
- value: "To insert predefined instances in Content mode, click the + button while hovering over the Content Block on the canvas and choose an instance from the list."
482
- }
483
- ]
484
- }
485
- ]
486
- }
487
- ]
488
- }
489
- ]
490
- };
491
- var blockPropsMeta = {
492
- props: {},
493
- initialProps: []
494
- };
495
- var coreMetas = {
496
- [rootComponent]: rootMeta,
497
- [collectionComponent]: collectionMeta,
498
- [descendantComponent]: descendantMeta,
499
- [blockComponent]: blockMeta,
500
- [blockTemplateComponent]: blockTemplateMeta
501
- };
502
- var corePropsMetas = {
503
- [rootComponent]: rootPropsMeta,
504
- [collectionComponent]: collectionPropsMeta,
505
- [descendantComponent]: descendantPropsMeta,
506
- [blockComponent]: blockPropsMeta,
507
- [blockTemplateComponent]: blockTemplatePropsMeta
508
- };
509
- var isCoreComponent = (component) => component === rootComponent || component === collectionComponent || component === descendantComponent || component === blockComponent || component === blockTemplateComponent;
510
-
511
- // src/css/css.ts
512
84
  import { kebabCase } from "change-case";
513
85
  var createImageValueTransformer = (assets, { assetBaseUrl }) => (styleValue) => {
514
86
  if (styleValue.type === "image" && styleValue.value.type === "asset") {
@@ -1118,7 +690,11 @@ import {
1118
690
  parseComponentName as parseComponentName2,
1119
691
  generateExpression,
1120
692
  decodeDataSourceVariable,
1121
- transpileExpression as transpileExpression2
693
+ transpileExpression as transpileExpression2,
694
+ blockComponent,
695
+ blockTemplateComponent,
696
+ collectionComponent,
697
+ descendantComponent as descendantComponent2
1122
698
  } from "@webstudio-is/sdk";
1123
699
  var generateAction = ({
1124
700
  scope,
@@ -1220,7 +796,7 @@ var generateJsxElement = ({
1220
796
  children,
1221
797
  classesMap
1222
798
  }) => {
1223
- if (instance.component === descendantComponent) {
799
+ if (instance.component === descendantComponent2) {
1224
800
  return "";
1225
801
  }
1226
802
  let generatedProps = "";
@@ -1232,8 +808,7 @@ ${indexAttribute}="${index}"`;
1232
808
  let conditionValue;
1233
809
  let collectionDataValue;
1234
810
  let collectionItemValue;
1235
- const classMapArray = classesMap?.get(instance.id);
1236
- const classes = classMapArray !== void 0 ? [JSON.stringify(classMapArray.join(" "))] : [];
811
+ let classNameValue;
1237
812
  for (const prop of props.values()) {
1238
813
  if (prop.instanceId !== instance.id) {
1239
814
  continue;
@@ -1267,7 +842,7 @@ ${indexAttribute}="${index}"`;
1267
842
  continue;
1268
843
  }
1269
844
  if (prop.name === "className" && propValue !== void 0) {
1270
- classes.push(propValue);
845
+ classNameValue = propValue;
1271
846
  continue;
1272
847
  }
1273
848
  if (propValue !== void 0) {
@@ -1275,9 +850,16 @@ ${indexAttribute}="${index}"`;
1275
850
  ${prop.name}={${propValue}}`;
1276
851
  }
1277
852
  }
1278
- if (classes.length !== 0) {
1279
- generatedProps += `
1280
- className={${classes.join(` + " " + `)}}`;
853
+ const classMapArray = classesMap?.get(instance.id);
854
+ if (classMapArray || classNameValue) {
855
+ let classNameTemplate = classMapArray ? classMapArray.join(" ") : "";
856
+ if (classNameValue) {
857
+ if (classNameTemplate) {
858
+ classNameTemplate += " ";
859
+ }
860
+ classNameTemplate += "${" + classNameValue + "}";
861
+ }
862
+ generatedProps += "\nclassName={`" + classNameTemplate + "`}";
1281
863
  }
1282
864
  let generatedElement = "";
1283
865
  if (instance.component === blockTemplateComponent) {
@@ -1489,16 +1071,9 @@ var generateWebstudioComponent = ({
1489
1071
  };
1490
1072
  export {
1491
1073
  addGlobalRules,
1492
- blockComponent,
1493
- blockTemplateComponent,
1494
- blockTemplateMeta,
1495
1074
  collapsedAttribute,
1496
- collectionComponent,
1497
1075
  componentAttribute,
1498
- coreMetas,
1499
- corePropsMetas,
1500
1076
  createImageValueTransformer,
1501
- descendantComponent,
1502
1077
  editablePlaceholderVariable,
1503
1078
  editingPlaceholderVariable,
1504
1079
  generateCss,
@@ -1512,11 +1087,8 @@ export {
1512
1087
  idAttribute,
1513
1088
  indexAttribute,
1514
1089
  isAttributeNameSafe,
1515
- isCoreComponent,
1516
1090
  namespaceMeta,
1517
1091
  normalizeProps,
1518
- portalComponent,
1519
- rootComponent,
1520
1092
  selectorIdAttribute,
1521
1093
  showAttribute,
1522
1094
  textContentAttribute
package/lib/runtime.js CHANGED
@@ -47,12 +47,134 @@ var useVariableState = (initialState) => {
47
47
  return [value, setState];
48
48
  };
49
49
 
50
+ // src/page-settings-meta.tsx
51
+ import { useEffect, useState as useState2 } from "react";
52
+ import { jsx } from "react/jsx-runtime";
53
+ var isElementRenderedWithReact = (element) => {
54
+ return Object.keys(element).some((key) => key.startsWith("__react"));
55
+ };
56
+ var isServer = typeof window === "undefined";
57
+ var Meta = (props) => {
58
+ const [localProps, setLocalProps] = useState2();
59
+ useEffect(() => {
60
+ const selector = `meta[${props.name ? `name="${props.name}"` : `property="${props.property}"`}]`;
61
+ let allMetas = document.querySelectorAll(selector);
62
+ for (const meta of allMetas) {
63
+ if (!isElementRenderedWithReact(meta)) {
64
+ meta.remove();
65
+ }
66
+ }
67
+ allMetas = document.querySelectorAll(selector);
68
+ if (allMetas.length === 0) {
69
+ setLocalProps(props);
70
+ }
71
+ }, [props]);
72
+ if (isServer) {
73
+ return /* @__PURE__ */ jsx("meta", { ...props });
74
+ }
75
+ if (localProps === void 0) {
76
+ return;
77
+ }
78
+ return /* @__PURE__ */ jsx("meta", { ...localProps });
79
+ };
80
+ var PageSettingsMeta = ({
81
+ url,
82
+ host,
83
+ siteName,
84
+ pageMeta,
85
+ imageLoader
86
+ }) => {
87
+ const metas = [];
88
+ if (url !== void 0) {
89
+ metas.push({
90
+ property: "og:url",
91
+ content: url
92
+ });
93
+ }
94
+ if (pageMeta.title) {
95
+ metas.push({
96
+ property: "og:title",
97
+ content: pageMeta.title
98
+ });
99
+ }
100
+ metas.push({ property: "og:type", content: "website" });
101
+ if (siteName) {
102
+ metas.push({
103
+ property: "og:site_name",
104
+ content: siteName
105
+ });
106
+ }
107
+ if (pageMeta.excludePageFromSearch) {
108
+ metas.push({
109
+ name: "robots",
110
+ content: "noindex, nofollow"
111
+ });
112
+ }
113
+ if (pageMeta.description) {
114
+ metas.push({
115
+ name: "description",
116
+ content: pageMeta.description
117
+ });
118
+ metas.push({
119
+ property: "og:description",
120
+ content: pageMeta.description
121
+ });
122
+ }
123
+ if (pageMeta.socialImageAssetName) {
124
+ metas.push({
125
+ property: "og:image",
126
+ content: `https://${host}${imageLoader({
127
+ src: pageMeta.socialImageAssetName,
128
+ // Do not transform social image (not enough information do we need to do this)
129
+ format: "raw"
130
+ })}`
131
+ });
132
+ } else if (pageMeta.socialImageUrl) {
133
+ metas.push({
134
+ property: "og:image",
135
+ content: pageMeta.socialImageUrl
136
+ });
137
+ }
138
+ metas.push(...pageMeta.custom);
139
+ return metas.map((meta, index) => /* @__PURE__ */ jsx(Meta, { ...meta }, index));
140
+ };
141
+
142
+ // src/page-settings-title.tsx
143
+ import { useEffect as useEffect2, useState as useState3 } from "react";
144
+ import { jsx as jsx2 } from "react/jsx-runtime";
145
+ var isServer2 = typeof window === "undefined";
146
+ var PageSettingsTitle = (props) => {
147
+ const [localProps, setLocalProps] = useState3();
148
+ useEffect2(() => {
149
+ const selector = `head > title`;
150
+ let allTitles = document.querySelectorAll(selector);
151
+ for (const meta of allTitles) {
152
+ if (!isElementRenderedWithReact(meta)) {
153
+ meta.remove();
154
+ }
155
+ }
156
+ allTitles = document.querySelectorAll(selector);
157
+ if (allTitles.length === 0) {
158
+ setLocalProps(props);
159
+ }
160
+ }, [props]);
161
+ if (isServer2) {
162
+ return /* @__PURE__ */ jsx2("title", { ...props });
163
+ }
164
+ if (localProps === void 0) {
165
+ return;
166
+ }
167
+ return /* @__PURE__ */ jsx2("title", { ...localProps });
168
+ };
169
+
50
170
  // src/runtime.ts
51
171
  var xmlNodeTagSuffix = "-ws-xml-node-fb77f896-8e96-40b9-b8f8-90a4e70d724a";
52
172
  var getIndexWithinAncestorFromComponentProps = (props) => {
53
173
  return props["data-ws-index"];
54
174
  };
55
175
  export {
176
+ PageSettingsMeta,
177
+ PageSettingsTitle,
56
178
  ReactSdkContext,
57
179
  getClosestInstance,
58
180
  getIndexWithinAncestorFromComponentProps,
@@ -1,6 +1,5 @@
1
1
  export * from "./remix";
2
2
  export * from "./css/index";
3
- export * from "./core-components";
4
3
  export * from "./components/components-utils";
5
4
  export * from "./embed-template";
6
5
  export * from "./props";
@@ -0,0 +1,10 @@
1
+ import type { ImageLoader } from "@webstudio-is/image";
2
+ import type { PageMeta } from "@webstudio-is/sdk";
3
+ export declare const isElementRenderedWithReact: (element: Element) => boolean;
4
+ export declare const PageSettingsMeta: ({ url, host, siteName, pageMeta, imageLoader, }: {
5
+ url?: string;
6
+ host: string;
7
+ siteName: string;
8
+ pageMeta: PageMeta;
9
+ imageLoader: ImageLoader;
10
+ }) => import("react/jsx-runtime").JSX.Element[];
@@ -0,0 +1,17 @@
1
+ type PageSettingsTitleProps = {
2
+ children: string;
3
+ };
4
+ /**
5
+ * Title tags are deduplicated on the server using the HTMLRewriter interface.
6
+ * This is not full deduplication. We simply skip rendering Page Setting title
7
+ * if it has already been rendered using HeadSlot/HeadTitle.
8
+ * To prevent React on the client from re-adding the removed title tag, we skip rendering them client-side.
9
+ * This approach works because React retains server-rendered title tag as long as they are not re-rendered by the client.
10
+ *
11
+ * The following component behavior ensures this:
12
+ * 1. On the server: Render title tag as usual.
13
+ * 2. On the client: Before rendering, remove any title tag with the same `name` or `property` that were not rendered by Client React,
14
+ * and then proceed with rendering as usual.
15
+ */
16
+ export declare const PageSettingsTitle: (props: PageSettingsTitleProps) => import("react/jsx-runtime").JSX.Element | undefined;
17
+ export {};
@@ -1,6 +1,8 @@
1
1
  export * from "./context";
2
2
  export * from "./hook";
3
3
  export * from "./variable-state";
4
+ export { PageSettingsMeta } from "./page-settings-meta";
5
+ export { PageSettingsTitle } from "./page-settings-title";
4
6
  /**
5
7
  * React has issues rendering certain elements, such as errors when a <link> element has children.
6
8
  * To render XML, we wrap it with an <svg> tag and add a suffix to avoid React's default behavior on these elements.