@nascentdigital/funnel-editor 4.5.0 → 4.5.1

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.
@@ -1,13 +1,17 @@
1
- import { RequiredBy } from '@nascentdigital/funnel-core';
1
+ import { RequiredBy, Transform } from '@nascentdigital/funnel-core';
2
+ import { ReactNode } from 'react';
2
3
  import { Popup } from './Popup';
3
- export declare const QuickPicker: import("react").ForwardRefExoticComponent<QuickPicker.Props & import("react").RefAttributes<QuickPicker.Ref<string, QuickPicker.Item<string>>>>;
4
+ export declare const QuickPicker: import("react").ForwardRefExoticComponent<QuickPicker.Props<string, QuickPicker.Item<string>> & import("react").RefAttributes<QuickPicker.Ref<string, QuickPicker.Item<string>>>>;
4
5
  export declare namespace QuickPicker {
5
- type Item<TId extends string = string> = {
6
+ type Item<TId extends string> = {
6
7
  id: TId;
7
8
  };
8
- type Props = {
9
+ type ItemVariant = 'card';
10
+ type Props<TId extends string, TItem extends Item<TId>> = {
11
+ variant: ItemVariant;
9
12
  title: string;
10
13
  description?: string;
14
+ renderItem: Transform<TItem, ReactNode>;
11
15
  };
12
16
  interface Ref<TId extends string, TItem extends Item<TId>> {
13
17
  readonly isOpen: boolean;
@@ -21,7 +21,7 @@ const classnames_1 = __importDefault(require("classnames"));
21
21
  const react_1 = require("react");
22
22
  const Popup_1 = require("./Popup");
23
23
  // component
24
- exports.QuickPicker = (0, react_1.forwardRef)(function QuickPicker({ title, description }, ref) {
24
+ exports.QuickPicker = (0, react_1.forwardRef)(function QuickPicker({ variant, title, description, renderItem }, ref) {
25
25
  // bind state
26
26
  const [opened, setOpened] = (0, react_1.useState)(false);
27
27
  const [deferral, setDeferral] = (0, react_1.useState)();
@@ -38,6 +38,7 @@ exports.QuickPicker = (0, react_1.forwardRef)(function QuickPicker({ title, desc
38
38
  },
39
39
  open: async (_a) => {
40
40
  var { items, selectedItem } = _a, popupProps = __rest(_a, ["items", "selectedItem"]);
41
+ console.debug('[QuickPicker] opening with', { items, selectedItem });
41
42
  // reset
42
43
  setItems(items);
43
44
  setSelectedItem(selectedItem);
@@ -55,19 +56,11 @@ exports.QuickPicker = (0, react_1.forwardRef)(function QuickPicker({ title, desc
55
56
  close: () => { var _a; return (_a = deferralRef.current) === null || _a === void 0 ? void 0 : _a.resolve(undefined); }
56
57
  }), []);
57
58
  // helpers
58
- const onPick = (e) => {
59
- // stop propagation
60
- e.preventDefault();
61
- e.stopPropagation();
59
+ const onPick = (item) => {
62
60
  // resolve
63
- deferral === null || deferral === void 0 ? void 0 : deferral.resolve(selectedItem);
61
+ deferral === null || deferral === void 0 ? void 0 : deferral.resolve(item);
64
62
  };
65
- const onClose = (e) => {
66
- // stop propagation
67
- if (e) {
68
- e.preventDefault();
69
- e.stopPropagation();
70
- }
63
+ const onClose = () => {
71
64
  // ignore if already complete
72
65
  if (deferral === null || deferral === void 0 ? void 0 : deferral.isComplete) {
73
66
  return;
@@ -79,17 +72,37 @@ exports.QuickPicker = (0, react_1.forwardRef)(function QuickPicker({ title, desc
79
72
  // handle keys
80
73
  switch (e.key) {
81
74
  case 'Enter':
82
- onPick(e);
75
+ // stop propagation
76
+ e.preventDefault();
77
+ e.stopPropagation();
78
+ // pick
79
+ onPick(selectedItem);
83
80
  break;
84
81
  case 'Escape':
85
- onClose(e);
82
+ // stop propagation
83
+ e.preventDefault();
84
+ e.stopPropagation();
85
+ // close
86
+ onClose();
86
87
  break;
87
88
  }
88
89
  };
89
90
  // render
90
- return ((0, jsx_runtime_1.jsxs)(Popup_1.Popup, Object.assign({}, (popupProps !== null && popupProps !== void 0 ? popupProps : {}), { className: (0, classnames_1.default)('glass-overlay w-[372px] flex flex-col gap-l8 p-l8 interpolate', 'border-t border-solid border-glass-stroke-l1 rounded-md', {
91
+ return ((0, jsx_runtime_1.jsxs)(Popup_1.Popup, Object.assign({}, (popupProps !== null && popupProps !== void 0 ? popupProps : {}), { className: (0, classnames_1.default)('glass-overlay w-[420px] max-w-full flex flex-col gap-l8 pt-l8 interpolate', 'border-t border-solid border-glass-stroke-l1 rounded-md', {
91
92
  'opacity-none': !opened,
92
93
  'opacity-full': opened
93
- }), animation: "out", open: !!deferral, onOpened: () => setOpened(true), onClose: onClose, onClosed: () => setOpened(false), children: [(0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)("h3", { className: "heading-sm text-content-primary", children: title }), description &&
94
- (0, jsx_runtime_1.jsx)("p", { className: "body-sm text-content-secondary", children: description })] }), (0, jsx_runtime_1.jsx)("section", { children: "Items" })] })));
94
+ }), animation: "out", open: !!deferral, onOpened: () => setOpened(true), onClose: onClose, onClosed: () => setOpened(false), children: [(0, jsx_runtime_1.jsxs)("section", { className: "px-l8", children: [(0, jsx_runtime_1.jsx)("h3", { className: "heading-sm text-content-primary", children: title }), description &&
95
+ (0, jsx_runtime_1.jsx)("p", { className: "body-sm text-content-secondary", children: description })] }), (0, jsx_runtime_1.jsx)("section", { className: (0, classnames_1.default)({
96
+ 'flex flex-wrap items-center gap-l5 max-h-[240px] px-l8 pb-l8 overflow-y-scroll': variant === 'card'
97
+ }), children: items.map(item => renderCard({
98
+ item,
99
+ content: renderItem(item),
100
+ selected: item.id === selectedItem,
101
+ onSelect: itemId => onPick(itemId)
102
+ })) })] })));
95
103
  });
104
+ function renderCard({ item, content, selected, onSelect }) {
105
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)('FieldCard w-[116px] h-[88px]', {
106
+ 'FieldCard-selected': selected
107
+ }), onClick: () => onSelect(item.id), children: content }, item.id));
108
+ }
@@ -16,8 +16,10 @@ export declare class BlockConfiguration {
16
16
  constructor({ block: blockOrSectionBlock, assetStore }: BlockConfiguration.ConstructorArgs);
17
17
  get editableFields(): ReadonlyArray<FieldModel.Config>;
18
18
  set editableFields(fields: ReadonlyArray<FieldModel.Config>);
19
+ get blocksFields(): FieldModel.Config[];
20
+ get blockFieldData(): Schema.Block.Data.ForFields<Schema.Fields>;
19
21
  get desktopFields(): FieldModel.Config[];
20
- get mobileFields(): FieldModel.Config[];
22
+ get mobileFields(): never[];
21
23
  get nameField(): FieldModel.Config | undefined;
22
24
  get themeField(): FieldModel.Config | undefined;
23
25
  get layoutField(): Field<Schema.EnumField<Schema.EnumField.Options<PanelLayout>>> | undefined;
@@ -47,19 +47,41 @@ class BlockConfiguration {
47
47
  this._editableFields = fields;
48
48
  this.raiseEvent('changed');
49
49
  }
50
- get desktopFields() {
50
+ get blocksFields() {
51
51
  return [
52
52
  this.nameField,
53
- this.themeField,
54
- this._editableFields.find(field => field.id === 'contentAlign')
53
+ this.themeField
55
54
  ].filter(funnel_core_1.Value.isDefined);
56
55
  }
57
- get mobileFields() {
56
+ get blockFieldData() {
57
+ const { assetStore, block } = this;
58
+ return block.fields.reduce((data, field) => {
59
+ // link assets
60
+ const fieldValue = field.value;
61
+ if ((fieldValue === null || fieldValue === void 0 ? void 0 : fieldValue.type) === 'asset') {
62
+ const assetData = funnel_core_1.Schema.AssetField.Value.fromData(fieldValue);
63
+ const asset = (assetData === null || assetData === void 0 ? void 0 : assetData.id)
64
+ ? assetStore.getAsset(assetData === null || assetData === void 0 ? void 0 : assetData.id)
65
+ : undefined;
66
+ data[field.id] = assetData && asset
67
+ ? funnel_core_1.Schema.AssetField.LinkedValue.toData(funnel_core_1.Schema.AssetField.Value.toLinked(assetData, asset))
68
+ : fieldValue;
69
+ }
70
+ else {
71
+ data[field.id] = field.value;
72
+ }
73
+ // keep reducing
74
+ return data;
75
+ }, {});
76
+ }
77
+ get desktopFields() {
58
78
  return [
59
- this.nameField,
60
- this.themeField
79
+ this._editableFields.find(field => field.id === 'contentAlign')
61
80
  ].filter(funnel_core_1.Value.isDefined);
62
81
  }
82
+ get mobileFields() {
83
+ return [].filter(funnel_core_1.Value.isDefined);
84
+ }
63
85
  get nameField() {
64
86
  const { block, sectionBlock } = this;
65
87
  return sectionBlock
@@ -175,30 +197,12 @@ class BlockConfiguration {
175
197
  }
176
198
  sync() {
177
199
  // recompute the editable fields
178
- const { assetStore, block, fields, editableFields } = this;
200
+ const { block, blockFieldData: fieldData, fields, editableFields } = this;
179
201
  const configData = editableFields.reduce((data, field) => {
180
202
  data[field.id] = field.value;
181
203
  // keep reducing
182
204
  return data;
183
205
  }, {});
184
- const fieldData = block.fields.reduce((data, field) => {
185
- // link assets
186
- const fieldValue = field.value;
187
- if ((fieldValue === null || fieldValue === void 0 ? void 0 : fieldValue.type) === 'asset') {
188
- const assetData = funnel_core_1.Schema.AssetField.Value.fromData(fieldValue);
189
- const asset = (assetData === null || assetData === void 0 ? void 0 : assetData.id)
190
- ? assetStore.getAsset(assetData === null || assetData === void 0 ? void 0 : assetData.id)
191
- : undefined;
192
- data[field.id] = assetData && asset
193
- ? funnel_core_1.Schema.AssetField.LinkedValue.toData(funnel_core_1.Schema.AssetField.Value.toLinked(assetData, asset))
194
- : fieldValue;
195
- }
196
- else {
197
- data[field.id] = field.value;
198
- }
199
- // keep reducing
200
- return data;
201
- }, {});
202
206
  // get customized schema (if there are effects)
203
207
  const blockSchema = block.schema;
204
208
  const { getConfig, getLayout } = blockSchema;
@@ -20,7 +20,8 @@ exports.BlockConfigEditor = (0, react_2.forwardRef)(function BlockConfigEditor(_
20
20
  const pageController = (0, hooks_1.usePageController)();
21
21
  const touch = (0, client_1.useTouch)();
22
22
  const panelLayoutPickerRef = (0, react_2.useRef)(null);
23
- const { modified = false, valid = false } = configuration !== null && configuration !== void 0 ? configuration : {};
23
+ const { modified = false, valid = false, blocksFields = [], desktopFields = [], mobileFields = [] } = configuration !== null && configuration !== void 0 ? configuration : {};
24
+ const settingsFields = blockView === 'desktop' ? desktopFields : mobileFields;
24
25
  // sync w/ configuration
25
26
  (0, core_1.useBlockConfiguration)(configuration);
26
27
  // bind ref
@@ -65,7 +66,7 @@ exports.BlockConfigEditor = (0, react_2.forwardRef)(function BlockConfigEditor(_
65
66
  if (!layoutField) {
66
67
  return;
67
68
  }
68
- // pick new layout (if ignore if skipped)
69
+ // pick new layout
69
70
  const { schema: layoutSchema, value: layoutData } = layoutField;
70
71
  const selectedLayout = await ((_a = panelLayoutPickerRef.current) === null || _a === void 0 ? void 0 : _a.open({
71
72
  anchor: e.currentTarget,
@@ -77,8 +78,9 @@ exports.BlockConfigEditor = (0, react_2.forwardRef)(function BlockConfigEditor(_
77
78
  })),
78
79
  selectedItem: layoutData === null || layoutData === void 0 ? void 0 : layoutData.value
79
80
  }));
80
- if (!selectedLayout) {
81
- return;
81
+ // update if something was picked
82
+ if (selectedLayout) {
83
+ layoutField.value = funnel_core_1.Schema.EnumField.Value.toData(selectedLayout);
82
84
  }
83
85
  };
84
86
  // skip if there's no config
@@ -86,8 +88,22 @@ exports.BlockConfigEditor = (0, react_2.forwardRef)(function BlockConfigEditor(_
86
88
  return null;
87
89
  }
88
90
  // render
89
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(core_1.MiniEditor, { className: "w-[550px] max-w-full", title: "Block Configuration", open: !!deferral && !deferral.isComplete, anchor: anchor, anchorOffset: 4, onSave: onSave, onClose: onCancel, disableSave: !modified || !valid, customActions: (0, jsx_runtime_1.jsx)(core_1.Button, { variant: "secondary", size: "sm", disabled: configuration.isConfigInDefault, onClick: () => configuration.reset(), children: "Reset to Default" }), wide: true, children: [(0, jsx_runtime_1.jsxs)("section", { className: "flex items-center gap-l4", children: [(0, jsx_runtime_1.jsx)("span", { className: "label-sm text-content-primary flex-1", children: "Layout" }), (0, jsx_runtime_1.jsx)(core_1.Button, { variant: "secondary", size: "sm", iconLeft: "ArrowLeftRight", children: "Rearrange" }), (0, jsx_runtime_1.jsx)(core_1.Button, { variant: "secondary", size: "sm", iconLeft: "LayoutTemplate", onClick: onChangePanelLayout, children: "Change" })] }), (0, jsx_runtime_1.jsx)(core_1.Tabs, { items: blockViews, selectedItem: blockView, onSelect: setBlockView }), (0, jsx_runtime_1.jsx)("h4", { className: "heading-sm text-content-primary", children: "Settings" }), (0, jsx_runtime_1.jsx)("section", { className: "stacked p-l10 bg-surface1 rounded-md", children: (0, jsx_runtime_1.jsx)(react_1.AnimatePresence, { children: (0, jsx_runtime_1.jsx)(core_1.Animate, { effect: "fade", present: true, children: (0, jsx_runtime_1.jsx)(BlockPreview_1.BlockPreview, { variant: blockView }) }, blockView) }) }), (0, jsx_runtime_1.jsx)("section", { className: "grid grid-cols-2 auto-rows-auto items-start gap-l6", children: (0, jsx_runtime_1.jsx)(core_1.FieldErrorController, { children: (blockView === 'desktop' ? configuration.desktopFields : configuration.mobileFields).map(field => (0, jsx_runtime_1.jsx)(core_1.FieldEditor, { className: "col-span-2", field: field, variant: "wide" }, field.id)) }) }), (0, jsx_runtime_1.jsx)("hr", { className: "divider border-border-l1" })] }), (0, jsx_runtime_1.jsx)(core_1.QuickPicker, { ref: panelLayoutPickerRef, title: "Pick Block Layout", description: "Select an available layout for this block." })] }));
91
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(core_1.MiniEditor, { className: "w-[550px] max-w-full", title: "Block Configuration", open: !!deferral && !deferral.isComplete, anchor: anchor, anchorOffset: 4, onSave: onSave, onClose: onCancel, disableSave: !modified || !valid, customActions: (0, jsx_runtime_1.jsx)(core_1.Button, { variant: "secondary", size: "sm", disabled: configuration.isConfigInDefault, onClick: () => configuration.reset(), children: "Reset to Default" }), wide: true, children: [(0, jsx_runtime_1.jsxs)("section", { className: "flex items-center gap-l4", children: [(0, jsx_runtime_1.jsx)("span", { className: "label-sm text-content-primary flex-1", children: "Layout" }), (0, jsx_runtime_1.jsx)(core_1.Button, { variant: "secondary", size: "sm", iconLeft: "ArrowLeftRight", children: "Rearrange" }), (0, jsx_runtime_1.jsx)(core_1.Button, { variant: "secondary", size: "sm", iconLeft: "LayoutTemplate", onClick: onChangePanelLayout, children: "Change" })] }), blocksFields.length > 0 &&
92
+ (0, jsx_runtime_1.jsx)("section", { className: "grid grid-cols-2 auto-rows-auto items-start gap-l6", children: (0, jsx_runtime_1.jsx)(core_1.FieldErrorController, { children: blocksFields.map(field => (0, jsx_runtime_1.jsx)(core_1.FieldEditor, { className: "col-span-2", field: field, variant: "wide" }, field.id)) }) }), (0, jsx_runtime_1.jsx)(core_1.Tabs, { items: blockViews, selectedItem: blockView, onSelect: setBlockView }), (0, jsx_runtime_1.jsx)("section", { className: "stacked p-l6 bg-surface1 rounded-md", children: (0, jsx_runtime_1.jsx)(react_1.AnimatePresence, { children: (0, jsx_runtime_1.jsx)(core_1.Animate, { effect: "fade", present: true, children: (0, jsx_runtime_1.jsx)(BlockPreview_1.BlockPreview, { variant: blockView, configuration: configuration }) }, blockView) }) }), settingsFields.length > 0 &&
93
+ (0, jsx_runtime_1.jsx)("section", { className: "grid grid-cols-2 auto-rows-auto items-start gap-l6", children: (0, jsx_runtime_1.jsx)(core_1.FieldErrorController, { children: settingsFields.map(field => (0, jsx_runtime_1.jsx)(core_1.FieldEditor, { className: "col-span-2", field: field, variant: "wide" }, field.id)) }) }), (0, jsx_runtime_1.jsx)("hr", { className: "divider border-border-l1" })] }), (0, jsx_runtime_1.jsx)(core_1.QuickPicker, { ref: panelLayoutPickerRef, variant: "card", title: "Pick Block Layout", description: "Select an available layout for this block.", renderItem: (item) => renderPanelLayoutItem(item) })] }));
90
94
  });
95
+ function renderPanelLayoutItem(item) {
96
+ switch (item.id) {
97
+ case 'stack.h':
98
+ return ((0, jsx_runtime_1.jsx)("div", { children: "Stack H" }));
99
+ case 'stack.v':
100
+ return ((0, jsx_runtime_1.jsx)("div", { children: "Stack V" }));
101
+ case 'nested':
102
+ return ((0, jsx_runtime_1.jsx)("div", { children: "Nested" }));
103
+ default:
104
+ return ((0, jsx_runtime_1.jsx)("div", { children: item.id }));
105
+ }
106
+ }
91
107
  const blockViews = [
92
108
  { id: 'desktop', label: 'Desktop' },
93
109
  { id: 'mobile', label: 'Mobile' },
@@ -1,8 +1,10 @@
1
- export declare function BlockPreview({ className, variant }: BlockPreview.Props): import("react/jsx-runtime").JSX.Element;
1
+ import { BlockConfiguration } from '../../../core';
2
+ export declare function BlockPreview({ className, variant, configuration }: BlockPreview.Props): import("react/jsx-runtime").JSX.Element;
2
3
  export declare namespace BlockPreview {
3
4
  type Variant = 'desktop' | 'mobile';
4
5
  type Props = {
5
6
  className?: string;
6
7
  variant: Variant;
8
+ configuration: BlockConfiguration;
7
9
  };
8
10
  }
@@ -1,13 +1,456 @@
1
1
  "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
2
13
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
15
  };
5
16
  Object.defineProperty(exports, "__esModule", { value: true });
6
17
  exports.BlockPreview = BlockPreview;
7
18
  const jsx_runtime_1 = require("react/jsx-runtime");
19
+ const funnel_core_1 = require("@nascentdigital/funnel-core");
8
20
  const classnames_1 = __importDefault(require("classnames"));
21
+ const react_1 = require("react");
22
+ const core_1 = require("../../../core");
9
23
  // component
10
- function BlockPreview({ className, variant }) {
24
+ function BlockPreview({ className, variant, configuration }) {
25
+ // determine active panels
26
+ const { layout } = configuration;
27
+ // sync configuration
28
+ (0, core_1.useBlockConfiguration)(configuration);
29
+ // helpers
30
+ const onPanelClick = (panel) => {
31
+ console.debug('[BlockPreview] opening panel editor', panel);
32
+ };
33
+ // render
34
+ const tight = variant === 'mobile'
35
+ ? layout === null || layout === void 0 ? void 0 : layout.tight.mobile
36
+ : layout === null || layout === void 0 ? void 0 : layout.tight.desktop;
37
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)(className, 'w-full flex justify-center'), children: (0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)('bg-surface2 rounded-xs', {
38
+ 'min-h-[100px]': !layout,
39
+ 'w-[120px]': variant === 'desktop',
40
+ 'w-[64px]': variant === 'mobile',
41
+ 'pb-l4': tight === 'above',
42
+ 'pt-l4': tight === 'below',
43
+ 'py-l4': tight === 'none'
44
+ }), children: renderLayout({ variant, layout, onPanelClick }) }) }));
45
+ }
46
+ // helpers
47
+ var Skeleton;
48
+ (function (Skeleton) {
49
+ function HeadlinePart({ className, size }) {
50
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)(className, 'bg-ix-primary rounded-xxs overflow-hidden', {
51
+ 'h-6 ': size === 'sm',
52
+ 'h-8': size === 'md'
53
+ }) }));
54
+ }
55
+ Skeleton.HeadlinePart = HeadlinePart;
56
+ function CaptionPart({ className, size }) {
57
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)(className, 'h-4 bg-draft/tint3 rounded-px overflow-hidden', {
58
+ '': size === 'sm'
59
+ }) }));
60
+ }
61
+ Skeleton.CaptionPart = CaptionPart;
62
+ function MediaPart({ className, aspect }) {
63
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)(className, 'flex justify-center items-center bg-draft/tint3 rounded-xxs overflow-hidden', {
64
+ 'aspect-square': aspect === 'square' || aspect === 'auto',
65
+ 'aspect-phone': aspect === 'phone',
66
+ 'aspect-tv': aspect === 'tv',
67
+ 'aspect-widescreen': aspect === 'widescreen',
68
+ 'aspect-cinema': aspect === 'cinema'
69
+ }), children: (0, jsx_runtime_1.jsx)(core_1.Icon, { className: "text-content-tertiary", variant: "Image", size: {
70
+ size: 20,
71
+ strokeWidth: 1.5,
72
+ absoluteStrokeWidth: false
73
+ } }) }));
74
+ }
75
+ Skeleton.MediaPart = MediaPart;
76
+ function IconPart({ className, size }) {
77
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)(className, 'bg-draft/tint3 rounded-full overflow-hidden', {
78
+ 'w-10 h-10': size === 'sm',
79
+ 'w-14 h-14': size === 'md'
80
+ }) }));
81
+ }
82
+ Skeleton.IconPart = IconPart;
83
+ function LogoPart({ className, size }) {
84
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)(className, 'bg-draft/tint3 rounded-px overflow-hidden', {
85
+ 'w-14 h-6': size === 'sm',
86
+ 'w-20 h-9': size === 'md'
87
+ }) }));
88
+ }
89
+ Skeleton.LogoPart = LogoPart;
90
+ })(Skeleton || (Skeleton = {}));
91
+ function renderLayout({ className, variant, layout, onPanelClick }) {
92
+ // TODO: render empty state if there's no layout
93
+ if (!layout) {
94
+ return null;
95
+ }
96
+ // render root layout
97
+ const { type, items } = layout.root;
98
+ return ((0, jsx_runtime_1.jsx)("div", { className: getContainerClasses({ className, variant, siblingCount: 1, item: layout.root }), children: items.map((item, parentOffset) => (0, jsx_runtime_1.jsx)(react_1.Fragment, { children: renderItem({
99
+ className: (0, classnames_1.default)({
100
+ 'flex-1': type === 'row' && variant !== 'mobile'
101
+ }),
102
+ variant,
103
+ parent: type,
104
+ parentNested: false,
105
+ siblingCount: items.length,
106
+ item,
107
+ itemPosition: new ItemPosition({
108
+ parent: type,
109
+ parentOffset,
110
+ parentSize: items.length
111
+ }),
112
+ onPanelClick
113
+ }) }, `${item.type}.${parentOffset}`)) }));
114
+ }
115
+ function renderItem({ className, parent, parentNested, siblingCount, variant, item, itemPosition, onPanelClick }) {
116
+ // render item
117
+ const { type: itemType } = item;
118
+ switch (itemType) {
119
+ case 'row':
120
+ case 'column':
121
+ case 'stack':
122
+ return ((0, jsx_runtime_1.jsx)("div", { className: getContainerClasses({ className, parent, siblingCount, variant, item }), children: item.items.map((item, parentOffset, items) => (0, jsx_runtime_1.jsx)(react_1.Fragment, { children: renderItem({
123
+ className: (0, classnames_1.default)({
124
+ 'flex-1': itemType === 'row'
125
+ }),
126
+ variant,
127
+ parent: itemType,
128
+ parentNested: true,
129
+ siblingCount: items.length,
130
+ item: item,
131
+ itemPosition: new ItemPosition({
132
+ parent: itemType,
133
+ parentOffset,
134
+ parentSize: items.length
135
+ }),
136
+ onPanelClick
137
+ }) }, `${item.type}.${parentOffset}`)) }));
138
+ case 'message':
139
+ return renderMessage({ className, parent, parentNested, siblingCount, variant, item, itemPosition, onPanelClick });
140
+ case 'visual':
141
+ return renderVisual({ className, parent, parentNested, siblingCount, variant, item, itemPosition, onPanelClick });
142
+ case 'list':
143
+ return renderList({ className, parent, parentNested, siblingCount, variant, item, itemPosition, onPanelClick });
144
+ default:
145
+ console.error('[SkeletonLayout] no support for BlockLayout item', item);
146
+ return null;
147
+ }
148
+ }
149
+ function getContainerClasses({ className, variant, parent, item }) {
150
+ const { type, alignY } = item;
151
+ // row style
152
+ if (type === 'row' && variant !== 'mobile') {
153
+ return (0, classnames_1.default)(className, 'grid gap-l3 px-l4', {
154
+ 'items-start': alignY === 'start',
155
+ 'items-center': alignY === 'center',
156
+ 'items-end': alignY === 'end',
157
+ 'grid-cols-[1fr,1fr]': item.ratio.desktop === '1/1',
158
+ 'grid-cols-[1fr,2fr]': item.ratio.desktop === '1/2',
159
+ 'grid-cols-[1fr,3fr]': item.ratio.desktop === '1/3',
160
+ 'grid-cols-[2fr,1fr]': item.ratio.desktop === '2/1',
161
+ 'grid-cols-[2fr,3fr]': item.ratio.desktop === '2/3',
162
+ 'grid-cols-[3fr,1fr]': item.ratio.desktop === '3/1',
163
+ 'grid-cols-[3fr,2fr]': item.ratio.desktop === '3/2'
164
+ });
165
+ }
166
+ // or stack
167
+ else if (type === 'stack') {
168
+ return (0, classnames_1.default)(className, 'stacked-stretch');
169
+ }
170
+ // or column
171
+ else {
172
+ return (0, classnames_1.default)(className, 'flex flex-col gap-l4');
173
+ }
174
+ }
175
+ function renderMessage(args) {
176
+ // resolve props
177
+ const { variant, item: { textAlign } } = args;
178
+ const messageClasses = variant === 'mobile'
179
+ ? (0, classnames_1.default)({
180
+ 'items-start': textAlign.mobile === 'left',
181
+ 'items-center': textAlign.mobile === 'center',
182
+ 'items-end': textAlign.mobile === 'right'
183
+ })
184
+ : (0, classnames_1.default)({
185
+ 'items-start': textAlign.desktop === 'left',
186
+ 'items-center': textAlign.desktop === 'center',
187
+ 'items-end': textAlign.desktop === 'right'
188
+ });
189
+ // render
190
+ return ((0, jsx_runtime_1.jsxs)(EditablePanel, Object.assign({}, args, { panelClassName: (0, classnames_1.default)(messageClasses, getPanelClasses(Object.assign(Object.assign({}, args), { className: 'flex flex-col' }))), children: [(0, jsx_runtime_1.jsx)(Skeleton.HeadlinePart, { className: "w-full mb-l2", size: "md" }), (0, jsx_runtime_1.jsx)(Skeleton.CaptionPart, { className: "w-full mb-px", size: "md" }), (0, jsx_runtime_1.jsx)(Skeleton.CaptionPart, { className: "w-full mb-px", size: "md" }), (0, jsx_runtime_1.jsx)(Skeleton.CaptionPart, { className: "w-20", size: "md" })] })));
191
+ }
192
+ function renderVisual(args) {
193
+ // resolve props
194
+ const { variant, item: { aspectRatio }, parent } = args;
195
+ const aspect = variant === 'mobile' ? aspectRatio.mobile : aspectRatio.desktop;
196
+ // render
197
+ return ((0, jsx_runtime_1.jsx)(EditablePanel, Object.assign({}, args, { panelClassName: (0, classnames_1.default)(getPanelClasses(Object.assign(Object.assign({}, args), { className: '' }))), children: (0, jsx_runtime_1.jsx)(Skeleton.MediaPart, { className: "w-full", aspect: aspect }) })));
198
+ }
199
+ function renderList(args) {
200
+ // resolve props
201
+ const { variant, parent, parentNested, item: { layout: layoutRaw, itemType, itemCount: itemCountRaw } } = args;
202
+ const layout = variant === 'mobile'
203
+ ? layoutRaw.mobile
204
+ : layoutRaw.desktop;
205
+ // render a grid
206
+ if (layout === 'grid') {
207
+ return renderGrid(args);
208
+ }
209
+ // or render row/column
210
+ const itemCount = variant === 'mobile'
211
+ ? itemCountRaw.mobile
212
+ : itemCountRaw.desktop;
213
+ return ((0, jsx_runtime_1.jsx)(EditablePanel, Object.assign({}, args, { panelClassName: getListPanelClasses(args), children: funnel_core_1.ArrayUtils.series(itemCount, 1).map(row => (0, jsx_runtime_1.jsx)(react_1.Fragment, { children: renderListItem({ variant, parent, parentNested, layout, itemType, index: row, count: itemCount }) }, `list.${row}`)) })));
214
+ }
215
+ function getListPanelClasses(args) {
216
+ // resolve props
217
+ const { variant, item: { layout: layoutRaw } } = args;
218
+ const layout = variant === 'mobile'
219
+ ? layoutRaw.mobile
220
+ : layoutRaw.desktop;
221
+ // determine list classes
222
+ let listClasses = '';
223
+ switch (layout) {
224
+ case 'row':
225
+ listClasses = 'grid grid-rows-1 auto-cols-auto grid-flow-col gap-l2';
226
+ break;
227
+ case 'column':
228
+ listClasses = 'flex flex-col gap-l2';
229
+ break;
230
+ }
231
+ // return completed classes
232
+ return getPanelClasses(Object.assign(Object.assign({}, args), { className: listClasses }));
233
+ }
234
+ function renderListItem({ className, variant, parent, parentNested, layout, itemType, index, count }) {
235
+ let itemClassName = '';
236
+ switch (itemType) {
237
+ case 'message':
238
+ case 'message+visual':
239
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)(className, 'flex flex-row p-3 bg-background rounded-xxs'), children: [itemType === 'message+visual' &&
240
+ (0, jsx_runtime_1.jsx)("div", { className: "flex justify-center items-center", children: (0, jsx_runtime_1.jsx)("div", { className: "bg-content-tertiary rounded-full w-4 h-4" }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 flex flex-col", children: [(0, jsx_runtime_1.jsx)(Skeleton.HeadlinePart, { className: "w-11/12 mb-l1", size: "sm" }), (0, jsx_runtime_1.jsx)(Skeleton.CaptionPart, { className: "w-full mb-px", size: "sm" }), (0, jsx_runtime_1.jsx)(Skeleton.CaptionPart, { className: "w-8/12", size: "sm" })] })] }));
241
+ case 'visual': {
242
+ // determine aspect
243
+ let aspect;
244
+ switch (layout) {
245
+ case 'column':
246
+ aspect = parent === 'row' || parentNested
247
+ ? 'widescreen'
248
+ : variant === 'mobile'
249
+ ? 'widescreen'
250
+ : 'cinema';
251
+ break;
252
+ case 'row':
253
+ aspect = parent === 'row' || parentNested
254
+ ? 'square'
255
+ : 'widescreen';
256
+ break;
257
+ case 'grid': {
258
+ // aspect for mobile/nested
259
+ if (variant === 'mobile' || parent === 'row' || parentNested) {
260
+ itemClassName = 'w-full';
261
+ aspect = count === 1
262
+ ? 'square'
263
+ : count <= 3
264
+ ? 'tv'
265
+ : 'widescreen';
266
+ }
267
+ // adjust aspect based on layout (if it's a grid)
268
+ else if (layout === 'grid') {
269
+ // set base class
270
+ itemClassName = 'min-w-[50px]';
271
+ // override base + pick aspect
272
+ switch (count) {
273
+ case 1:
274
+ aspect = 'widescreen';
275
+ break;
276
+ case 2:
277
+ aspect = 'widescreen';
278
+ break;
279
+ case 3:
280
+ itemClassName = index === 1
281
+ ? 'min-w-full'
282
+ : 'min-w-[50px]';
283
+ aspect = index === 1
284
+ ? 'cinema'
285
+ : 'tv';
286
+ break;
287
+ case 4:
288
+ aspect = 'tv';
289
+ break;
290
+ case 5:
291
+ itemClassName = index === 3
292
+ ? 'min-w-full'
293
+ : 'min-w-[50px]';
294
+ aspect = index === 3
295
+ ? 'cinema'
296
+ : 'tv';
297
+ break;
298
+ default:
299
+ aspect = 'widescreen';
300
+ break;
301
+ }
302
+ }
303
+ // otherwise standard
304
+ else {
305
+ aspect = 'widescreen';
306
+ }
307
+ break;
308
+ }
309
+ }
310
+ // create visual
311
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)(className, itemClassName), children: (0, jsx_runtime_1.jsx)(Skeleton.MediaPart, { aspect: aspect }) }));
312
+ }
313
+ case 'icon':
314
+ return ((0, jsx_runtime_1.jsx)(Skeleton.IconPart, { className: (0, classnames_1.default)(className, itemClassName), size: variant === 'mobile' ? 'md' : 'sm' }));
315
+ case 'logo':
316
+ return ((0, jsx_runtime_1.jsx)(Skeleton.LogoPart, { className: (0, classnames_1.default)(className, itemClassName), size: variant === 'mobile' ? 'md' : 'sm' }));
317
+ default:
318
+ return null;
319
+ }
320
+ }
321
+ function renderGrid(args) {
322
+ // determine props
323
+ const { variant, parent, parentNested, item: { itemType, itemCount: itemCountRaw } } = args;
324
+ const itemCount = variant === 'mobile'
325
+ ? itemCountRaw.mobile
326
+ : itemCountRaw.desktop;
327
+ const { panelClassName, panelItemClassName } = getGridPanelClasses(args);
328
+ // render grid (really a flexbox layout with controlled item widths)
329
+ return ((0, jsx_runtime_1.jsx)(EditablePanel, Object.assign({}, args, { panelClassName: panelClassName, children: funnel_core_1.ArrayUtils.series(itemCount, 1).map(row => (0, jsx_runtime_1.jsx)(react_1.Fragment, { children: renderListItem({
330
+ className: panelItemClassName,
331
+ variant,
332
+ parent,
333
+ parentNested,
334
+ layout: 'grid',
335
+ itemType, index: row,
336
+ count: itemCount
337
+ }) }, `list.${row}`)) })));
338
+ }
339
+ function getGridPanelClasses(args) {
340
+ // resolve props
341
+ const { variant, parent, parentNested, item: { itemType, itemCount: itemCountRaw, itemAlign: itemAlignRaw } } = args;
342
+ const itemAlign = variant === 'mobile'
343
+ ? itemAlignRaw.mobile
344
+ : itemAlignRaw.desktop;
345
+ // determine list classes
346
+ const panelClasses = (0, classnames_1.default)('flex flex-wrap gap-l2', {
347
+ 'justify-center': itemAlign === 'center',
348
+ 'justify-end': itemAlign === 'end'
349
+ });
350
+ let panelItemClasses = '';
351
+ switch (itemType) {
352
+ case 'message':
353
+ case 'message+visual':
354
+ panelItemClasses = (0, classnames_1.default)({
355
+ 'w-full': variant === 'mobile' || (variant === 'desktop' && (parent === 'row' || parentNested)),
356
+ 'w-[50px]': variant === 'desktop' && parent !== 'row' && !parentNested
357
+ });
358
+ break;
359
+ case 'visual':
360
+ panelItemClasses = (0, classnames_1.default)({
361
+ '': variant === 'mobile' || (variant === 'desktop' && (parent === 'row' || parentNested)),
362
+ 'flex-1': variant === 'desktop' && parent !== 'row' && !parentNested
363
+ });
364
+ break;
365
+ case 'icon':
366
+ panelItemClasses = (0, classnames_1.default)({});
367
+ break;
368
+ case 'logo':
369
+ panelItemClasses = (0, classnames_1.default)({
370
+ '': variant === 'desktop',
371
+ '': variant === 'mobile'
372
+ });
373
+ break;
374
+ }
375
+ // return completed classes
376
+ return {
377
+ panelClassName: getPanelClasses(Object.assign(Object.assign({}, args), { className: panelClasses })),
378
+ panelItemClassName: panelItemClasses
379
+ };
380
+ }
381
+ function getPanelWrapClasses({ className, variant, parent, parentNested, item: { mobileOrder, alignY, fullBleed }, itemPosition }) {
382
+ return variant === 'mobile'
383
+ ? (0, classnames_1.default)(className, 'relative w-full flex flex-col', {
384
+ 'justify-start': alignY === 'start',
385
+ 'justify-center': alignY === 'center',
386
+ 'justify-end': alignY === 'end',
387
+ 'order-first': mobileOrder === 'first',
388
+ 'order-last': mobileOrder === 'last',
389
+ 'px-l4': !fullBleed.mobile
390
+ })
391
+ : (0, classnames_1.default)(className, 'relative grid grid-cols-12', {
392
+ 'items-start': alignY === 'start',
393
+ 'items-center': alignY === 'center',
394
+ 'items-end': alignY === 'end',
395
+ 'px-l4': parent !== 'row' && !fullBleed.desktop && !parentNested,
396
+ '-ml-l4': itemPosition.left && fullBleed.desktop,
397
+ '-mr-l4': itemPosition.right && fullBleed.desktop
398
+ });
399
+ }
400
+ function getPanelClasses({ className, variant, parent, parentNested, item: { alignX, size } }) {
401
+ // force full width for mobile
402
+ if (variant === 'mobile') {
403
+ return (0, classnames_1.default)(className);
404
+ }
405
+ // or size for desktop
406
+ switch (parent === 'row' || parentNested ? 'full' : size) {
407
+ case 'xs':
408
+ return (0, classnames_1.default)(className, 'col-span-4', {
409
+ 'col-start-1': alignX === 'start',
410
+ 'col-start-5': alignX === 'center',
411
+ 'col-end-13': alignX === 'end'
412
+ });
413
+ case 'sm':
414
+ return (0, classnames_1.default)(className, 'col-span-6', {
415
+ 'col-start-1': alignX === 'start',
416
+ 'col-start-4': alignX === 'center',
417
+ 'col-end-13': alignX === 'end'
418
+ });
419
+ case 'md':
420
+ return (0, classnames_1.default)(className, 'col-span-8', {
421
+ 'col-start-1': alignX === 'start',
422
+ 'col-start-3': alignX === 'center',
423
+ 'col-end-13': alignX === 'end'
424
+ });
425
+ case 'lg':
426
+ return (0, classnames_1.default)(className, 'col-span-10', {
427
+ 'col-start-1': alignX === 'start',
428
+ 'col-start-2': alignX === 'center',
429
+ 'col-end-13': alignX === 'end'
430
+ });
431
+ case 'full':
432
+ return (0, classnames_1.default)(className, 'col-span-full');
433
+ default:
434
+ return className;
435
+ }
436
+ }
437
+ function EditablePanel(_a) {
438
+ var { panelClassName, children } = _a, args = __rest(_a, ["panelClassName", "children"]);
11
439
  // render
12
- return ((0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)(className, 'w-full flex justify-center items-center text-center'), children: variant === 'desktop' ? 'Desktop' : 'Mobile' }));
440
+ return ((0, jsx_runtime_1.jsx)("div", { className: getPanelWrapClasses(args), children: (0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)(panelClassName, 'relative'), children: [children, (0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)('group absolute inset-0 z-overlay cursor-pointer interpolate', 'bg-surface1 border border-solid, border-ix-primary rounded-xxs overflow-hidden', 'opacity-none hover:opacity-full'), onClick: () => args.onPanelClick(args.item), children: (0, jsx_runtime_1.jsx)("div", { className: (0, classnames_1.default)('w-full h-full interpolate', 'flex flex-col justify-center items-center gap-l6', 'text-ix-primary group-hover:bg-ix-primary/tint1 group-active:bg-ix-primary/tint3'), children: (0, jsx_runtime_1.jsx)("span", { className: "label-xs label-strong", children: "Edit" }) }) })] }) }));
441
+ }
442
+ // TODO: scrap this
443
+ class ItemPosition {
444
+ constructor(args) {
445
+ // initialize instance variables
446
+ this.parent = args.parent;
447
+ this.parentOffset = args.parentOffset;
448
+ this.parentSize = args.parentSize;
449
+ }
450
+ get left() {
451
+ return this.parent === 'row' && this.parentOffset === 0;
452
+ }
453
+ get right() {
454
+ return this.parent === 'row' && this.parentOffset === 1;
455
+ }
13
456
  }
