Skip to content

Commit 905a128

Browse files
committed
Add jgclark output options to tag/mention chooser also
1 parent f654104 commit 905a128

10 files changed

Lines changed: 80 additions & 31 deletions

File tree

dwertheimer.Forms/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
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.27] 2026-02-08 @dwertheimer
8+
9+
### Added
10+
- tagChooser and mentionChooser now support 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+
712
## [1.0.26] 2026-02-08 @dwertheimer
813

914
### Added

dwertheimer.Forms/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,8 @@ All fields support these properties:
660660
key: 'tags',
661661
label: 'Tags',
662662
type: 'tag-chooser',
663-
returnAsArray: false // false=comma-separated, true=array
663+
returnAsArray: false,
664+
valueSeparator: 'commaSpace' // when string: 'comma' | 'commaSpace' | 'space' (default: commaSpace)
664665
}
665666
```
666667

@@ -670,7 +671,8 @@ All fields support these properties:
670671
key: 'people',
671672
label: 'People',
672673
type: 'mention-chooser',
673-
returnAsArray: false
674+
returnAsArray: false,
675+
valueSeparator: 'commaSpace' // when string: 'comma' | 'commaSpace' | 'space' (default: commaSpace)
674676
}
675677
```
676678

dwertheimer.Forms/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"noteplan.minAppVersion": "3.4.0",
55
"plugin.id": "dwertheimer.Forms",
66
"plugin.name": "📝 Template Forms",
7-
"plugin.version": "1.0.26",
7+
"plugin.version": "1.0.27",
88
"plugin.releaseStatus": "beta",
99
"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",

