@amplifyup/sdk 0.1.39 → 0.1.40

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/dist/react.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { createContext, useState, useEffect, useContext, Fragment as Fragment$1, useRef, Component } from 'react';
2
+ import React, { createContext, useState, useEffect, useContext, Fragment as Fragment$1, useSyncExternalStore, useRef, Component } from 'react';
3
3
  import { marked } from 'marked';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
 
@@ -3031,7 +3031,10 @@ function useLiveComposerProps(layoutNodeId, props) {
3031
3031
  return;
3032
3032
  }
3033
3033
  if (!matchesNode(nodeId)) return;
3034
- setLive(nextProps);
3034
+ setLive((prev) => ({
3035
+ ...prev,
3036
+ ...nextProps
3037
+ }));
3035
3038
  };
3036
3039
  const onPatch = (event) => {
3037
3040
  const detail = event.detail || {};
@@ -3192,6 +3195,32 @@ function isLikelyNonTextFieldName(name) {
3192
3195
  const n = name.toLowerCase();
3193
3196
  return n.includes("href") || n.includes("url") || n.includes("src") || n.includes("image") || n.includes("icon") || n.includes("overlay") || n.includes("color");
3194
3197
  }
3198
+ function isPlainTextValue(value) {
3199
+ return typeof value === "string" || typeof value === "number";
3200
+ }
3201
+ var COMPOSER_EMPTY_FIELD_HINT = "Click here to edit this field";
3202
+ var EMPTY_FIELD_PLACEHOLDERS = /* @__PURE__ */ new Set([
3203
+ COMPOSER_EMPTY_FIELD_HINT,
3204
+ "Click here to enter text",
3205
+ "Sample Title",
3206
+ "Sample Heading",
3207
+ "Sample Subheading"
3208
+ ]);
3209
+ function isComposerEmptyText(value) {
3210
+ if (value === void 0 || value === null) return true;
3211
+ if (typeof value !== "string") return false;
3212
+ const trimmed = value.trim();
3213
+ return trimmed === "" || EMPTY_FIELD_PLACEHOLDERS.has(trimmed);
3214
+ }
3215
+ function isReactNodeFallback(fallback) {
3216
+ if (fallback == null || fallback === false) return false;
3217
+ return !isPlainTextValue(fallback);
3218
+ }
3219
+ function htmlHasVisualChrome(html) {
3220
+ return /<(input|button|select|textarea|form|svg|iframe|video|audio|img)\b/i.test(
3221
+ html
3222
+ );
3223
+ }
3195
3224
  function canInlineEdit(name, value) {
3196
3225
  if (isLikelyNonTextFieldName(name)) return false;
3197
3226
  if (value !== void 0 && value !== null && typeof value === "object") {
@@ -3200,15 +3229,18 @@ function canInlineEdit(name, value) {
3200
3229
  if (typeof value === "boolean") return false;
3201
3230
  return true;
3202
3231
  }
3232
+ function subscribeComposerEdit(onStoreChange) {
3233
+ if (typeof window === "undefined") return () => {
3234
+ };
3235
+ window.addEventListener("amplifyup:composer-edit", onStoreChange);
3236
+ return () => window.removeEventListener("amplifyup:composer-edit", onStoreChange);
3237
+ }
3203
3238
  function useComposerEditMode() {
3204
- const [enabled, setEnabled] = useState(false);
3205
- useEffect(() => {
3206
- const sync = () => setEnabled(isComposerEditMode());
3207
- sync();
3208
- window.addEventListener("amplifyup:composer-edit", sync);
3209
- return () => window.removeEventListener("amplifyup:composer-edit", sync);
3210
- }, []);
3211
- return enabled;
3239
+ return useSyncExternalStore(
3240
+ subscribeComposerEdit,
3241
+ isComposerEditMode,
3242
+ () => false
3243
+ );
3212
3244
  }
3213
3245
  function useFieldOverlay(layoutNodeId, name, source) {
3214
3246
  const [overlay, setOverlay] = useState(void 0);
@@ -3239,6 +3271,14 @@ function commitComposerField(layoutNodeId, componentId, name, value) {
3239
3271
  "*"
3240
3272
  );
3241
3273
  }
3274
+ function placeCaret(el, atStart = false) {
3275
+ const range = document.createRange();
3276
+ range.selectNodeContents(el);
3277
+ range.collapse(atStart);
3278
+ const sel = window.getSelection();
3279
+ sel?.removeAllRanges();
3280
+ sel?.addRange(range);
3281
+ }
3242
3282
  function ComposerInlineField({
3243
3283
  name,
3244
3284
  value,
@@ -3249,35 +3289,54 @@ function ComposerInlineField({
3249
3289
  componentId
3250
3290
  }) {
3251
3291
  const [editing, setEditing] = useState(false);
3292
+ const [focused, setFocused] = useState(false);
3252
3293
  const elRef = useRef(null);
3253
- const text = formatFieldValue(value);
3294
+ const storedEmpty = isComposerEmptyText(value);
3295
+ const text = storedEmpty ? "" : formatFieldValue(value);
3254
3296
  const fallbackText = typeof fallback === "string" || typeof fallback === "number" ? String(fallback) : "";
3255
- const display = text || fallbackText;
3297
+ const hint = COMPOSER_EMPTY_FIELD_HINT;
3298
+ const idleDisplay = storedEmpty ? hint : text || fallbackText;
3256
3299
  const showRendered = !!render && !editing;
3300
+ const showHint = storedEmpty && !focused && !showRendered;
3257
3301
  useEffect(() => {
3258
3302
  const el = elRef.current;
3259
3303
  if (!el || showRendered) return;
3260
3304
  if (document.activeElement === el) return;
3261
- const next = display || "";
3305
+ if (el.querySelector(
3306
+ "input, button, select, textarea, form, svg, iframe, img"
3307
+ )) {
3308
+ return;
3309
+ }
3310
+ const next = idleDisplay || "";
3262
3311
  if (el.textContent !== next) el.textContent = next;
3263
- }, [display, showRendered]);
3312
+ }, [idleDisplay, showRendered]);
3264
3313
  useEffect(() => {
3265
3314
  if (!editing || !elRef.current) return;
3266
3315
  const el = elRef.current;
3267
- el.textContent = display || "";
3316
+ if (storedEmpty) {
3317
+ el.textContent = "";
3318
+ el.focus();
3319
+ placeCaret(el, true);
3320
+ return;
3321
+ }
3322
+ el.textContent = idleDisplay || "";
3268
3323
  el.focus();
3269
- const range = document.createRange();
3270
- range.selectNodeContents(el);
3271
- const sel = window.getSelection();
3272
- sel?.removeAllRanges();
3273
- sel?.addRange(range);
3274
- }, [editing]);
3324
+ placeCaret(el, false);
3325
+ }, [editing, storedEmpty, idleDisplay]);
3275
3326
  const finishEdit = () => {
3276
3327
  const raw = (elRef.current?.innerText ?? "").replace(/\u00a0/g, " ").replace(/\n+$/g, "");
3277
3328
  const next = raw.trim() === "" ? "" : raw;
3278
3329
  setEditing(false);
3330
+ setFocused(false);
3331
+ if (storedEmpty && next === "") {
3332
+ if (elRef.current) elRef.current.textContent = hint;
3333
+ if (typeof value === "string" && EMPTY_FIELD_PLACEHOLDERS.has(value.trim())) {
3334
+ commitComposerField(layoutNodeId, componentId, name, "");
3335
+ }
3336
+ return;
3337
+ }
3279
3338
  if (next === text) return;
3280
- if (!text && next === fallbackText) return;
3339
+ if (!text && !storedEmpty && next === fallbackText) return;
3281
3340
  commitComposerField(layoutNodeId, componentId, name, next);
3282
3341
  };
3283
3342
  return /* @__PURE__ */ jsx(
@@ -3287,6 +3346,7 @@ function ComposerInlineField({
3287
3346
  className,
3288
3347
  "data-amplifyup-field": name,
3289
3348
  "data-amplifyup-node-id": layoutNodeId || void 0,
3349
+ "data-amplifyup-empty-hint": showHint ? "true" : void 0,
3290
3350
  contentEditable: !showRendered,
3291
3351
  suppressContentEditableWarning: true,
3292
3352
  role: "textbox",
@@ -3303,6 +3363,12 @@ function ComposerInlineField({
3303
3363
  },
3304
3364
  onFocus: () => {
3305
3365
  if (!isComposerEditMode()) return;
3366
+ setFocused(true);
3367
+ const el = elRef.current;
3368
+ if (el && storedEmpty) {
3369
+ el.textContent = "";
3370
+ placeCaret(el, true);
3371
+ }
3306
3372
  if (render) setEditing(true);
3307
3373
  },
3308
3374
  onBlur: finishEdit,
@@ -3314,8 +3380,9 @@ function ComposerInlineField({
3314
3380
  }
3315
3381
  if (event.key === "Escape") {
3316
3382
  event.preventDefault();
3317
- if (elRef.current) elRef.current.textContent = display || "";
3383
+ if (elRef.current) elRef.current.textContent = idleDisplay || "";
3318
3384
  setEditing(false);
3385
+ setFocused(false);
3319
3386
  event.currentTarget.blur();
3320
3387
  }
3321
3388
  },
@@ -3324,12 +3391,59 @@ function ComposerInlineField({
3324
3391
  outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
3325
3392
  outlineOffset: "2px",
3326
3393
  minWidth: "1ch",
3327
- display: render ? void 0 : "inline"
3394
+ minHeight: "1em",
3395
+ display: render ? void 0 : "inline",
3396
+ opacity: showHint ? 0.5 : void 0
3328
3397
  },
3329
- children: showRendered ? value != null && value !== "" ? render(value) : fallback ?? display : null
3398
+ children: showRendered ? value != null && value !== "" && !storedEmpty ? render(value) : fallback ?? idleDisplay : isReactNodeFallback(fallback) && !idleDisplay ? fallback : null
3330
3399
  }
3331
3400
  );
3332
3401
  }
3402
+ function ComposerInlineHtml({
3403
+ name,
3404
+ html,
3405
+ className,
3406
+ Tag = "div",
3407
+ layoutNodeId,
3408
+ componentId
3409
+ }) {
3410
+ const elRef = useRef(null);
3411
+ const sourceTextRef = useRef("");
3412
+ useEffect(() => {
3413
+ const el = elRef.current;
3414
+ if (!el) return;
3415
+ if (document.activeElement === el || el.contains(document.activeElement)) {
3416
+ return;
3417
+ }
3418
+ if (el.innerHTML !== html) el.innerHTML = html;
3419
+ sourceTextRef.current = (el.innerText ?? "").replace(/\u00a0/g, " ").trim();
3420
+ }, [html]);
3421
+ const finishEdit = () => {
3422
+ const el = elRef.current;
3423
+ if (!el) return;
3424
+ const next = (el.innerText ?? "").replace(/\u00a0/g, " ").replace(/\n+$/g, "");
3425
+ if (next.trim() === sourceTextRef.current) return;
3426
+ commitComposerField(layoutNodeId, componentId, name, next.trim());
3427
+ };
3428
+ return React.createElement(Tag, {
3429
+ ref: elRef,
3430
+ className,
3431
+ "data-amplifyup-field": name,
3432
+ "data-amplifyup-node-id": layoutNodeId || void 0,
3433
+ contentEditable: true,
3434
+ suppressContentEditableWarning: true,
3435
+ dangerouslySetInnerHTML: { __html: html },
3436
+ onMouseDown: (event) => {
3437
+ event.stopPropagation();
3438
+ },
3439
+ onBlur: finishEdit,
3440
+ style: {
3441
+ cursor: "text",
3442
+ outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
3443
+ outlineOffset: "2px"
3444
+ }
3445
+ });
3446
+ }
3333
3447
  function formatFieldValue(value) {
3334
3448
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
3335
3449
  return String(value);
@@ -3396,7 +3510,13 @@ function Field({
3396
3510
  if (render) {
3397
3511
  return /* @__PURE__ */ jsx("span", { className, style: { display: "contents" }, children: render(fieldValue) });
3398
3512
  }
3399
- if (composerEdit && canInlineEdit(name, fieldValue)) {
3513
+ if (Array.isArray(fieldValue)) {
3514
+ const list = renderLinkList(fieldValue, className);
3515
+ if (list) return list;
3516
+ }
3517
+ const text = formatFieldValue(fieldValue);
3518
+ const showVisualFallback = !text && isReactNodeFallback(fallback);
3519
+ if (composerEdit && canInlineEdit(name, fieldValue) && !showVisualFallback) {
3400
3520
  return /* @__PURE__ */ jsx(
3401
3521
  ComposerInlineField,
3402
3522
  {
@@ -3412,11 +3532,6 @@ function Field({
3412
3532
  if (fieldValue === void 0 || fieldValue === null) {
3413
3533
  return fallback ? /* @__PURE__ */ jsx("span", { className, children: fallback }) : null;
3414
3534
  }
3415
- if (Array.isArray(fieldValue)) {
3416
- const list = renderLinkList(fieldValue, className);
3417
- if (list) return list;
3418
- }
3419
- const text = formatFieldValue(fieldValue);
3420
3535
  if (!text) {
3421
3536
  return fallback ? /* @__PURE__ */ jsx("span", { className, children: fallback }) : null;
3422
3537
  }
@@ -3447,20 +3562,20 @@ function RichText({
3447
3562
  console.warn(`[AmplifyUp SDK] RichText "${name}" used outside ComponentContextProvider`);
3448
3563
  return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
3449
3564
  }
3450
- if (composerEdit && canInlineEdit(name, fieldValue)) {
3451
- return /* @__PURE__ */ jsx(
3452
- ComposerInlineField,
3453
- {
3454
- name,
3455
- value: fieldValue,
3456
- fallback,
3457
- className,
3458
- layoutNodeId: context.layoutNodeId,
3459
- componentId: context.componentId
3460
- }
3461
- );
3462
- }
3463
- if (fieldValue === void 0 || fieldValue === null || fieldValue === "") {
3565
+ if (isComposerEmptyText(fieldValue)) {
3566
+ if (composerEdit && canInlineEdit(name, fieldValue) && !isReactNodeFallback(fallback)) {
3567
+ return /* @__PURE__ */ jsx(
3568
+ ComposerInlineField,
3569
+ {
3570
+ name,
3571
+ value: fieldValue,
3572
+ fallback: typeof fallback === "string" || typeof fallback === "number" ? fallback : void 0,
3573
+ className,
3574
+ layoutNodeId: context.layoutNodeId,
3575
+ componentId: context.componentId
3576
+ }
3577
+ );
3578
+ }
3464
3579
  return fallback ? /* @__PURE__ */ jsx(Tag, { className, children: fallback }) : null;
3465
3580
  }
3466
3581
  let html = "";
@@ -3478,6 +3593,30 @@ function RichText({
3478
3593
  if (!html) {
3479
3594
  return fallback ? /* @__PURE__ */ jsx(Tag, { className, children: fallback }) : null;
3480
3595
  }
3596
+ if (composerEdit && htmlHasVisualChrome(html)) {
3597
+ return /* @__PURE__ */ jsx(
3598
+ Tag,
3599
+ {
3600
+ className,
3601
+ "data-amplifyup-field": name,
3602
+ "data-amplifyup-node-id": context.layoutNodeId || void 0,
3603
+ dangerouslySetInnerHTML: { __html: html }
3604
+ }
3605
+ );
3606
+ }
3607
+ if (composerEdit && canInlineEdit(name, fieldValue)) {
3608
+ return /* @__PURE__ */ jsx(
3609
+ ComposerInlineHtml,
3610
+ {
3611
+ name,
3612
+ html,
3613
+ className,
3614
+ Tag,
3615
+ layoutNodeId: context.layoutNodeId,
3616
+ componentId: context.componentId
3617
+ }
3618
+ );
3619
+ }
3481
3620
  return /* @__PURE__ */ jsx(Tag, { className, dangerouslySetInnerHTML: { __html: html } });
3482
3621
  }
3483
3622
  function isPortableImageValue(value) {
@@ -3528,6 +3667,12 @@ function Image({
3528
3667
  }
3529
3668
  );
3530
3669
  }
3670
+ function nodeRenderProps(node) {
3671
+ return {
3672
+ ...node.settings || {},
3673
+ ...node.props || {}
3674
+ };
3675
+ }
3531
3676
  var ComponentRenderBoundary = class extends Component {
3532
3677
  constructor() {
3533
3678
  super(...arguments);
@@ -3951,7 +4096,7 @@ function AmplifyRenderer({
3951
4096
  {
3952
4097
  nodeId: node.id,
3953
4098
  componentId: node.componentId,
3954
- props: node.props || {},
4099
+ props: nodeRenderProps(node),
3955
4100
  slots: slotsToPass,
3956
4101
  renderComponent
3957
4102
  }