@@ -2724,6 +2724,12 @@ video {
2724
2724
  --tw-border-opacity: 1;
2725
2725
  border-color: rgb(var(--color-destructive) / var(--tw-border-opacity, 1));
2726
2726
  }
2727
+ .FieldCard.FieldCard-selected{
2728
+ pointer-events: none;
2729
+ cursor: default;
2730
+ --tw-border-opacity: 1;
2731
+ border-color: rgb(var(--color-ix-primary) / var(--tw-border-opacity, 1));
2732
+ }
2727
2733
  .FieldCard.FieldCard-disabled{
2728
2734
  cursor: default;
2729
2735
  opacity: .38;
@@ -4564,6 +4570,9 @@ video {
4564
4570
  .h-\[80px\]{
4565
4571
  height: 80px;
4566
4572
  }
4573
+ .h-\[88px\]{
4574
+ height: 88px;
4575
+ }
4567
4576
  .h-dvh{
4568
4577
  height: 100dvh;
4569
4578
  }
@@ -4579,6 +4588,9 @@ video {
4579
4588
  .h-screen{
4580
4589
  height: 100vh;
4581
4590
  }
4591
+ .max-h-\[240px\]{
4592
+ max-height: 240px;
4593
+ }
4582
4594
  .max-h-\[400px\]{
4583
4595
  max-height: 400px;
4584
4596
  }
@@ -4669,6 +4681,9 @@ video {
4669
4681
  .w-\[100px\]{
4670
4682
  width: 100px;
4671
4683
  }
4684
+ .w-\[116px\]{
4685
+ width: 116px;
4686
+ }
4672
4687
  .w-\[120px\]{
4673
4688
  width: 120px;
4674
4689
  }
@@ -4690,9 +4705,6 @@ video {
4690
4705
  .w-\[330px\]{
4691
4706
  width: 330px;
4692
4707
  }
4693
- .w-\[372px\]{
4694
- width: 372px;
4695
- }
4696
4708
  .w-\[420px\]{
4697
4709
  width: 420px;
4698
4710
  }
@@ -5332,9 +5344,6 @@ video {
5332
5344
  .p-3{
5333
5345
  padding: 3px;
5334
5346
  }
5335
- .p-l10{
5336
- padding: 40px;
5337
- }
5338
5347
  .p-l2{
5339
5348
  padding: 4px;
5340
5349
  }
@@ -6005,6 +6014,9 @@ video {
6005
6014
  .hover\:underline:hover{
6006
6015
  text-decoration-line: underline;
6007
6016
  }
6017
+ .hover\:opacity-full:hover{
6018
+ opacity: 1;
6019
+ }
6008
6020
  .hover\:outline-ix-primary\/\[\.35\]:hover{
6009
6021
  outline-color: rgb(var(--color-ix-primary) / .35);
6010
6022
  }
@@ -6113,6 +6125,9 @@ video {
6113
6125
  --tw-bg-opacity: 1;
6114
6126
  background-color: rgb(var(--color-ix-primary-hover) / var(--tw-bg-opacity, 1));
6115
6127
  }
6128
+ .group:hover .group-hover\:bg-ix-primary\/tint1{
6129
+ background-color: rgb(var(--color-ix-primary) / .1);
6130
+ }
6116
6131
  .group:hover .group-hover\:bg-ix-util-hover\/tint1{
6117
6132
  background-color: rgb(var(--color-ix-util-hover) / .1);
6118
6133
  }
@@ -6136,6 +6151,9 @@ video {
6136
6151
  --tw-bg-opacity: 1;
6137
6152
  background-color: rgb(var(--color-ix-primary-press) / var(--tw-bg-opacity, 1));
6138
6153
  }
6154
+ .group:active .group-active\:bg-ix-primary\/tint3{
6155
+ background-color: rgb(var(--color-ix-primary) / .3);
6156
+ }
6139
6157
  .group:active .group-active\:bg-ix-util-press\/tint2{
6140
6158
  background-color: rgb(var(--color-ix-util-press) / .2);
6141
6159
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nascentdigital/funnel-editor",
3
- "version": "4.5.0",
3
+ "version": "4.5.1",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "restricted"
@@ -89,5 +89,5 @@
89
89
  "ts-jest": "^29",
90
90
  "type-fest": "^4"
91
91
  },
92
- "gitHead": "09e92177c535f933fe19944aec48a33aadb6e62d"
92
+ "gitHead": "3348c90ff56f3dd931ab564f8a6ad13d454a4ca1"
93
93
  }