Skip to content

Commit f654104

Browse files
committed
Merge branch 'forms/1.0.26-folder-chooser-display-all'
2 parents efa33c4 + 0fe8dae commit f654104

14 files changed

Lines changed: 167 additions & 42 deletions

‎dwertheimer.Forms/CHANGELOG.md‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,29 @@
44

55
See Plugin [README](https://github.com/NotePlan/plugins/blob/main/dwertheimer.Forms/README.md) for details on available commands and use case.
66

7+
## [1.0.26] 2026-02-08 @dwertheimer
8+
9+
### Added
10+
- **Frontmatter Key Chooser value separator**: When not returning as array, the Frontmatter Key Chooser supports a `valueSeparator` option: `comma` (value1,value2), `commaSpace` (value1, value2 — default for readability), or `space` (value1 value2). Form Builder includes a Value Separator dropdown; type definitions, renderer, test examples, and docs updated.
11+
12+
### Fixed
13+
- **FolderChooser displays all folders**: Folder chooser (e.g. in form fields) now shows all folders instead of the default 25. Uses `maxResults: 0` so the dropdown shows the full list with scrolling.
14+
- **SpaceChooser WebView error (isMounted)**: Fixed `ReferenceError: Can't find variable: isMounted` in SpaceChooser. The `useEffect` referenced `isMounted` which was only defined inside `loadSpaces`; added `isMountedRef` and proper mount tracking (matches FolderChooser, TagChooser, MentionChooser pattern).
15+
- **SpaceChooser Private option displays blank**: Selecting "Private" (id: empty string) in the space chooser now correctly shows "Private" in the text field instead of blank. SearchableChooser previously skipped the display lookup when `displayValue` was falsy (empty string); now allows lookup when `value` is set (including `''` for empty-id items).
16+
17+
### Edited in this release
18+
- `helpers/react/DynamicDialog/FolderChooser.jsx` — Set `maxResults: 0` in SearchableChooser config so all folders are shown (unlimited, scroll).
19+
- `helpers/react/DynamicDialog/SpaceChooser.jsx` — Added `isMountedRef` and mount-tracking effect; replaced `isMounted` references with `isMountedRef.current` in `loadSpaces` and load effect.
20+
- `helpers/react/DynamicDialog/SearchableChooser.jsx` — Allow display-value lookup when `value` is `''` (not just truthy); condition changed from `displayValue && ...` to `value !== undefined && value !== null && ...`.
21+
- `helpers/react/DynamicDialog/ContainedMultiSelectChooser.jsx` — Added `valueSeparator` prop (`'comma'` | `'commaSpace'` | `'space'`) and join/parse logic for string output.
22+
- `helpers/react/DynamicDialog/FrontmatterKeyChooser.jsx` — Added `valueSeparator` prop, default `'commaSpace'`; pass-through to ContainedMultiSelectChooser.
23+
- `helpers/react/DynamicDialog/DynamicDialog.jsx` — Added `valueSeparator` to TSettingItem.
24+
- `helpers/react/DynamicDialog/dialogElementRenderer.js` — Extract and pass `valueSeparator` for frontmatter-key-chooser.
25+
- `dwertheimer.Forms/src/components/FieldEditor.jsx` — Value Separator dropdown for frontmatter-key-chooser (comma / comma+space / space).
26+
- `dwertheimer.Forms/src/FormFieldRenderTest.js` — valueSeparator examples (commaSpace, space, comma).
27+
- `helpers/react/DynamicDialog/CREATING_NEW_DYNAMICDIALOG_FIELD_TYPES.md` — Documented valueSeparator; Form Item Editor and test examples.
28+
- `dwertheimer.Forms/README.md` — frontmatter-key-chooser JSON example with valueSeparator.
29+
730
## [1.0.25] 2026-02-06 @dwertheimer
831

932
### Merged

‎dwertheimer.Forms/README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,8 @@ All fields support these properties:
681681
label: 'Status',
682682
type: 'frontmatter-key-chooser',
683683
frontmatterKey: 'status', // which frontmatter key to search
684-
returnAsArray: false
684+
returnAsArray: false,
685+
valueSeparator: 'commaSpace' // when string: 'comma' | 'commaSpace' | 'space' (default: commaSpace)
685686
}
686687
```
687688

‎dwertheimer.Forms/plugin.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
"noteplan.minAppVersion": "3.4.0",
55
"plugin.id": "dwertheimer.Forms",
66
"plugin.name": "📝 Template Forms",
7-
"plugin.version": "1.0.25",
7+
"plugin.version": "1.0.26",
88
"plugin.releaseStatus": "beta",
9-
"plugin.lastUpdateInfo": "Merge fix branch: conditional-values/button-group default, Form Browser overlay, simplified submit response; plus 1.0.24 (Processing Template dropdown, GenericDatePicker Safari, formProcessorTitle eliminated).",
9+
"plugin.lastUpdateInfo": "FolderChooser now shows all folders. Frontmatter Key Chooser supports valueSeparator option -- space, comma, or commaSpace. Thx @jgclark!",
1010
"plugin.description": "Dynamic Forms for NotePlan using Templating -- fill out a multi-field form and have the data sent to a template for processing",
1111
"plugin.author": "dwertheimer",
1212
"plugin.requiredFiles": ["react.c.FormView.bundle.dev.js", "react.c.FormBuilderView.bundle.dev.js", "react.c.FormBrowserView.bundle.dev.js"],

‎dwertheimer.Forms/src/FormFieldRenderTest.js‎

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,12 +1282,37 @@ export async function testFormFieldRender(): Promise<void> {
12821282
},
12831283
{
12841284
type: 'frontmatter-key-chooser',
1285-
label: 'Frontmatter Key Chooser (Fixed Key: "status")',
1285+
label: 'Frontmatter Key Chooser (Fixed Key: "status", comma+space)',
12861286
key: 'testFrontmatterKeyChooser',
12871287
frontmatterKey: 'status',
12881288
placeholder: 'Type to search values...',
1289-
description: 'Multi-select chooser for frontmatter key values. Returns comma-separated string like "value1,value2". Key is fixed to "status".',
1289+
description: 'Multi-select chooser for frontmatter key values. Returns string like "value1, value2" (valueSeparator: commaSpace). Key is fixed to "status".',
12901290
returnAsArray: false,
1291+
valueSeparator: 'commaSpace',
1292+
noteType: 'All',
1293+
caseSensitive: false,
1294+
},
1295+
{
1296+
type: 'frontmatter-key-chooser',
1297+
label: 'Frontmatter Key Chooser (Fixed Key: "status", space-separated)',
1298+
key: 'testFrontmatterKeyChooserSpace',
1299+
frontmatterKey: 'status',
1300+
placeholder: 'Type to search values...',
1301+
description: 'Same as above but valueSeparator: space — returns "value1 value2".',
1302+
returnAsArray: false,
1303+
valueSeparator: 'space',
1304+
noteType: 'All',
1305+
caseSensitive: false,
1306+
},
1307+
{
1308+
type: 'frontmatter-key-chooser',
1309+
label: 'Frontmatter Key Chooser (Fixed Key: "status", comma no space)',
1310+
key: 'testFrontmatterKeyChooserComma',
1311+
frontmatterKey: 'status',
1312+
placeholder: 'Type to search values...',
1313+
description: 'Same but valueSeparator: comma — returns "value1,value2" (no space after comma).',
1314+
returnAsArray: false,
1315+
valueSeparator: 'comma',
12911316
noteType: 'All',
12921317
caseSensitive: false,
12931318
},

‎dwertheimer.Forms/src/components/FieldEditor.jsx‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1549,7 +1549,23 @@ export function FieldEditor({ field, allFields, onSave, onCancel, requestFromPlu
15491549
/>
15501550
Return as Array
15511551
</label>
1552-
<div className="field-editor-help">If checked, returns selected values as an array. Otherwise, returns as comma-separated string.</div>
1552+
<div className="field-editor-help">If checked, returns selected values as an array. Otherwise, returns as string (format set by Value Separator below).</div>
1553+
</div>
1554+
<div className="field-editor-row">
1555+
<label>Value Separator (when not returning array):</label>
1556+
<select
1557+
value={((editedField: any): { valueSeparator?: string }).valueSeparator || 'commaSpace'}
1558+
onChange={(e) => {
1559+
const updated = { ...editedField }
1560+
;(updated: any).valueSeparator = e.target.value || undefined
1561+
setEditedField(updated)
1562+
}}
1563+
>
1564+
<option value="comma">Comma (no space) — value1,value2</option>
1565+
<option value="commaSpace">Comma with space — value1, value2</option>
1566+
<option value="space">Space — value1 value2</option>
1567+
</select>
1568+
<div className="field-editor-help">How to join multiple selected values when not returning as array. Comma with space is default for readability.</div>
15531569
</div>
15541570
<div className="field-editor-row">
15551571
<label>

‎helpers/react/DynamicDialog/CREATING_NEW_DYNAMICDIALOG_FIELD_TYPES.md‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export type TSettingItem = {
5252
- `includePattern?: string`
5353
- `excludePattern?: string`
5454
- `returnAsArray?: boolean`
55+
- `valueSeparator?: 'comma' | 'commaSpace' | 'space'` (for string output: comma, comma+space, or space-separated)
5556
- `defaultChecked?: boolean`
5657
- `maxHeight?: string`
5758
@@ -278,7 +279,7 @@ If your field has custom properties that need to be edited in the Form Builder,
278279
) : null}
279280
```
280281

