@nascentdigital/funnel-editor 4.5.0 → 4.5.2
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/core/components/interaction/QuickPicker.d.ts +8 -4
- package/dist/core/components/interaction/QuickPicker.js +30 -17
- package/dist/core/components/interaction/SideSheet.d.ts +26 -0
- package/dist/core/components/interaction/SideSheet.js +200 -0
- package/dist/core/components/interaction/index.d.ts +1 -0
- package/dist/core/components/interaction/index.js +1 -0
- package/dist/core/domain/BlockConfiguration.d.ts +3 -1
- package/dist/core/domain/BlockConfiguration.js +29 -25
- package/dist/page/components/config/BlockConfig.js +6 -21
- package/dist/page/components/config/BlockConfigEditor.d.ts +6 -10
- package/dist/page/components/config/BlockConfigEditor.js +30 -37
- package/dist/page/components/config/BlockPreview.d.ts +3 -1
- package/dist/page/components/config/BlockPreview.js +445 -2
- package/dist/page/components/config/LegacyBlockConfigEditor.d.ts +6 -10
- package/dist/page/components/config/LegacyBlockConfigEditor.js +13 -41
- package/dist/styles/index.css +41 -9
- package/package.json +2 -2
|
@@ -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
|
|
6
|
+
type Item<TId extends string> = {
|
|
6
7
|
id: TId;
|
|
7
8
|
};
|
|
8
|
-
type
|
|
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 = (
|
|
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(
|
|
61
|
+
deferral === null || deferral === void 0 ? void 0 : deferral.resolve(item);
|
|
64
62
|
};
|
|
65
|
-
const onClose = (
|
|
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
|
-
|
|
75
|
+
// stop propagation
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
e.stopPropagation();
|
|
78
|
+
// pick
|
|
79
|
+
onPick(selectedItem);
|
|
83
80
|
break;
|
|
84
81
|
case 'Escape':
|
|
85
|
-
|
|
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-[
|
|
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", {
|
|
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
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Callback, Provider, Transform } from '@nascentdigital/funnel-core';
|
|
2
|
+
import { HTMLMotionProps, Variants } from 'framer-motion';
|
|
3
|
+
import { PropsWithChildren, ReactNode } from 'react';
|
|
4
|
+
export declare function SideSheet({ className, title, wide, backdrop, disabled, disableDismiss, disableOverlay, disableSave, canSave, renderSaveLabel, onSave: onSaveRaw, open, onOpened: onOpenedRaw, onClose: onCloseRaw, onClosed: onClosedRaw, onKeyDown: onKeyDownRaw, customActions, children, ...props }: SideSheet.Props): import("react").ReactPortal | null;
|
|
5
|
+
export declare namespace SideSheet {
|
|
6
|
+
type Props = Omit<HTMLMotionProps<'div'>, 'children'> & PropsWithChildren & {
|
|
7
|
+
title: ReactNode;
|
|
8
|
+
wide?: boolean;
|
|
9
|
+
backdrop?: Backdrop;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
disableDismiss?: boolean;
|
|
12
|
+
disableOverlay?: boolean;
|
|
13
|
+
disableSave?: boolean;
|
|
14
|
+
canSave?: Provider<boolean> | Provider.Async<boolean>;
|
|
15
|
+
renderSaveLabel?: Transform<State, string>;
|
|
16
|
+
onSave?: Callback.Void | Callback.AsyncVoid;
|
|
17
|
+
customActions?: ReactNode;
|
|
18
|
+
open?: boolean;
|
|
19
|
+
onOpened?: Callback.Void;
|
|
20
|
+
onClose?: Callback.Void;
|
|
21
|
+
onClosed?: Callback.Void;
|
|
22
|
+
};
|
|
23
|
+
type State = 'editing' | 'validating' | 'saving' | 'error' | 'saved';
|
|
24
|
+
type Backdrop = 'none' | 'glass';
|
|
25
|
+
}
|
|
26
|
+
export declare const Animations: Record<'background' | 'slideRight', Variants>;
|
|
@@ -0,0 +1,200 @@
|
|
|
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
|
+
};
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.Animations = void 0;
|
|
18
|
+
exports.SideSheet = SideSheet;
|
|
19
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
20
|
+
const funnel_core_1 = require("@nascentdigital/funnel-core");
|
|
21
|
+
const classnames_1 = __importDefault(require("classnames"));
|
|
22
|
+
const framer_motion_1 = require("framer-motion");
|
|
23
|
+
const react_1 = require("motion/react");
|
|
24
|
+
const react_2 = require("react");
|
|
25
|
+
const react_dom_1 = require("react-dom");
|
|
26
|
+
const Button_1 = require("./Button");
|
|
27
|
+
// component
|
|
28
|
+
function SideSheet(_a) {
|
|
29
|
+
var { className, title, wide, backdrop = 'none', disabled = false, disableDismiss = false, disableOverlay = false, disableSave = false, canSave = funnel_core_1.Fn.True, renderSaveLabel = onRenderSaveLabel, onSave: onSaveRaw = funnel_core_1.Fn.NoOp, open = false, onOpened: onOpenedRaw = funnel_core_1.Fn.NoOp, onClose: onCloseRaw = funnel_core_1.Fn.NoOp, onClosed: onClosedRaw = funnel_core_1.Fn.NoOp, onKeyDown: onKeyDownRaw, customActions, children } = _a, props = __rest(_a, ["className", "title", "wide", "backdrop", "disabled", "disableDismiss", "disableOverlay", "disableSave", "canSave", "renderSaveLabel", "onSave", "open", "onOpened", "onClose", "onClosed", "onKeyDown", "customActions", "children"]);
|
|
30
|
+
// bind state + hooksb= + refs
|
|
31
|
+
const [state, setState] = (0, react_2.useState)('editing');
|
|
32
|
+
const locked = disabled || state === 'validating' || state === 'saving' || state === 'saved';
|
|
33
|
+
const id = (0, react_2.useId)();
|
|
34
|
+
const openRef = (0, react_2.useRef)(open);
|
|
35
|
+
openRef.current = open;
|
|
36
|
+
const onOpenedRef = (0, react_2.useRef)(onOpenedRaw);
|
|
37
|
+
onOpenedRef.current = onOpenedRaw;
|
|
38
|
+
const onClosedRef = (0, react_2.useRef)(onClosedRaw);
|
|
39
|
+
onClosedRef.current = onClosedRaw;
|
|
40
|
+
// reset state on open
|
|
41
|
+
(0, react_2.useEffect)(() => {
|
|
42
|
+
if (open) {
|
|
43
|
+
setState('editing');
|
|
44
|
+
}
|
|
45
|
+
}, [open]);
|
|
46
|
+
// lock tab focus
|
|
47
|
+
(0, react_2.useEffect)(() => {
|
|
48
|
+
// lock when opened
|
|
49
|
+
let unlock = undefined;
|
|
50
|
+
if (open) {
|
|
51
|
+
unlock = funnel_core_1.Browser.lockFocus(`#${CSS.escape(id)}`);
|
|
52
|
+
}
|
|
53
|
+
// unlock on unmount
|
|
54
|
+
return () => unlock && unlock();
|
|
55
|
+
}, [open, id]);
|
|
56
|
+
// skip for SSR
|
|
57
|
+
if (!funnel_core_1.Browser.available) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
// helpers
|
|
61
|
+
const onSave = async (e) => {
|
|
62
|
+
console.debug('[SideSheet/onSave]', { state, disabled, open });
|
|
63
|
+
// consume event
|
|
64
|
+
if (e) {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
e.stopPropagation();
|
|
67
|
+
}
|
|
68
|
+
// ignore if state is invalid
|
|
69
|
+
if (state !== 'editing' && state !== 'error') {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// try to save
|
|
73
|
+
try {
|
|
74
|
+
// update state
|
|
75
|
+
setState('validating');
|
|
76
|
+
// skip if not valid
|
|
77
|
+
const valid = await Promise.resolve(canSave());
|
|
78
|
+
if (!valid) {
|
|
79
|
+
setState('error');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// update state
|
|
83
|
+
setState('saving');
|
|
84
|
+
// try to save
|
|
85
|
+
await Promise.resolve(onSaveRaw());
|
|
86
|
+
// mark as saved
|
|
87
|
+
setState('saved');
|
|
88
|
+
}
|
|
89
|
+
// handle error
|
|
90
|
+
catch (e) {
|
|
91
|
+
console.warn('[SideSheet/onSave] error', e);
|
|
92
|
+
// update state
|
|
93
|
+
setState('error');
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const onClose = (e) => {
|
|
97
|
+
// consume event
|
|
98
|
+
if (e) {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
e.stopPropagation();
|
|
101
|
+
}
|
|
102
|
+
// close
|
|
103
|
+
onCloseRaw();
|
|
104
|
+
};
|
|
105
|
+
const onKeyDown = async (e) => {
|
|
106
|
+
// ignore if disabled OR state is invalid
|
|
107
|
+
if (disabled || (state !== 'editing' && state !== 'error')) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// cancel on escape
|
|
111
|
+
switch (e.key) {
|
|
112
|
+
case 'Escape':
|
|
113
|
+
if (!disableDismiss) {
|
|
114
|
+
onClose(e);
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
case 'Enter':
|
|
118
|
+
await onSave(e);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const onAnimationComplete = (definition) => {
|
|
123
|
+
if (definition === 'visible') {
|
|
124
|
+
onOpenedRef.current();
|
|
125
|
+
}
|
|
126
|
+
else if (definition === 'exit') {
|
|
127
|
+
onClosedRef.current();
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const onBackdropClick = (e) => {
|
|
131
|
+
// close only if the backdrop is directly clicked
|
|
132
|
+
const target = e.target;
|
|
133
|
+
if ((target === null || target === void 0 ? void 0 : target.id) === id) {
|
|
134
|
+
// stop bubbling
|
|
135
|
+
e.stopPropagation();
|
|
136
|
+
// close
|
|
137
|
+
onClose();
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
// render
|
|
141
|
+
return (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)(react_1.AnimatePresence, { children: open &&
|
|
142
|
+
(0, jsx_runtime_1.jsxs)(framer_motion_1.motion.div, { id: id, className: (0, classnames_1.default)('fixed inset-0 z-popup', {
|
|
143
|
+
'pointer-events-none': disableOverlay
|
|
144
|
+
}), variants: exports.Animations.background, initial: "initial", animate: "animate", exit: "exit", onAnimationComplete: definition => onAnimationComplete(definition), onClick: disableDismiss || locked ? undefined : onBackdropClick, layout: true, children: [backdrop !== 'none' &&
|
|
145
|
+
(0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { className: (0, classnames_1.default)('fixed inset-0 z-backdrop pointer-events-none', {
|
|
146
|
+
'backdrop-blur-[9px] bg-glass/backdrop will-change-[backdrop-filter]': backdrop === 'glass'
|
|
147
|
+
}) }, "backdrop"), (0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, Object.assign({}, props, { className: (0, classnames_1.default)(className, 'fixed right-0 inset-y-0 pointer-events-none', {
|
|
148
|
+
'w-full sm:w-[380px]': !wide,
|
|
149
|
+
'w-full md:w-screen-sm': wide,
|
|
150
|
+
}), variants: exports.Animations.slideRight, initial: "initial", animate: "animate", exit: "exit", onKeyDown: onKeyDown, tabIndex: -1, children: (0, jsx_runtime_1.jsxs)("div", { className: (0, classnames_1.default)(className, 'w-full h-full max-h-full pointer-events-auto', 'flex flex-col', 'bg-background border-l border-solid border-border-l1 rounded-tl-md rounded-bl-md'), children: [(0, jsx_runtime_1.jsx)("h2", { className: "heading-md text-content-primary p-l8", children: title }), (0, jsx_runtime_1.jsx)("div", { className: "flex-1 overflow-y-scroll", children: children }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-end items-center gap-l4 p-l8", children: [(0, jsx_runtime_1.jsx)(Button_1.Button, { variant: "text", size: "sm", disabled: locked, onClick: onClose, children: "Cancel" }), customActions &&
|
|
151
|
+
(0, jsx_runtime_1.jsx)("div", { className: "flex-1 flex justify-end items-center gap-l4", children: customActions }), (0, jsx_runtime_1.jsx)(Button_1.Button, { variant: "primary", size: "sm", loading: state === 'saving', disabled: disableSave || locked, onClick: onSave, children: renderSaveLabel(state) })] })] }) }))] }, id) }), document.body);
|
|
152
|
+
}
|
|
153
|
+
// helpers
|
|
154
|
+
exports.Animations = {
|
|
155
|
+
background: {
|
|
156
|
+
hidden: {
|
|
157
|
+
opacity: 0
|
|
158
|
+
},
|
|
159
|
+
visible: {
|
|
160
|
+
opacity: 1,
|
|
161
|
+
transition: {
|
|
162
|
+
ease: 'easeIn',
|
|
163
|
+
duration: 0.3,
|
|
164
|
+
when: 'beforeChildren'
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
exit: {
|
|
168
|
+
opacity: 0,
|
|
169
|
+
transition: {
|
|
170
|
+
ease: 'easeIn',
|
|
171
|
+
duration: 0.15,
|
|
172
|
+
bounce: 0,
|
|
173
|
+
when: 'afterChildren'
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
slideRight: {
|
|
178
|
+
initial: {
|
|
179
|
+
x: '100%'
|
|
180
|
+
},
|
|
181
|
+
animate: {
|
|
182
|
+
x: '0'
|
|
183
|
+
},
|
|
184
|
+
exit: {
|
|
185
|
+
x: '100%'
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
function onRenderSaveLabel(state) {
|
|
190
|
+
switch (state) {
|
|
191
|
+
case 'editing':
|
|
192
|
+
case 'validating':
|
|
193
|
+
case 'error':
|
|
194
|
+
return 'Save';
|
|
195
|
+
case 'saving':
|
|
196
|
+
return 'Saving...';
|
|
197
|
+
case 'saved':
|
|
198
|
+
return 'Saved';
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -41,6 +41,7 @@ __exportStar(require("./Profile"), exports);
|
|
|
41
41
|
__exportStar(require("./QuickInput"), exports);
|
|
42
42
|
__exportStar(require("./QuickPicker"), exports);
|
|
43
43
|
__exportStar(require("./SegmentedButton"), exports);
|
|
44
|
+
__exportStar(require("./SideSheet"), exports);
|
|
44
45
|
__exportStar(require("./Sortable"), exports);
|
|
45
46
|
__exportStar(require("./Tabs"), exports);
|
|
46
47
|
__exportStar(require("./Toggle"), exports);
|
|
@@ -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():
|
|
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
|
|
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
|
|
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.
|
|
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 {
|
|
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;
|
|
@@ -9,8 +9,7 @@ const LegacyBlockConfigEditor_1 = require("./LegacyBlockConfigEditor");
|
|
|
9
9
|
// component
|
|
10
10
|
function BlockConfig({ className, block: blockOrSectionBlock }) {
|
|
11
11
|
// bind ref
|
|
12
|
-
const
|
|
13
|
-
const legacyEditorRef = (0, react_1.useRef)(null);
|
|
12
|
+
const [configuration, setConfiguration] = (0, react_1.useState)();
|
|
14
13
|
// skip if there's no config
|
|
15
14
|
const block = blockOrSectionBlock instanceof core_1.SectionBlock
|
|
16
15
|
? blockOrSectionBlock.block
|
|
@@ -19,25 +18,11 @@ function BlockConfig({ className, block: blockOrSectionBlock }) {
|
|
|
19
18
|
if (config.length === 0) {
|
|
20
19
|
return undefined;
|
|
21
20
|
}
|
|
22
|
-
// helpers
|
|
23
|
-
const onEdit = async (event) => {
|
|
24
|
-
var _a, _b;
|
|
25
|
-
// use new configurator (if enabled)
|
|
26
|
-
if (process.env.NEXT_PUBLIC_EDITOR_BLOCK_CONFIG === 'experimental') {
|
|
27
|
-
await ((_a = editorRef.current) === null || _a === void 0 ? void 0 : _a.edit({
|
|
28
|
-
block: blockOrSectionBlock,
|
|
29
|
-
anchor: event.currentTarget
|
|
30
|
-
}));
|
|
31
|
-
}
|
|
32
|
-
// or use legacy version
|
|
33
|
-
else {
|
|
34
|
-
await ((_b = legacyEditorRef.current) === null || _b === void 0 ? void 0 : _b.edit({
|
|
35
|
-
block: blockOrSectionBlock,
|
|
36
|
-
anchor: event.currentTarget
|
|
37
|
-
}));
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
21
|
// render
|
|
41
22
|
const valid = config.every(field => field.valid);
|
|
42
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(core_1.StatusChip, { className: className, variant: valid ? 'success' : 'error', onClick:
|
|
23
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(core_1.StatusChip, { className: className, variant: valid ? 'success' : 'error', onClick: () => setConfiguration(block.getConfiguration()), children: "Configure" }), process.env.NEXT_PUBLIC_EDITOR_BLOCK_CONFIG === 'experimental'
|
|
24
|
+
?
|
|
25
|
+
(0, jsx_runtime_1.jsx)(BlockConfigEditor_1.BlockConfigEditor, { configuration: configuration, onClose: () => setConfiguration(undefined) })
|
|
26
|
+
:
|
|
27
|
+
(0, jsx_runtime_1.jsx)(LegacyBlockConfigEditor_1.LegacyBlockConfigEditor, { configuration: configuration, onClose: () => setConfiguration(undefined) })] }));
|
|
43
28
|
}
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export declare
|
|
1
|
+
import { Callback } from '@nascentdigital/funnel-core';
|
|
2
|
+
import { BlockConfiguration } from '../../../core';
|
|
3
|
+
export declare function BlockConfigEditor({ configuration, onClose }: BlockConfigEditor.Props): import("react/jsx-runtime").JSX.Element | null;
|
|
4
4
|
export declare namespace BlockConfigEditor {
|
|
5
|
-
type Props = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
type Options = {
|
|
10
|
-
block: Block | SectionBlock;
|
|
11
|
-
anchor: HTMLElement | Rect;
|
|
5
|
+
type Props = {
|
|
6
|
+
configuration?: BlockConfiguration;
|
|
7
|
+
onClose: Callback.Void;
|
|
12
8
|
};
|
|
13
9
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BlockConfigEditor =
|
|
3
|
+
exports.BlockConfigEditor = BlockConfigEditor;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const funnel_core_1 = require("@nascentdigital/funnel-core");
|
|
6
6
|
const client_1 = require("@nascentdigital/funnel-ui/client");
|
|
@@ -11,52 +11,30 @@ const core_1 = require("../../../core");
|
|
|
11
11
|
const hooks_1 = require("../../hooks");
|
|
12
12
|
const BlockPreview_1 = require("./BlockPreview");
|
|
13
13
|
// editor
|
|
14
|
-
|
|
14
|
+
function BlockConfigEditor({ configuration, onClose }) {
|
|
15
15
|
// bind state + hooks
|
|
16
|
-
const [deferral, setDeferral] = (0, react_2.useState)(undefined);
|
|
17
|
-
const [configuration, setConfiguration] = (0, react_2.useState)();
|
|
18
|
-
const [anchor, setAnchor] = (0, react_2.useState)();
|
|
19
16
|
const [blockView, setBlockView] = (0, react_2.useState)('desktop');
|
|
20
17
|
const pageController = (0, hooks_1.usePageController)();
|
|
21
18
|
const touch = (0, client_1.useTouch)();
|
|
22
19
|
const panelLayoutPickerRef = (0, react_2.useRef)(null);
|
|
23
|
-
const { modified = false, valid = false } = configuration !== null && configuration !== void 0 ? configuration : {};
|
|
20
|
+
const { modified = false, valid = false, blocksFields = [], desktopFields = [], mobileFields = [] } = configuration !== null && configuration !== void 0 ? configuration : {};
|
|
21
|
+
const settingsFields = blockView === 'desktop' ? desktopFields : mobileFields;
|
|
24
22
|
// sync w/ configuration
|
|
25
23
|
(0, core_1.useBlockConfiguration)(configuration);
|
|
26
|
-
//
|
|
27
|
-
(0, react_2.
|
|
28
|
-
|
|
29
|
-
// reset state
|
|
30
|
-
setConfiguration(block.getConfiguration());
|
|
31
|
-
setAnchor(anchor);
|
|
24
|
+
// reset to desktop when open
|
|
25
|
+
(0, react_2.useEffect)(() => {
|
|
26
|
+
if (configuration) {
|
|
32
27
|
setBlockView('desktop');
|
|
33
|
-
// create deferral
|
|
34
|
-
const deferral = new funnel_core_1.Deferral();
|
|
35
|
-
setDeferral(deferral);
|
|
36
|
-
// wait until deferral is resolved
|
|
37
|
-
return deferral.promise;
|
|
38
28
|
}
|
|
39
|
-
}
|
|
29
|
+
}, [configuration]);
|
|
40
30
|
// helpers
|
|
41
31
|
const onSave = async () => {
|
|
42
32
|
// save config
|
|
43
33
|
configuration === null || configuration === void 0 ? void 0 : configuration.save();
|
|
44
34
|
// wait for page to save
|
|
45
35
|
await (0, rxjs_1.firstValueFrom)(pageController.saved$);
|
|
46
|
-
// complete
|
|
47
|
-
|
|
48
|
-
// refresh
|
|
49
|
-
touch();
|
|
50
|
-
};
|
|
51
|
-
const onCancel = () => {
|
|
52
|
-
// skip if missing or already completed
|
|
53
|
-
if (!deferral || deferral.isComplete) {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
// cancel
|
|
57
|
-
deferral.resolve(undefined);
|
|
58
|
-
// touch
|
|
59
|
-
touch();
|
|
36
|
+
// complete
|
|
37
|
+
onClose();
|
|
60
38
|
};
|
|
61
39
|
const onChangePanelLayout = async (e) => {
|
|
62
40
|
var _a;
|
|
@@ -65,7 +43,7 @@ exports.BlockConfigEditor = (0, react_2.forwardRef)(function BlockConfigEditor(_
|
|
|
65
43
|
if (!layoutField) {
|
|
66
44
|
return;
|
|
67
45
|
}
|
|
68
|
-
// pick new layout
|
|
46
|
+
// pick new layout
|
|
69
47
|
const { schema: layoutSchema, value: layoutData } = layoutField;
|
|
70
48
|
const selectedLayout = await ((_a = panelLayoutPickerRef.current) === null || _a === void 0 ? void 0 : _a.open({
|
|
71
49
|
anchor: e.currentTarget,
|
|
@@ -77,8 +55,9 @@ exports.BlockConfigEditor = (0, react_2.forwardRef)(function BlockConfigEditor(_
|
|
|
77
55
|
})),
|
|
78
56
|
selectedItem: layoutData === null || layoutData === void 0 ? void 0 : layoutData.value
|
|
79
57
|
}));
|
|
80
|
-
if
|
|
81
|
-
|
|
58
|
+
// update if something was picked
|
|
59
|
+
if (selectedLayout) {
|
|
60
|
+
layoutField.value = funnel_core_1.Schema.EnumField.Value.toData(selectedLayout);
|
|
82
61
|
}
|
|
83
62
|
};
|
|
84
63
|
// skip if there's no config
|
|
@@ -86,8 +65,22 @@ exports.BlockConfigEditor = (0, react_2.forwardRef)(function BlockConfigEditor(_
|
|
|
86
65
|
return null;
|
|
87
66
|
}
|
|
88
67
|
// render
|
|
89
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(core_1.
|
|
90
|
-
})
|
|
68
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(core_1.SideSheet, { title: "Block Configuration", open: !!configuration, onSave: onSave, onClose: onClose, 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 &&
|
|
69
|
+
(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 &&
|
|
70
|
+
(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) })] }));
|
|
71
|
+
}
|
|
72
|
+
function renderPanelLayoutItem(item) {
|
|
73
|
+
switch (item.id) {
|
|
74
|
+
case 'stack.h':
|
|
75
|
+
return ((0, jsx_runtime_1.jsx)("div", { children: "Stack H" }));
|
|
76
|
+
case 'stack.v':
|
|
77
|
+
return ((0, jsx_runtime_1.jsx)("div", { children: "Stack V" }));
|
|
78
|
+
case 'nested':
|
|
79
|
+
return ((0, jsx_runtime_1.jsx)("div", { children: "Nested" }));
|
|
80
|
+
default:
|
|
81
|
+
return ((0, jsx_runtime_1.jsx)("div", { children: item.id }));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
91
84
|
const blockViews = [
|
|
92
85
|
{ id: 'desktop', label: 'Desktop' },
|
|
93
86
|
{ id: 'mobile', label: 'Mobile' },
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
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
|
}
|