@od-labs/payloadcms-dynamic-value-richtext 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +128 -96
  2. package/dist/exports/jsx.d.ts +9 -0
  3. package/dist/exports/jsx.js +73 -0
  4. package/dist/exports/jsx.js.map +1 -0
  5. package/dist/exports/react.d.ts +19 -0
  6. package/dist/exports/react.js +22 -0
  7. package/dist/exports/react.js.map +1 -0
  8. package/dist/exports/rsc.d.ts +1 -0
  9. package/dist/exports/rsc.js +3 -0
  10. package/dist/exports/rsc.js.map +1 -0
  11. package/dist/features/DynamicValue/feature.client.d.ts +4 -8
  12. package/dist/features/DynamicValue/feature.client.js +187 -89
  13. package/dist/features/DynamicValue/feature.client.js.map +1 -1
  14. package/dist/features/DynamicValue/feature.server.d.ts +16 -5
  15. package/dist/features/DynamicValue/feature.server.js +78 -39
  16. package/dist/features/DynamicValue/feature.server.js.map +1 -1
  17. package/dist/features/DynamicValue/types.d.ts +23 -23
  18. package/dist/features/DynamicValue/types.js.map +1 -1
  19. package/dist/icons/dynamicValue/bold.d.ts +2 -0
  20. package/dist/icons/dynamicValue/bold.js +6 -0
  21. package/dist/icons/dynamicValue/bold.js.map +1 -0
  22. package/dist/icons/dynamicValue/index.d.ts +3 -0
  23. package/dist/icons/dynamicValue/index.js +14 -0
  24. package/dist/icons/dynamicValue/index.js.map +1 -0
  25. package/dist/icons/dynamicValue/italic.d.ts +2 -0
  26. package/dist/icons/dynamicValue/italic.js +6 -0
  27. package/dist/icons/dynamicValue/italic.js.map +1 -0
  28. package/dist/icons/dynamicValue/link.d.ts +2 -0
  29. package/dist/icons/dynamicValue/link.js +6 -0
  30. package/dist/icons/dynamicValue/link.js.map +1 -0
  31. package/dist/icons/dynamicValue/strikethrough.d.ts +2 -0
  32. package/dist/icons/dynamicValue/strikethrough.js +6 -0
  33. package/dist/icons/dynamicValue/strikethrough.js.map +1 -0
  34. package/dist/icons/dynamicValue/types.d.ts +5 -0
  35. package/dist/icons/dynamicValue/types.js +3 -0
  36. package/dist/icons/dynamicValue/types.js.map +1 -0
  37. package/dist/icons/dynamicValue/underline.d.ts +2 -0
  38. package/dist/icons/dynamicValue/underline.js +6 -0
  39. package/dist/icons/dynamicValue/underline.js.map +1 -0
  40. package/dist/index.d.ts +6 -6
  41. package/dist/index.js +75 -3
  42. package/dist/index.js.map +1 -1
  43. package/dist/nodes/DynamicValueNode/index.d.ts +13 -13
  44. package/dist/nodes/DynamicValueNode/index.js +155 -83
  45. package/dist/nodes/DynamicValueNode/index.js.map +1 -1
  46. package/package.json +41 -1
@@ -1,13 +1,17 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { createClientFeature } from '@payloadcms/richtext-lexical/client';
4
- import { $insertNodes } from '@payloadcms/richtext-lexical/lexical';
4
+ import { $createTextNode, $insertNodes } from '@payloadcms/richtext-lexical/lexical';
5
5
  import { useLexicalComposerContext } from '@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext';
6
6
  import { LexicalTypeaheadMenuPlugin } from '@payloadcms/richtext-lexical/lexical/react/LexicalTypeaheadMenuPlugin';
7
+ import { useForm } from '@payloadcms/ui';
7
8
  import { AtSign, Hash, Variable } from 'lucide-react';
8
9
  import React, { useCallback, useMemo, useState } from 'react';
9
10
  import * as ReactDOM from 'react-dom';
10
11
  import { $createDynamicValueNode, DynamicValueNode } from '../../nodes/DynamicValueNode/index.js';