281-
Look for similar patterns in the file for other field types to see how to add editor UI.
282+
For `frontmatter-key-chooser`, the Form Item Editor includes a **Value Separator** dropdown (when not returning as array): Comma (no space), Comma with space, or Space. Look for similar patterns in the file for other field types to see how to add editor UI.
282283

283284
### 7. Test/Examples
284285

@@ -315,6 +316,15 @@ Add examples to the `testFormFields` array. Create a new heading/section for you
315316
description: 'Example returning array format',
316317
returnAsArray: true,
317318
},
319+
{
320+
type: 'your-new-type',
321+
label: 'Your New Chooser (Comma+space string)',
322+
key: 'testYourNewChooserCommaSpace',
323+
placeholder: 'Type to search...',
324+
description: 'Example with valueSeparator: commaSpace (e.g. "a, b, c")',
325+
returnAsArray: false,
326+
valueSeparator: 'commaSpace',
327+
},
318328
// ... add more examples for each important parameter
319329
```
320330

@@ -324,8 +334,8 @@ Add examples to the `testFormFields` array. Create a new heading/section for you
324334
- Include examples for:
325335
- Basic usage
326336
- Each custom property/parameter
327-
- Different return formats (string vs array, if applicable)
328-
- Different states (default checked, with filters, etc.)
337+
- Different return formats (string vs array, if applicable) and string separator options (`valueSeparator`: comma, commaSpace, space) for choosers that support it (e.g. `frontmatter-key-chooser`)
338+
- Different states (default checked, with filters, etc.)
329339
- Use descriptive keys like `testYourNewChooser`, `testYourNewChooserCustom`, etc.
330340
- Include helpful descriptions explaining what each example demonstrates
331341

@@ -351,7 +361,7 @@ The `tag-chooser` and `mention-chooser` field types serve as complete reference
351361
2. **Dynamic Data Loading**: Both load data via `requestFromPlugin('getHashtags')` / `requestFromPlugin('getMentions')`
352362
3. **Memoization**: `getItemDisplayLabel` functions are wrapped in `useCallback`
353363
4. **Prefix Handling**: Components handle adding/removing prefixes (`#` for tags, `@` for mentions)
354-
5. **Return Formats**: Support both string (comma-separated) and array return formats via `returnAsArray` prop
364+
5. **Return Formats**: Support both string and array return formats via `returnAsArray` prop. When returning a string, `valueSeparator` controls how multiple values are joined: `'comma'` (no space), `'commaSpace'` (comma + space for readability), or `'space'` (space-separated).
355365
6. **Default State**: Support `defaultChecked` to pre-select all items
356366
7. **Filtering**: Support `includePattern` and `excludePattern` for regex-based filtering
357367