dwertheimer.Forms/src/FormFieldRenderTest.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,11 +1261,21 @@ export async function testFormFieldRender(): Promise<void> {
12611261
},
12621262
{
12631263
type: 'tag-chooser',
1264-
label: 'Tag Chooser (default: string format)',
1264+
label: 'Tag Chooser (comma+space string)',
12651265
key: 'testTagChooser',
12661266
placeholder: 'Type to search hashtags...',
1267-
description: 'Multi-select hashtag chooser. Returns comma-separated string like "#tag1,#tag2"',
1267+
description: 'Multi-select hashtag chooser. Returns string like "#tag1, #tag2" (valueSeparator: commaSpace).',
12681268
returnAsArray: false,
1269+
valueSeparator: 'commaSpace',
1270+
},
1271+
{
1272+
type: 'tag-chooser',
1273+
label: 'Tag Chooser (space-separated)',
1274+
key: 'testTagChooserSpace',
1275+
placeholder: 'Type to search hashtags...',
1276+
description: 'Same with valueSeparator: space — returns "#tag1 #tag2".',
1277+
returnAsArray: false,
1278+
valueSeparator: 'space',
12691279
},
12701280
{
12711281
type: 'mention-chooser',
@@ -1275,6 +1285,15 @@ export async function testFormFieldRender(): Promise<void> {
12751285
description: 'Multi-select mention chooser. Returns array like ["@mention1", "@mention2"]',
12761286
returnAsArray: true,
12771287
},
1288+
{
1289+
type: 'mention-chooser',
1290+
label: 'Mention Chooser (comma+space string)',
1291+
key: 'testMentionChooserCommaSpace',
1292+
placeholder: 'Type to search mentions...',
1293+
description: 'Returns string like "@user1, @user2" (valueSeparator: commaSpace).',
1294+
returnAsArray: false,
1295+
valueSeparator: 'commaSpace',
1296+
},
12781297
{
12791298
type: 'heading',
12801299
label: 'Frontmatter Key Chooser',

dwertheimer.Forms/src/components/FieldEditor.jsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1790,7 +1790,23 @@ export function FieldEditor({ field, allFields, onSave, onCancel, requestFromPlu
17901790
/>
17911791
Return as Array
17921792
</label>
1793-
<div className="field-editor-help">If checked, returns selected values as an array. Otherwise, returns as comma-separated string.</div>
1793+
<div className="field-editor-help">If checked, returns selected values as an array. Otherwise, returns as string (format set by Value Separator below).</div>
1794+
</div>
1795+
<div className="field-editor-row">
1796+
<label>Value Separator (when not returning array):</label>
1797+
<select
1798+
value={((editedField: any): { valueSeparator?: string }).valueSeparator || 'commaSpace'}
1799+
onChange={(e) => {
1800+
const updated = { ...editedField }
1801+
;(updated: any).valueSeparator = e.target.value || undefined
1802+
setEditedField(updated)
1803+
}}
1804+
>
1805+
<option value="comma">Comma (no space) — value1,value2</option>
1806+
<option value="commaSpace">Comma with space — value1, value2</option>
1807+
<option value="space">Space — value1 value2</option>
1808+
</select>
1809+
<div className="field-editor-help">How to join multiple selected values when not returning as array. Comma with space is default for readability.</div>
17941810
</div>
17951811
<div className="field-editor-row">
17961812
<label>

helpers/react/DynamicDialog/CREATING_NEW_DYNAMICDIALOG_FIELD_TYPES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ If your field has custom properties that need to be edited in the Form Builder,
279279
) : null}
280280
```
281281

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.
282+
For multi-select choosers that return a string (`tag-chooser`, `mention-chooser`, `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.
283283

284284
### 7. Test/Examples
285285

helpers/react/DynamicDialog/DynamicDialog.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +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
209+
valueSeparator?: 'comma' | 'commaSpace' | 'space', // for tag-chooser, mention-chooser, frontmatter-key-chooser: when returnAsArray false, how to join values: 'comma'=no space, 'commaSpace'=comma+space, 'space'=space-separated
210210
// frontmatter-key-chooser options
211211
frontmatterKey?: string, // for frontmatter-key-chooser, the frontmatter key to get values for (can be fixed or from sourceKeyKey)
212212
sourceKeyKey?: string, // Value dependency: for frontmatter-key-chooser, key of another field to get the frontmatter key from dynamically

helpers/react/DynamicDialog/MentionChooser.jsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export type MentionChooserProps = {
1717
disabled?: boolean,
1818
compactDisplay?: boolean,
1919
placeholder?: string,
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: 'commaSpace')
2122
defaultChecked?: boolean, // If true, all items checked by default (default: false)
2223
includePattern?: string, // Regex pattern to include mentions
2324
excludePattern?: string, // Regex pattern to exclude mentions
@@ -47,6 +48,7 @@ export function MentionChooser({
4748
compactDisplay = false,
4849
placeholder = 'Type to search mentions...',
4950
returnAsArray = false,
51+
valueSeparator = 'commaSpace',
5052
defaultChecked = false,
5153
includePattern = '',
5254
excludePattern = '',
@@ -73,7 +75,7 @@ export function MentionChooser({
7375
const [loaded, setLoaded] = useState<boolean>(hasInitialMentions) // If preloaded, mark as loaded
7476
const [loading, setLoading] = useState<boolean>(false)
7577
// Ref to track if component is mounted (prevents callbacks after unmount)
76-
const isMountedRef = useRef<boolean>(true)
78+
const isMountedRef = React.useRef<boolean>(true)
7779

7880
// Track mount state to prevent callbacks after unmount
7981
useEffect(() => {
@@ -207,6 +209,7 @@ export function MentionChooser({
207209
items={mentions}
208210
getItemDisplayLabel={getItemDisplayLabel}
209211
returnAsArray={returnAsArray}
212+
valueSeparator={valueSeparator}
210213
defaultChecked={defaultChecked}
211214
includePattern={includePattern}
212215
excludePattern={excludePattern}

helpers/react/DynamicDialog/TagChooser.jsx

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// A multi-select chooser for hashtags using ContainedMultiSelectChooser
55
//--------------------------------------------------------------------------
66

7-
import React, { useState, useEffect, useCallback } from 'react'
7+
import React, { useState, useEffect, useCallback, useRef } from 'react'
88
import ContainedMultiSelectChooser from './ContainedMultiSelectChooser.jsx'
99
import { DropdownSelectChooser, type DropdownOption } from './DropdownSelectChooser.jsx'
1010
import { logDebug, logError } from '@helpers/react/reactDev.js'
@@ -17,7 +17,8 @@ export type TagChooserProps = {
1717
disabled?: boolean,
1818
compactDisplay?: boolean,
1919
placeholder?: string,
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: 'commaSpace')
2122
defaultChecked?: boolean, // If true, all items checked by default (default: false)
2223
includePattern?: string, // Regex pattern to include tags
2324
excludePattern?: string, // Regex pattern to exclude tags
@@ -47,6 +48,7 @@ export function TagChooser({
4748
compactDisplay = false,
4849
placeholder = 'Type to search hashtags...',
4950
returnAsArray = false,
51+
valueSeparator = 'commaSpace',
5052
defaultChecked = false,
5153
includePattern = '',
5254
excludePattern = '',
@@ -144,27 +146,24 @@ export function TagChooser({
144146
// Handle creating a new tag
145147
// Note: Tags in NotePlan are derived from notes, so we can't "create" them in DataStore
146148
// Instead, we add the new tag to our local list so it can be selected and used in the form
147-
const handleCreateTag = useCallback(
148-
async (newTag: string): Promise<void> => {
149-
// Remove # prefix if present (we store tags without prefix internally)
150-
const cleanedTag = newTag.startsWith('#') ? newTag.substring(1) : newTag
151-
const trimmedTag = cleanedTag.trim()
149+
const handleCreateTag = useCallback(async (newTag: string): Promise<void> => {
150+
// Remove # prefix if present (we store tags without prefix internally)
151+
const cleanedTag = newTag.startsWith('#') ? newTag.substring(1) : newTag
152+
const trimmedTag = cleanedTag.trim()
152153

153-
if (!trimmedTag) {
154-
return
155-
}
154+
if (!trimmedTag) {
155+
return
156+
}
156157

157-
// Add the new tag to our local list if it doesn't already exist
158-
setHashtags((prev) => {
159-
if (!prev.includes(trimmedTag)) {
160-
logDebug('TagChooser', `Added new tag to local list: ${trimmedTag}`)
161-
return [...prev, trimmedTag]
162-
}
163-
return prev
164-
})
165-
},
166-
[],
167-
)
158+
// Add the new tag to our local list if it doesn't already exist
159+
setHashtags((prev) => {
160+
if (!prev.includes(trimmedTag)) {
161+
logDebug('TagChooser', `Added new tag to local list: ${trimmedTag}`)
162+
return [...prev, trimmedTag]
163+
}
164+
return prev
165+
})
166+
}, [])
168167

169168
// If renderAsDropdown is true and singleValue is true, render as dropdown
170169
if (renderAsDropdown && singleValue) {
@@ -207,6 +206,7 @@ export function TagChooser({
207206
items={hashtags}
208207
getItemDisplayLabel={getItemDisplayLabel}
209208
returnAsArray={returnAsArray}
209+
valueSeparator={valueSeparator}
210210
defaultChecked={defaultChecked}
211211
includePattern={includePattern}
212212
excludePattern={excludePattern}

helpers/react/DynamicDialog/dialogElementRenderer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,7 @@ export function renderItem({
11571157
const compactDisplay = item.compactDisplay || false
11581158
const currentValue = item.value || item.default || ''
11591159
const returnAsArray = (item: any).returnAsArray ?? false
1160+
const valueSeparator = (item: any).valueSeparator ?? 'commaSpace'
11601161
const defaultChecked = (item: any).defaultChecked ?? false
11611162
const includePattern = (item: any).includePattern || ''
11621163
const excludePattern = (item: any).excludePattern || ''
@@ -1185,6 +1186,7 @@ export function renderItem({
11851186
compactDisplay={compactDisplay}
11861187
placeholder={item.placeholder || 'Type to search hashtags...'}
11871188
returnAsArray={returnAsArray}
1189+
valueSeparator={valueSeparator}
11881190
defaultChecked={defaultChecked}
11891191
includePattern={includePattern}
11901192
excludePattern={excludePattern}
@@ -1207,6 +1209,7 @@ export function renderItem({
12071209
const compactDisplay = item.compactDisplay || false
12081210
const currentValue = item.value || item.default || ''
12091211
const returnAsArray = (item: any).returnAsArray ?? false
1212+
const valueSeparator = (item: any).valueSeparator ?? 'commaSpace'
12101213
const defaultChecked = (item: any).defaultChecked ?? false
12111214
const includePattern = (item: any).includePattern || ''
12121215
const excludePattern = (item: any).excludePattern || ''
@@ -1235,6 +1238,7 @@ export function renderItem({
12351238
compactDisplay={compactDisplay}
12361239
placeholder={item.placeholder || 'Type to search mentions...'}
12371240
returnAsArray={returnAsArray}
1241+
valueSeparator={valueSeparator}
12381242
defaultChecked={defaultChecked}
12391243
includePattern={includePattern}
12401244
excludePattern={excludePattern}

0 commit comments

Comments
 (0)