12
+ export function $isDynamicValueNode(node) {
13
+ return node?.getType?.() === 'dynamic-value';
14
+ }
11
15
  function useTriggerMatch(trigger) {
12
16
  return useCallback((text)=>{
13
17
  const triggerIndex = text.lastIndexOf(trigger);
@@ -31,98 +35,109 @@ function useTriggerMatch(trigger) {
31
35
  trigger
32
36
  ]);
33
37
  }
38
+ const SelectIcon = (trigger)=>{
39
+ const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable;
40
+ return ()=>/*#__PURE__*/ _jsx("div", {
41
+ style: {
42
+ alignItems: 'center',
43
+ display: 'flex',
44
+ justifyContent: 'center',
45
+ opacity: 0.8
46
+ },
47
+ children: /*#__PURE__*/ _jsx(IconToUse, {
48
+ className: "icon",
49
+ focusable: "false",
50
+ size: 14,
51
+ strokeWidth: 1.5,
52
+ style: {
53
+ color: 'currentColor'
54
+ }
55
+ })
56
+ });
57
+ };
34
58
  const DynamicValuePlugin = ({ anchorElem, options: allOptions, trigger })=>{
35
59
  const [editor] = useLexicalComposerContext();
60
+ const { fields } = useForm();
36
61
  const [queryString, setQueryString] = useState(null);
37
62
  const checkForTriggerMatch = useTriggerMatch(trigger);
38
63
  const options = useMemo(()=>{
39
64
  const searchString = queryString?.toLowerCase() || '';
40
- return (allOptions || []).filter((option)=>option.label.toLowerCase().includes(searchString) || option.value.toLowerCase().includes(searchString)).map((opt)=>({
65
+ return (allOptions || []).filter((option)=>{
66
+ const val = fields[option.value]?.value;
67
+ const hasValue = val !== undefined && val !== null && val !== '';
68
+ return hasValue && (option.label.toLowerCase().includes(searchString) || option.value.toLowerCase().includes(searchString));
69
+ }).map((opt)=>({
41
70
  ...opt,
42
71
  key: opt.value
43
72
  }));
44
73
  }, [
45
74
  queryString,
46
- allOptions
75
+ allOptions,
76
+ fields
47
77
  ]);
48
- const TypeaheadMenu = LexicalTypeaheadMenuPlugin;
49
78
  const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable;
50
- return /*#__PURE__*/ _jsx(TypeaheadMenu, {
51
- anchorElem: anchorElem || (typeof document !== 'undefined' ? document.body : undefined),
79
+ return /*#__PURE__*/ _jsx(LexicalTypeaheadMenuPlugin, {
80
+ ...{
81
+ anchorElem: anchorElem || (typeof document !== 'undefined' ? document.body : undefined)
82
+ },
52
83
  menuRenderFn: (anchorElementRef, { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex })=>{
53
84
  const anchor = anchorElementRef?.current || anchorElem || (typeof document !== 'undefined' ? document.body : null);
54
85
  if (!anchor || !options.length) {
55
86
  return null;
56
87
  }
57
- return /*#__PURE__*/ ReactDOM.createPortal(/*#__PURE__*/ _jsxs("div", {
58
- className: "dynamic-value-popup",
88
+ return /*#__PURE__*/ ReactDOM.createPortal(/*#__PURE__*/ _jsx("div", {
89
+ className: "slash-menu-popup",
59
90
  style: {
60
- background: 'var(--theme-elevation-100)',
61
- border: '1px solid var(--theme-elevation-250)',
62
- borderRadius: '8px',
63
- boxShadow: '0 8px 32px rgba(0,0,0,0.2)',
91
+ maxHeight: '400px',
64
92
  minWidth: '260px',
65
- overflow: 'hidden',
66
- padding: '4px',
67
- zIndex: 100000
93
+ overflowY: 'auto',
94
+ padding: '8px'
68
95
  },
69
- children: [
70
- /*#__PURE__*/ _jsx("div", {
71
- style: {
72
- borderBottom: '1px solid var(--theme-elevation-150)',
73
- color: 'var(--theme-text)',
74
- fontSize: '10px',
75
- fontWeight: 700,
76
- letterSpacing: '0.05em',
77
- marginBottom: '4px',
78
- opacity: 0.6,
79
- padding: '10px 14px 6px',
80
- textTransform: 'uppercase'
81
- },
82
- children: "Merge Fields"
83
- }),
84
- /*#__PURE__*/ _jsx("div", {
85
- style: {
86
- maxHeight: '300px',
87
- overflowY: 'auto'
88
- },
89
- children: options.map((option, index)=>{
96
+ children: /*#__PURE__*/ _jsxs("div", {
97
+ className: "slash-menu-popup__group",
98
+ children: [
99
+ /*#__PURE__*/ _jsx("div", {
100
+ className: "slash-menu-popup__group-title",
101
+ children: "Dynamic Values"
102
+ }),
103
+ options.map((option, index)=>{
90
104
  const isSelected = selectedIndex === index;
91
105
  return /*#__PURE__*/ _jsxs("button", {
106
+ "aria-selected": isSelected,
107
+ className: `slash-menu-popup__item ${isSelected ? 'slash-menu-popup__item--selected' : ''}`,
92
108
  onClick: ()=>selectOptionAndCleanUp(option),
93
109
  onMouseEnter: ()=>setHighlightedIndex(index),
110
+ role: "option",
94
111
  style: {
95
- alignItems: 'center',
96
- background: isSelected ? 'var(--theme-primary)' : 'transparent',
97
- border: 'none',
98
- borderRadius: '6px',
99
- color: isSelected ? 'white' : 'var(--theme-text)',
100
- cursor: 'pointer',
101
- display: 'flex',
102
- gap: '10px',
103
- padding: '8px 12px',
104
- textAlign: 'left',
105
- transition: 'all 0.15s ease',
106
- width: '100%'
112
+ minHeight: '50px'
107
113
  },
114
+ tabIndex: -1,
108
115
  type: "button",
109
116
  children: [
110
117
  /*#__PURE__*/ _jsx(IconToUse, {
111
- size: 14,
118
+ className: "icon",
119
+ size: 20,
120
+ strokeWidth: 1.5,
112
121
  style: {
113
- opacity: isSelected ? 1 : 0.4
122
+ flexShrink: 0
114
123
  }
115
124
  }),
116
- /*#__PURE__*/ _jsxs("div", {
125
+ /*#__PURE__*/ _jsxs("span", {
126
+ className: "slash-menu-popup__item-text",
117
127
  style: {
128
+ alignItems: 'flex-start',
118
129
  display: 'flex',
119
- flexDirection: 'column'
130
+ flex: '1',
131
+ flexDirection: 'column',
132
+ marginLeft: '8px'
120
133
  },
121
134
  children: [
122
135
  /*#__PURE__*/ _jsx("span", {
136
+ className: "text",
123
137
  style: {
124
138
  fontSize: '13px',
125
- fontWeight: 500
139
+ fontWeight: '500',
140
+ lineHeight: 1.2
126
141
  },
127
142
  children: option.label
128
143
  }),
@@ -130,6 +145,8 @@ const DynamicValuePlugin = ({ anchorElem, options: allOptions, trigger })=>{
130
145
  style: {
131
146
  fontFamily: 'var(--font-mono)',
132
147
  fontSize: '11px',
148
+ lineHeight: 1.2,
149
+ marginTop: '2px',
133
150
  opacity: 0.5
134
151
  },
135
152
  children: option.value
@@ -139,8 +156,8 @@ const DynamicValuePlugin = ({ anchorElem, options: allOptions, trigger })=>{
139
156
  ]
140
157
  }, option.value);
141
158
  })
142
- })
143
- ]
159
+ ]
160
+ })
144
161
  }), anchor);
145
162
  },
146
163
  onQueryChange: setQueryString,
@@ -151,7 +168,8 @@ const DynamicValuePlugin = ({ anchorElem, options: allOptions, trigger })=>{
151
168
  }
152
169
  const node = $createDynamicValueNode(option.value, option.label);
153
170
  $insertNodes([
154
- node
171
+ node,
172
+ $createTextNode(' ')
155
173
  ]);
156
174
  closeMenu();
157
175
  });
@@ -160,10 +178,120 @@ const DynamicValuePlugin = ({ anchorElem, options: allOptions, trigger })=>{
160
178
  triggerFn: checkForTriggerMatch
161
179
  });
162
180
  };
163
- export const DynamicValueFeatureClient = createClientFeature(({ props })=>{
181
+ const DropdownItemComponent = ({ editor, field, item, trigger })=>{
182
+ const { fields } = useForm();
183
+ const val = fields[field.value]?.value;
184
+ const hasValue = val !== undefined && val !== null && val !== '';
185
+ if (!hasValue) {
186
+ return null;
187
+ }
188
+ const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable;
189
+ return /*#__PURE__*/ _jsx("button", {
190
+ "aria-label": field.label,
191
+ className: "btn toolbar-popup__dropdown-item btn--icon btn--icon-style-none btn--size-medium btn--icon-position-left btn--style-none",
192
+ "data-item-key": field.value,
193
+ onClick: (e)=>{
194
+ e.preventDefault();
195
+ item.onSelect({
196
+ editor,
197
+ isActive: false
198
+ });
199
+ },
200
+ style: {
201
+ minHeight: '50px',
202
+ whiteSpace: 'nowrap'
203
+ },
204
+ title: field.label,
205
+ type: "button",
206
+ children: /*#__PURE__*/ _jsxs("span", {
207
+ className: "btn__content",
208
+ style: {
209
+ alignItems: 'center',
210
+ display: 'flex'
211
+ },
212
+ children: [
213
+ /*#__PURE__*/ _jsxs("span", {
214
+ className: "btn__label",
215
+ style: {
216
+ alignItems: 'flex-start',
217
+ display: 'flex',
218
+ flex: 1,
219
+ flexDirection: 'column',
220
+ marginLeft: '8px',
221
+ marginRight: '8px'
222
+ },
223
+ children: [
224
+ /*#__PURE__*/ _jsx("span", {
225
+ className: "text",
226
+ style: {
227
+ fontSize: '13px',
228
+ fontWeight: 500,
229
+ lineHeight: '1.2'
230
+ },
231
+ children: field.label
232
+ }),
233
+ /*#__PURE__*/ _jsx("span", {
234
+ style: {
235
+ fontFamily: 'var(--font-mono)',
236
+ fontSize: '11px',
237
+ lineHeight: '1.2',
238
+ marginTop: '2px',
239
+ opacity: 0.5
240
+ },
241
+ children: field.value
242
+ })
243
+ ]
244
+ }),
245
+ /*#__PURE__*/ _jsx("span", {
246
+ className: "btn__icon",
247
+ style: {
248
+ flexShrink: 0
249
+ },
250
+ children: /*#__PURE__*/ _jsx(IconToUse, {
251
+ size: 20,
252
+ strokeWidth: 1.5,
253
+ style: {
254
+ opacity: 0.4
255
+ }
256
+ })
257
+ })
258
+ ]
259
+ })
260
+ });
261
+ };
262
+ export const DynamicValueFeatureClient = createClientFeature((args)=>{
263
+ const props = args?.clientFeatureProps || args?.props || {};
164
264
  const options = props?.options || [];
165
265
  const trigger = props?.trigger || '@';
166
- const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable;
266
+ const groupItems = options.map((field)=>({
267
+ Component: ({ editor, item })=>/*#__PURE__*/ _jsx(DropdownItemComponent, {
268
+ editor: editor,
269
+ field: field,
270
+ item: item,
271
+ trigger: trigger
272
+ }),
273
+ isActive: ()=>false,
274
+ isEnabled: ()=>true,
275
+ key: `dv-item-${field.value}`,
276
+ label: field.label,
277
+ onSelect: ({ editor })=>{
278
+ editor.update(()=>{
279
+ const node = $createDynamicValueNode(field.value, field.label);
280
+ $insertNodes([
281
+ node,
282
+ $createTextNode(' ')
283
+ ]);
284
+ });
285
+ },
286
+ order: 1
287
+ }));
288
+ const toolbarGroup = {
289
+ type: 'dropdown',
290
+ ChildComponent: SelectIcon(trigger),
291
+ items: groupItems,
292
+ key: 'dynamic-value-toolbar-group',
293
+ order: 100
294
+ };
167
295
  return {
168
296
  nodes: [
169
297
  DynamicValueNode
@@ -181,37 +309,7 @@ export const DynamicValueFeatureClient = createClientFeature(({ props })=>{
181
309
  sanitizedClientFeatureProps: props,
182
310
  toolbarFixed: {
183
311
  groups: [
184
- {
185
- type: 'dropdown',
186
- ChildComponent: ()=>/*#__PURE__*/ _jsx("div", {
187
- style: {
188
- alignItems: 'center',
189
- display: 'flex',
190
- gap: '4px'
191
- },
192
- children: /*#__PURE__*/ _jsx(IconToUse, {
193
- size: 14,
194
- style: {
195
- color: 'var(--theme-text)',
196
- opacity: 0.7
197
- }
198
- })
199
- }),
200
- items: options.map((field)=>({
201
- key: field.value,
202
- label: field.label,
203
- onSelect: ({ editor })=>{
204
- editor.update(()=>{
205
- const node = $createDynamicValueNode(field.value, field.label);
206
- $insertNodes([
207
- node
208
- ]);
209
- });
210
- }
211
- })),
212
- key: 'dynamicValue',
213
- order: 99
214
- }
312
+ toolbarGroup
215
313
  ]
216
314
  }
217
315
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/features/DynamicValue/feature.client.tsx"],"sourcesContent":["'use client'\r\nimport { createClientFeature } from '@payloadcms/richtext-lexical/client'\r\nimport { $insertNodes } from '@payloadcms/richtext-lexical/lexical'\r\nimport { useLexicalComposerContext } from '@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext'\r\nimport { LexicalTypeaheadMenuPlugin } from '@payloadcms/richtext-lexical/lexical/react/LexicalTypeaheadMenuPlugin'\r\nimport { AtSign, Hash, Variable } from 'lucide-react'\r\nimport React, { useCallback, useMemo, useState } from 'react'\r\nimport * as ReactDOM from 'react-dom'\r\n\r\nimport type { DynamicValueOption } from './types.js'\r\n\r\nimport { $createDynamicValueNode, DynamicValueNode } from '../../nodes/DynamicValueNode/index.js'\r\n\r\nfunction useTriggerMatch(trigger: string) {\r\n return useCallback(\r\n (text: string) => {\r\n const triggerIndex = text.lastIndexOf(trigger)\r\n if (triggerIndex === -1) {\r\n return null\r\n }\r\n\r\n const charBeforeTrigger = triggerIndex > 0 ? text[triggerIndex - 1] : null\r\n if (charBeforeTrigger && !/[\\s(]/.test(charBeforeTrigger)) {\r\n return null\r\n }\r\n\r\n const matchingString = text.slice(triggerIndex + 1)\r\n if (/\\s/.test(matchingString)) {\r\n return null\r\n }\r\n\r\n return {\r\n leadOffset: triggerIndex,\r\n matchingString,\r\n replaceableString: trigger + matchingString,\r\n }\r\n },\r\n [trigger],\r\n )\r\n}\r\n\r\nconst DynamicValuePlugin: React.FC<{\r\n anchorElem?: HTMLElement\r\n options: DynamicValueOption[]\r\n trigger: string\r\n}> = ({ anchorElem, options: allOptions, trigger }) => {\r\n const [editor] = useLexicalComposerContext()\r\n const [queryString, setQueryString] = useState<null | string>(null)\r\n\r\n const checkForTriggerMatch = useTriggerMatch(trigger)\r\n\r\n const options = useMemo(() => {\r\n const searchString = queryString?.toLowerCase() || ''\r\n return (allOptions || [])\r\n .filter(\r\n (option: DynamicValueOption) =>\r\n option.label.toLowerCase().includes(searchString) ||\r\n option.value.toLowerCase().includes(searchString),\r\n )\r\n .map((opt: DynamicValueOption) => ({\r\n ...opt,\r\n key: opt.value,\r\n }))\r\n }, [queryString, allOptions])\r\n\r\n const TypeaheadMenu = LexicalTypeaheadMenuPlugin as any\r\n\r\n const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable\r\n\r\n return (\r\n <TypeaheadMenu\r\n anchorElem={anchorElem || (typeof document !== 'undefined' ? document.body : undefined)}\r\n menuRenderFn={(\r\n anchorElementRef: any,\r\n { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }: any,\r\n ) => {\r\n const anchor =\r\n anchorElementRef?.current ||\r\n anchorElem ||\r\n (typeof document !== 'undefined' ? document.body : null)\r\n if (!anchor || !options.length) {\r\n return null\r\n }\r\n\r\n return ReactDOM.createPortal(\r\n <div\r\n className=\"dynamic-value-popup\"\r\n style={{\r\n background: 'var(--theme-elevation-100)',\r\n border: '1px solid var(--theme-elevation-250)',\r\n borderRadius: '8px',\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.2)',\r\n minWidth: '260px',\r\n overflow: 'hidden',\r\n padding: '4px',\r\n zIndex: 100000,\r\n }}\r\n >\r\n <div\r\n style={{\r\n borderBottom: '1px solid var(--theme-elevation-150)',\r\n color: 'var(--theme-text)',\r\n fontSize: '10px',\r\n fontWeight: 700,\r\n letterSpacing: '0.05em',\r\n marginBottom: '4px',\r\n opacity: 0.6,\r\n padding: '10px 14px 6px',\r\n textTransform: 'uppercase',\r\n }}\r\n >\r\n Merge Fields\r\n </div>\r\n <div style={{ maxHeight: '300px', overflowY: 'auto' }}>\r\n {options.map((option, index) => {\r\n const isSelected = selectedIndex === index\r\n return (\r\n <button\r\n key={option.value}\r\n onClick={() => selectOptionAndCleanUp(option)}\r\n onMouseEnter={() => setHighlightedIndex(index)}\r\n style={{\r\n alignItems: 'center',\r\n background: isSelected ? 'var(--theme-primary)' : 'transparent',\r\n border: 'none',\r\n borderRadius: '6px',\r\n color: isSelected ? 'white' : 'var(--theme-text)',\r\n cursor: 'pointer',\r\n display: 'flex',\r\n gap: '10px',\r\n padding: '8px 12px',\r\n textAlign: 'left',\r\n transition: 'all 0.15s ease',\r\n width: '100%',\r\n }}\r\n type=\"button\"\r\n >\r\n <IconToUse size={14} style={{ opacity: isSelected ? 1 : 0.4 }} />\r\n <div style={{ display: 'flex', flexDirection: 'column' }}>\r\n <span style={{ fontSize: '13px', fontWeight: 500 }}>{option.label}</span>\r\n <span\r\n style={{ fontFamily: 'var(--font-mono)', fontSize: '11px', opacity: 0.5 }}\r\n >\r\n {option.value}\r\n </span>\r\n </div>\r\n </button>\r\n )\r\n })}\r\n </div>\r\n </div>,\r\n anchor,\r\n )\r\n }}\r\n onQueryChange={setQueryString}\r\n onSelectOption={(option: any, textNodeToReplace: any, closeMenu: any) => {\r\n editor.update(() => {\r\n if (textNodeToReplace) {\r\n textNodeToReplace.remove()\r\n }\r\n const node = $createDynamicValueNode(option.value, option.label)\r\n $insertNodes([node])\r\n closeMenu()\r\n })\r\n }}\r\n options={options}\r\n triggerFn={checkForTriggerMatch}\r\n />\r\n )\r\n}\r\n\r\nexport const DynamicValueFeatureClient = createClientFeature<\r\n { options: DynamicValueOption[]; trigger: string },\r\n { options: DynamicValueOption[]; trigger: string }\r\n>(({ props }) => {\r\n const options = props?.options || []\r\n const trigger = props?.trigger || '@'\r\n const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable\r\n\r\n return {\r\n nodes: [DynamicValueNode],\r\n plugins: [\r\n {\r\n Component: (pluginProps) => (\r\n <DynamicValuePlugin {...pluginProps} options={options} trigger={trigger} />\r\n ),\r\n position: 'floatingAnchorElem',\r\n },\r\n ],\r\n sanitizedClientFeatureProps: props,\r\n toolbarFixed: {\r\n groups: [\r\n {\r\n type: 'dropdown',\r\n ChildComponent: () => (\r\n <div style={{ alignItems: 'center', display: 'flex', gap: '4px' }}>\r\n <IconToUse size={14} style={{ color: 'var(--theme-text)', opacity: 0.7 }} />\r\n </div>\r\n ),\r\n items: options.map((field: DynamicValueOption) => ({\r\n key: field.value,\r\n label: field.label,\r\n onSelect: ({ editor }: { editor: any }) => {\r\n editor.update(() => {\r\n const node = $createDynamicValueNode(field.value, field.label)\r\n $insertNodes([node])\r\n })\r\n },\r\n })),\r\n key: 'dynamicValue',\r\n order: 99,\r\n },\r\n ],\r\n },\r\n }\r\n})\r\n"],"names":["createClientFeature","$insertNodes","useLexicalComposerContext","LexicalTypeaheadMenuPlugin","AtSign","Hash","Variable","React","useCallback","useMemo","useState","ReactDOM","$createDynamicValueNode","DynamicValueNode","useTriggerMatch","trigger","text","triggerIndex","lastIndexOf","charBeforeTrigger","test","matchingString","slice","leadOffset","replaceableString","DynamicValuePlugin","anchorElem","options","allOptions","editor","queryString","setQueryString","checkForTriggerMatch","searchString","toLowerCase","filter","option","label","includes","value","map","opt","key","TypeaheadMenu","IconToUse","document","body","undefined","menuRenderFn","anchorElementRef","selectedIndex","selectOptionAndCleanUp","setHighlightedIndex","anchor","current","length","createPortal","div","className","style","background","border","borderRadius","boxShadow","minWidth","overflow","padding","zIndex","borderBottom","color","fontSize","fontWeight","letterSpacing","marginBottom","opacity","textTransform","maxHeight","overflowY","index","isSelected","button","onClick","onMouseEnter","alignItems","cursor","display","gap","textAlign","transition","width","type","size","flexDirection","span","fontFamily","onQueryChange","onSelectOption","textNodeToReplace","closeMenu","update","remove","node","triggerFn","DynamicValueFeatureClient","props","nodes","plugins","Component","pluginProps","position","sanitizedClientFeatureProps","toolbarFixed","groups","ChildComponent","items","field","onSelect","order"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/features/DynamicValue/feature.client.tsx"],"sourcesContent":["'use client'\r\n\r\nimport { createClientFeature } from '@payloadcms/richtext-lexical/client'\r\nimport {\r\n $createTextNode,\r\n $insertNodes,\r\n type LexicalNode,\r\n} from '@payloadcms/richtext-lexical/lexical'\r\nimport { useLexicalComposerContext } from '@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext'\r\nimport { LexicalTypeaheadMenuPlugin } from '@payloadcms/richtext-lexical/lexical/react/LexicalTypeaheadMenuPlugin'\r\nimport { useForm } from '@payloadcms/ui'\r\nimport { AtSign, Hash, Variable } from 'lucide-react'\r\nimport React, { useCallback, useMemo, useState } from 'react'\r\nimport * as ReactDOM from 'react-dom'\r\n\r\nimport { $createDynamicValueNode, DynamicValueNode } from '../../nodes/DynamicValueNode/index.js'\r\n\r\nexport function $isDynamicValueNode(\r\n node: LexicalNode | null | undefined,\r\n): node is DynamicValueNode {\r\n return node?.getType?.() === 'dynamic-value'\r\n}\r\n\r\nfunction useTriggerMatch(trigger: string) {\r\n return useCallback(\r\n (text: string) => {\r\n const triggerIndex = text.lastIndexOf(trigger)\r\n if (triggerIndex === -1) {\r\n return null\r\n }\r\n\r\n const charBeforeTrigger = triggerIndex > 0 ? text[triggerIndex - 1] : null\r\n if (charBeforeTrigger && !/[\\s(]/.test(charBeforeTrigger)) {\r\n return null\r\n }\r\n\r\n const matchingString = text.slice(triggerIndex + 1)\r\n if (/\\s/.test(matchingString)) {\r\n return null\r\n }\r\n\r\n return {\r\n leadOffset: triggerIndex,\r\n matchingString,\r\n replaceableString: trigger + matchingString,\r\n }\r\n },\r\n [trigger],\r\n )\r\n}\r\n\r\nconst SelectIcon = (trigger: string) => {\r\n const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable\r\n return () => (\r\n <div style={{ alignItems: 'center', display: 'flex', justifyContent: 'center', opacity: 0.8 }}>\r\n <IconToUse\r\n className=\"icon\"\r\n focusable=\"false\"\r\n size={14}\r\n strokeWidth={1.5}\r\n style={{ color: 'currentColor' }}\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nconst DynamicValuePlugin = ({\r\n anchorElem,\r\n options: allOptions,\r\n trigger,\r\n}: {\r\n anchorElem?: HTMLElement\r\n options: any[]\r\n trigger: string\r\n}) => {\r\n const [editor] = useLexicalComposerContext()\r\n const { fields } = useForm()\r\n const [queryString, setQueryString] = useState<null | string>(null)\r\n const checkForTriggerMatch = useTriggerMatch(trigger)\r\n\r\n const options = useMemo(() => {\r\n const searchString = queryString?.toLowerCase() || ''\r\n return (allOptions || [])\r\n .filter((option) => {\r\n const val = fields[option.value]?.value\r\n const hasValue = val !== undefined && val !== null && val !== ''\r\n return (\r\n hasValue &&\r\n (option.label.toLowerCase().includes(searchString) ||\r\n option.value.toLowerCase().includes(searchString))\r\n )\r\n })\r\n .map((opt) => ({\r\n ...opt,\r\n key: opt.value,\r\n }))\r\n }, [queryString, allOptions, fields])\r\n\r\n const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable\r\n\r\n return (\r\n <LexicalTypeaheadMenuPlugin\r\n {...({\r\n anchorElem: anchorElem || (typeof document !== 'undefined' ? document.body : undefined),\r\n } as any)}\r\n menuRenderFn={(\r\n anchorElementRef,\r\n { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex },\r\n ) => {\r\n const anchor =\r\n anchorElementRef?.current ||\r\n anchorElem ||\r\n (typeof document !== 'undefined' ? document.body : null)\r\n if (!anchor || !options.length) {\r\n return null\r\n }\r\n\r\n return ReactDOM.createPortal(\r\n <div\r\n className=\"slash-menu-popup\"\r\n style={{\r\n maxHeight: '400px',\r\n minWidth: '260px',\r\n overflowY: 'auto',\r\n padding: '8px',\r\n }}\r\n >\r\n <div className=\"slash-menu-popup__group\">\r\n <div className=\"slash-menu-popup__group-title\">Dynamic Values</div>\r\n {options.map((option, index) => {\r\n const isSelected = selectedIndex === index\r\n return (\r\n <button\r\n aria-selected={isSelected}\r\n className={`slash-menu-popup__item ${isSelected ? 'slash-menu-popup__item--selected' : ''}`}\r\n key={option.value}\r\n onClick={() => selectOptionAndCleanUp(option)}\r\n onMouseEnter={() => setHighlightedIndex(index)}\r\n role=\"option\"\r\n style={{ minHeight: '50px' }}\r\n tabIndex={-1}\r\n type=\"button\"\r\n >\r\n <IconToUse\r\n className=\"icon\"\r\n size={20}\r\n strokeWidth={1.5}\r\n style={{ flexShrink: 0 }}\r\n />\r\n <span\r\n className=\"slash-menu-popup__item-text\"\r\n style={{\r\n alignItems: 'flex-start',\r\n display: 'flex',\r\n flex: '1',\r\n flexDirection: 'column',\r\n marginLeft: '8px',\r\n }}\r\n >\r\n <span\r\n className=\"text\"\r\n style={{\r\n fontSize: '13px',\r\n fontWeight: '500',\r\n lineHeight: 1.2,\r\n }}\r\n >\r\n {option.label}\r\n </span>\r\n <span\r\n style={{\r\n fontFamily: 'var(--font-mono)',\r\n fontSize: '11px',\r\n lineHeight: 1.2,\r\n marginTop: '2px',\r\n opacity: 0.5,\r\n }}\r\n >\r\n {option.value}\r\n </span>\r\n </span>\r\n </button>\r\n )\r\n })}\r\n </div>\r\n </div>,\r\n anchor,\r\n )\r\n }}\r\n onQueryChange={setQueryString}\r\n onSelectOption={(option: any, textNodeToReplace, closeMenu) => {\r\n editor.update(() => {\r\n if (textNodeToReplace) {\r\n textNodeToReplace.remove()\r\n }\r\n const node = $createDynamicValueNode(option.value, option.label)\r\n $insertNodes([node, $createTextNode(' ')])\r\n closeMenu()\r\n })\r\n }}\r\n options={options as any}\r\n triggerFn={checkForTriggerMatch as any}\r\n />\r\n )\r\n}\r\n\r\nconst DropdownItemComponent = ({ editor, field, item, trigger }: any) => {\r\n const { fields } = useForm()\r\n const val = fields[field.value]?.value\r\n const hasValue = val !== undefined && val !== null && val !== ''\r\n\r\n if (!hasValue) {\r\n return null\r\n }\r\n\r\n const IconToUse = trigger === '@' ? AtSign : trigger === '#' ? Hash : Variable\r\n\r\n return (\r\n <button\r\n aria-label={field.label}\r\n className=\"btn toolbar-popup__dropdown-item btn--icon btn--icon-style-none btn--size-medium btn--icon-position-left btn--style-none\"\r\n data-item-key={field.value}\r\n onClick={(e) => {\r\n e.preventDefault()\r\n item.onSelect({ editor, isActive: false })\r\n }}\r\n style={{ minHeight: '50px', whiteSpace: 'nowrap' }}\r\n title={field.label}\r\n type=\"button\"\r\n >\r\n <span className=\"btn__content\" style={{ alignItems: 'center', display: 'flex' }}>\r\n <span\r\n className=\"btn__label\"\r\n style={{\r\n alignItems: 'flex-start',\r\n display: 'flex',\r\n flex: 1,\r\n flexDirection: 'column',\r\n marginLeft: '8px',\r\n marginRight: '8px',\r\n }}\r\n >\r\n <span className=\"text\" style={{ fontSize: '13px', fontWeight: 500, lineHeight: '1.2' }}>\r\n {field.label}\r\n </span>\r\n <span\r\n style={{\r\n fontFamily: 'var(--font-mono)',\r\n fontSize: '11px',\r\n lineHeight: '1.2',\r\n marginTop: '2px',\r\n opacity: 0.5,\r\n }}\r\n >\r\n {field.value}\r\n </span>\r\n </span>\r\n <span className=\"btn__icon\" style={{ flexShrink: 0 }}>\r\n <IconToUse size={20} strokeWidth={1.5} style={{ opacity: 0.4 }} />\r\n </span>\r\n </span>\r\n </button>\r\n )\r\n}\r\n\r\nexport const DynamicValueFeatureClient = createClientFeature((args: any) => {\r\n const props = args?.clientFeatureProps || args?.props || {}\r\n const options = props?.options || []\r\n const trigger = props?.trigger || '@'\r\n\r\n const groupItems = options.map((field: any) => ({\r\n Component: ({ editor, item }: any) => (\r\n <DropdownItemComponent editor={editor} field={field} item={item} trigger={trigger} />\r\n ),\r\n isActive: () => false,\r\n isEnabled: () => true,\r\n key: `dv-item-${field.value}`,\r\n label: field.label,\r\n onSelect: ({ editor }: any) => {\r\n editor.update(() => {\r\n const node = $createDynamicValueNode(field.value, field.label)\r\n $insertNodes([node, $createTextNode(' ')])\r\n })\r\n },\r\n order: 1,\r\n }))\r\n\r\n const toolbarGroup = {\r\n type: 'dropdown' as const,\r\n ChildComponent: SelectIcon(trigger),\r\n items: groupItems,\r\n key: 'dynamic-value-toolbar-group', // Unique key to avoid Link feature collision\r\n order: 100,\r\n }\r\n\r\n return {\r\n nodes: [DynamicValueNode],\r\n plugins: [\r\n {\r\n Component: (pluginProps) => (\r\n <DynamicValuePlugin {...pluginProps} options={options} trigger={trigger} />\r\n ),\r\n position: 'floatingAnchorElem',\r\n },\r\n ],\r\n sanitizedClientFeatureProps: props,\r\n toolbarFixed: {\r\n groups: [toolbarGroup],\r\n },\r\n }\r\n})\r\n"],"names":["createClientFeature","$createTextNode","$insertNodes","useLexicalComposerContext","LexicalTypeaheadMenuPlugin","useForm","AtSign","Hash","Variable","React","useCallback","useMemo","useState","ReactDOM","$createDynamicValueNode","DynamicValueNode","$isDynamicValueNode","node","getType","useTriggerMatch","trigger","text","triggerIndex","lastIndexOf","charBeforeTrigger","test","matchingString","slice","leadOffset","replaceableString","SelectIcon","IconToUse","div","style","alignItems","display","justifyContent","opacity","className","focusable","size","strokeWidth","color","DynamicValuePlugin","anchorElem","options","allOptions","editor","fields","queryString","setQueryString","checkForTriggerMatch","searchString","toLowerCase","filter","option","val","value","hasValue","undefined","label","includes","map","opt","key","document","body","menuRenderFn","anchorElementRef","selectedIndex","selectOptionAndCleanUp","setHighlightedIndex","anchor","current","length","createPortal","maxHeight","minWidth","overflowY","padding","index","isSelected","button","aria-selected","onClick","onMouseEnter","role","minHeight","tabIndex","type","flexShrink","span","flex","flexDirection","marginLeft","fontSize","fontWeight","lineHeight","fontFamily","marginTop","onQueryChange","onSelectOption","textNodeToReplace","closeMenu","update","remove","triggerFn","DropdownItemComponent","field","item","aria-label","data-item-key","e","preventDefault","onSelect","isActive","whiteSpace","title","marginRight","DynamicValueFeatureClient","args","props","clientFeatureProps","groupItems","Component","isEnabled","order","toolbarGroup","ChildComponent","items","nodes","plugins","pluginProps","position","sanitizedClientFeatureProps","toolbarFixed","groups"],"mappings":"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"}
@@ -1,5 +1,16 @@
1
- import type { DynamicValueConfig, DynamicValueOption } from './types.js';
2
- export declare const DynamicValueFeature: import("@payloadcms/richtext-lexical").FeatureProviderProviderServer<DynamicValueConfig, object, {
3
- options: DynamicValueOption[];
4
- trigger: string;
5
- }>;
1
+ import type { Field } from 'payload';
2
+ export type DynamicValueFeatureProps = {
3
+ collections?: string[];
4
+ fields?: Field[];
5
+ globals?: string[];
6
+ trigger?: string;
7
+ };
8
+ type DynamicValueOption = {
9
+ label: string;
10
+ value: string;
11
+ };
12
+ export declare const DynamicValueFeature: import("@payloadcms/richtext-lexical").FeatureProviderProviderServer<DynamicValueFeatureProps, DynamicValueFeatureProps, {
13
+ options: DynamicValueOption[];
14
+ trigger: string;
15
+ }>;
16
+ export {};
@@ -1,62 +1,101 @@
1
1
  import { createNode, createServerFeature } from '@payloadcms/richtext-lexical';
2
- import { DynamicValueNode } from '../../nodes/DynamicValueNode/index.js';
3
- export const DynamicValueFeature = createServerFeature({
4
- feature: ({ config: payloadConfig, props })=>{
5
- const options = [];
6
- const trigger = props?.trigger || '@';
7
- const flattenFields = (fields, prefix = '')=>{
8
- fields.forEach((field)=>{
9
- if ('name' in field && field.name && field.type !== 'array' && field.type !== 'blocks') {
10
- const name = prefix ? `${prefix}.${field.name}` : field.name;
11
- options.push({
12
- label: field.label || field.name,
13
- value: name
14
- });
15
- }
16
- if ('fields' in field && field.fields && Array.isArray(field.fields)) {
17
- flattenFields(field.fields, 'name' in field && field.name ? prefix ? `${prefix}.${field.name}` : field.name : prefix);
18
- }
19
- if (field.type === 'tabs' && field.tabs) {
20
- field.tabs.forEach((tab)=>{
21
- flattenFields(tab.fields, prefix);
22
- });
23
- }
2
+ import { DYNAMIC_VALUE_NODE_TYPE, DynamicValueNode, LEGACY_DYNAMIC_VALUE_NODE_TYPE } from '../../nodes/DynamicValueNode/index.js';
3
+ const blockedFieldTypes = new Set([
4
+ 'array',
5
+ 'blocks',
6
+ 'richText',
7
+ 'ui',
8
+ 'upload'
9
+ ]);
10
+ const flattenFields = (fields, options, prefix = '')=>{
11
+ for (const field of fields){
12
+ const fieldName = 'name' in field ? field.name : undefined;
13
+ const hasFieldName = typeof fieldName === 'string' && fieldName.length > 0;
14
+ const nextPrefix = hasFieldName ? prefix ? `${prefix}.${fieldName}` : fieldName : prefix;
15
+ if (hasFieldName && typeof field.type === 'string' && !blockedFieldTypes.has(field.type) && nextPrefix) {
16
+ const fieldLabel = 'label' in field && typeof field.label === 'string' ? field.label : fieldName;
17
+ options.push({
18
+ label: fieldLabel,
19
+ value: nextPrefix
24
20
  });
21
+ }
22
+ if ('fields' in field && Array.isArray(field.fields)) {
23
+ flattenFields(field.fields, options, nextPrefix);
24
+ }
25
+ if (field.type === 'tabs' && Array.isArray(field.tabs)) {
26
+ for (const tab of field.tabs){
27
+ if (tab.fields) {
28
+ flattenFields(tab.fields, options, prefix);
29
+ }
30
+ }
31
+ }
32
+ }
33
+ };
34
+ const mapToEnabledSlugs = (value)=>{
35
+ if (Array.isArray(value)) {
36
+ return value;
37
+ }
38
+ if (!value || typeof value !== 'object') {
39
+ return [];
40
+ }
41
+ return Object.entries(value).filter(([, enabled])=>Boolean(enabled)).map(([slug])=>slug);
42
+ };
43
+ export const DynamicValueFeature = createServerFeature({
44
+ feature: ({ config: payloadConfig, props: featureProps })=>{
45
+ const pluginOptions = payloadConfig.custom?.dynamicValue || {};
46
+ const props = {
47
+ ...pluginOptions,
48
+ ...featureProps || {}
25
49
  };
26
- // 1. Process directly provided fields
27
- if (props?.fields) {
28
- flattenFields(props.fields);
50
+ const options = [];
51
+ const trigger = props.trigger || '@';
52
+ if (props.fields) {
53
+ flattenFields(props.fields, options);
29
54
  }
30
- // 2. Process collections
31
- if (props?.collections && payloadConfig.collections) {
32
- props.collections.forEach((slug)=>{
33
- const collection = payloadConfig.collections?.find((c)=>c.slug === slug);
55
+ if (props.collections && payloadConfig.collections) {
56
+ const collectionSlugs = mapToEnabledSlugs(props.collections);
57
+ for (const slug of collectionSlugs){
58
+ const collection = payloadConfig.collections.find((item)=>item.slug === slug);
34
59
  if (collection) {
35
- flattenFields(collection.fields, slug);
60
+ flattenFields(collection.fields, options, slug);
36
61
  }
37
- });
62
+ }
38
63
  }
39
- // 3. Process globals
40
- if (props?.globals && payloadConfig.globals) {
41
- props.globals.forEach((slug)=>{
42
- const global = payloadConfig.globals?.find((g)=>g.slug === slug);
64
+ if (props.globals && payloadConfig.globals) {
65
+ const globalSlugs = mapToEnabledSlugs(props.globals);
66
+ for (const slug of globalSlugs){
67
+ const global = payloadConfig.globals.find((item)=>item.slug === slug);
43
68
  if (global) {
44
- flattenFields(global.fields, slug);
69
+ flattenFields(global.fields, options, slug);
45
70
  }
46
- });
71
+ }
47
72
  }
48
73
  return {
49
74
  ClientFeature: '@od-labs/payloadcms-dynamic-value-richtext/client#DynamicValueFeatureClient',
50
- clientProps: {
75
+ clientFeatureProps: {
51
76
  options,
52
77
  trigger
53
78
  },
54
79
  key: 'dynamicValue',
55
80
  nodes: [
56
81
  createNode({
82
+ converters: {
83
+ html: {
84
+ converter: ({ node })=>{
85
+ const field = node.field || node.value || '';
86
+ const label = node.label || field;
87
+ return `<span data-payload-dynamic-value="true" data-payload-dynamic-field="${field}">${label}</span>`;
88
+ },
89
+ nodeTypes: [
90
+ DYNAMIC_VALUE_NODE_TYPE,
91
+ LEGACY_DYNAMIC_VALUE_NODE_TYPE
92
+ ]
93
+ }
94
+ },
57
95
  node: DynamicValueNode
58
96
  })
59
- ]
97
+ ],
98
+ sanitizedServerFeatureProps: props
60
99
  };
61
100
  },
62
101
  key: 'dynamicValue'