@@ -399,7 +409,7 @@ After creating your new field type, verify:
399409
- [ ] Request handler is added to `handleRequest` switch (if needed)
400410
- [ ] Field type is added to `FIELD_TYPES` array
401411
- [ ] Editor UI is added to `FieldEditor` (if custom properties)
402-
- [ ] Examples are added to `FormFieldRenderTest.js`
412+
- [ ] Examples are added to `FormFieldRenderTest.js` (including valueSeparator variants if the field returns a string of multiple values)
403413
- [ ] Component handles loading/error/empty states
404414
- [ ] Component properly memoizes functions (if needed) - note: `requestFromPlugin` is already memoized in parent
405415
- [ ] Component passes `requestFromPlugin` if needed

‎helpers/react/DynamicDialog/ContainedMultiSelectChooser.jsx‎

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export type ContainedMultiSelectChooserProps = {
1717
placeholder?: string,
1818
items: Array<string>, // Array of item strings (without prefix)
1919
getItemDisplayLabel: (item: string) => string, // Function to format item for display (e.g., add # or @ prefix)
20-
returnAsArray?: boolean, // If true, return as array, otherwise return as comma-separated string (default: false)
20+
returnAsArray?: boolean, // If true, return as array, otherwise return as string (default: false)
21+
valueSeparator?: 'comma' | 'commaSpace' | 'space', // When returnAsArray false: 'comma'=no space, 'commaSpace'=comma+space, 'space'=space-separated (default: 'comma')
2122
defaultChecked?: boolean, // If true, all items checked by default (default: false)
2223
includePattern?: string, // Regex pattern to include items
2324
excludePattern?: string, // Regex pattern to exclude items
@@ -52,6 +53,7 @@ export function ContainedMultiSelectChooser({
5253
items,
5354
getItemDisplayLabel,
5455
returnAsArray = false,
56+
valueSeparator = 'comma',
5557
defaultChecked = false,
5658
includePattern = '',
5759
excludePattern = '',
@@ -79,6 +81,13 @@ export function ContainedMultiSelectChooser({
7981
// Generate unique input id - use fieldKey if provided, otherwise fallback to fieldType with random suffix
8082
const inputId = fieldKey ? `${fieldType}-${fieldKey}-search` : `${fieldType}-search-${Math.random().toString(36).substr(2, 9)}`
8183

84+
// String separator for joining selected values when returnAsArray is false
85+
const joinSeparator = useMemo((): string => {
86+
if (valueSeparator === 'commaSpace') return ', '
87+
if (valueSeparator === 'space') return ' '
88+
return ','
89+
}, [valueSeparator])
90+
8291
// Filter items based on include/exclude patterns
8392
const filteredItems = useMemo(() => {
8493
let filtered = [...items]
@@ -120,13 +129,13 @@ export function ContainedMultiSelectChooser({
120129
defaultInitializedRef.current = true
121130
// Format and store the value we just set
122131
const formattedItems = filtered.map((item: string) => getItemDisplayLabel(item))
123-
const newValue: string | Array<string> = returnAsArray ? formattedItems : formattedItems.join(',')
132+
const newValue: string | Array<string> = returnAsArray ? formattedItems : formattedItems.join(joinSeparator)
124133
lastSyncedValueRef.current = newValue
125134
// Call onChange to notify parent component of the initial value
126135
onChange(newValue)
127136
}
128137
// eslint-disable-next-line react-hooks/exhaustive-deps
129-
}, [filteredItems.length, defaultChecked, value, returnAsArray]) // Only check filteredItems.length, not filteredItems itself. onChange/getItemDisplayLabel are stable or handled elsewhere
138+
}, [filteredItems.length, defaultChecked, value, returnAsArray, joinSeparator]) // Only check filteredItems.length, not filteredItems itself. onChange/getItemDisplayLabel are stable or handled elsewhere
130139

131140
// Parse value prop to get selected values and sync selectedValues
132141
// Only sync from value prop if it's different from what we last synced (to avoid loops)
@@ -181,8 +190,11 @@ export function ContainedMultiSelectChooser({
181190
// Remove duplicates
182191
parsed = Array.from(new Set(cleaned))
183192
} else {
184-
// If string, split by comma and extract item names
185-
const itemList = value.split(',').map((item: string) => item.trim()).filter(Boolean)
193+
// If string, split by comma or whitespace depending on valueSeparator
194+
const itemList =
195+
valueSeparator === 'space'
196+
? value.split(/\s+/).map((item: string) => item.trim()).filter(Boolean)
197+
: value.split(',').map((item: string) => item.trim()).filter(Boolean)
186198
const cleaned = itemList.map((item: string) => {
187199
let cleanedItem = item
188200
while (cleanedItem.startsWith('#') || cleanedItem.startsWith('@')) {
@@ -199,7 +211,7 @@ export function ContainedMultiSelectChooser({
199211
setSelectedValues(parsed)
200212
// Update ref to track what we synced
201213
lastSyncedValueRef.current = value
202-
}, [value, defaultChecked, returnAsArray, singleValue]) // Only sync from value prop to prevent resetting selections
214+
}, [value, defaultChecked, returnAsArray, singleValue, valueSeparator]) // Only sync from value prop to prevent resetting selections
203215

204216
// Calculate effective maxHeight: height prop > maxRows > maxHeight
205217
const effectiveMaxHeight = useMemo(() => {
@@ -319,8 +331,8 @@ export function ContainedMultiSelectChooser({
319331
setSelectedValues(newSelected)
320332
// Return format based on returnAsArray prop
321333
// If returnAsArray is true, return original item values (not formatted labels)
322-
// If returnAsArray is false, return formatted display labels joined by comma
323-
const newValue = returnAsArray ? newSelected : newSelected.map((item: string) => getItemDisplayLabel(item)).join(',')
334+
// If returnAsArray is false, return formatted display labels joined by valueSeparator
335+
const newValue = returnAsArray ? newSelected : newSelected.map((item: string) => getItemDisplayLabel(item)).join(joinSeparator)
324336
// Update ref before calling onChange to prevent re-sync
325337
lastSyncedValueRef.current = newValue
326338
onChange(newValue)
@@ -365,7 +377,7 @@ export function ContainedMultiSelectChooser({
365377
const allValues = Array.from(new Set(displayItems)).filter((item: string) => item.toLowerCase() !== 'is:checked')
366378
setSelectedValues(allValues)
367379
const formattedItems = allValues.map((item: string) => getItemDisplayLabel(item))
368-
const newValue = returnAsArray ? formattedItems : formattedItems.join(',')
380+
const newValue = returnAsArray ? formattedItems : formattedItems.join(joinSeparator)
369381
// Update ref before calling onChange to prevent re-sync
370382
lastSyncedValueRef.current = newValue
371383
onChange(newValue)
@@ -459,8 +471,8 @@ export function ContainedMultiSelectChooser({
459471

460472
// Format and update value via onChange
461473
const formattedItems = newSelected.map((item: string) => getItemDisplayLabel(item))
462-
const newValue: string | Array<string> = returnAsArray ? formattedItems : formattedItems.join(',')
463-
logDebug('ContainedMultiSelectChooser', `[CREATE MODE] Calling onChange with: ${typeof newValue === 'string' ? newValue : newValue.join(',')}`)
474+
const newValue: string | Array<string> = returnAsArray ? formattedItems : formattedItems.join(joinSeparator)
475+
logDebug('ContainedMultiSelectChooser', `[CREATE MODE] Calling onChange with: ${typeof newValue === 'string' ? newValue : newValue.join(joinSeparator)}`)
464476

465477
lastSyncedValueRef.current = newValue
466478
onChange(newValue)

‎helpers/react/DynamicDialog/DynamicDialog.jsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ export type TSettingItem = {
206206
allowCreate?: boolean, // for tag-chooser and mention-chooser, if true, show "+New" button to create new items (default: true)
207207
singleValue?: boolean, // for tag-chooser, mention-chooser, and frontmatter-key-chooser, if true, allow selecting only one value (no checkboxes, returns single value) (default: false)
208208
renderAsDropdown?: boolean, // for tag-chooser, mention-chooser, and frontmatter-key-chooser, if true and singleValue is true, render as dropdown-select instead of filterable chooser (default: false)
209+
valueSeparator?: 'comma' | 'commaSpace' | 'space', // for frontmatter-key-chooser (and choosers using ContainedMultiSelectChooser): when returnAsArray false, how to join values: 'comma'=no space, 'commaSpace'=comma+space, 'space'=space-separated
209210
// frontmatter-key-chooser options
210211
frontmatterKey?: string, // for frontmatter-key-chooser, the frontmatter key to get values for (can be fixed or from sourceKeyKey)
211212
sourceKeyKey?: string, // Value dependency: for frontmatter-key-chooser, key of another field to get the frontmatter key from dynamically

‎helpers/react/DynamicDialog/FolderChooser.jsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ export function FolderChooser({
491491
iconClass: 'fa-solid fa-folder',
492492
fieldType: 'folder-chooser',
493493
debugLogging: true,
494-
maxResults: 25,
494+
maxResults: 0, // 0 = show all folders (unlimited, scroll)
495495
inputMaxLength: 100,
496496
dropdownMaxLength: 80,
497497
// Add Option-click support for creating subfolders

0 commit comments

Comments
 (0)