");
+
+ expect(wrapper!.find("[role=listbox]").exists()).toBe(false);
+ });
+ });
});
diff --git a/resources/js/components/Factory/Document/QuestionCard.vue b/resources/js/components/Factory/Document/QuestionCard.vue
index 3a499a66..30d8b19f 100644
--- a/resources/js/components/Factory/Document/QuestionCard.vue
+++ b/resources/js/components/Factory/Document/QuestionCard.vue
@@ -5,7 +5,20 @@
Question {{ number }} text
-
+
+
@@ -133,7 +146,9 @@ import ConfigureConsents from "@/components/Factory/Main/ConfigureConsents.vue";
import { useBlockTypes } from "@/components/Factory/utils/useBlockTypes";
import BlockMenu from "./BlockMenu.vue";
import QuestionSettings from "./QuestionSettings.vue";
+import TypePicker from "./TypePicker.vue";
import { cursorFor } from "./blockFocus";
+import { plainText } from "./questionSummary";
const props = defineProps<{
block: FormBlockModel;
@@ -166,6 +181,25 @@ watch(type, (value) => {
}
});
+// "/" in an empty question picks its answer type
+const typesOpen = ref(false);
+const openTypes = (event: KeyboardEvent) => {
+ const inText = (event.target as HTMLElement).closest("[contenteditable]");
+
+ if (event.key === "/" && inText && !plainText(message.value)) {
+ event.preventDefault();
+ typesOpen.value = true;
+ }
+};
+const closeTypes = () => {
+ typesOpen.value = false;
+ editor.value?.focus();
+};
+const changeType = (value: FormBlockType) => {
+ type.value = types.value.find((item) => item.value === value);
+ closeTypes();
+};
+
const takeCursor = () => {
if (cursorFor.value === props.block.uuid) {
cursorFor.value = null;
diff --git a/resources/js/components/Factory/Shared/Editor.vue b/resources/js/components/Factory/Shared/Editor.vue
index e0660f38..e4b18abb 100644
--- a/resources/js/components/Factory/Shared/Editor.vue
+++ b/resources/js/components/Factory/Shared/Editor.vue
@@ -137,6 +137,7 @@ const {
counter = false,
bare = false,
id,
+ placeholder = "Type your message here...",
} = defineProps<{
modelValue?: string;
counter?: boolean;
@@ -144,6 +145,7 @@ const {
bare?: boolean;
// names the text box after the D9Label with the same id
id?: string;
+ placeholder?: string;
}>();
const content = ref("");
@@ -181,7 +183,7 @@ const editor = useEditor({
extensions: [
FontFamily,
Placeholder.configure({
- placeholder: "Type your message here...",
+ placeholder,
}),
StarterKit.configure({
heading: {
From e710ffdd1ac4b51b3fbf5fc3aa3d2c580984e3ef Mon Sep 17 00:00:00 2001
From: Philipp Reinking
Date: Tue, 13 Oct 2026 02:42:30 +0200
Subject: [PATCH 05/14] docs: add the type picker to the changelog (#276)
Co-Authored-By: Claude Opus 5.5
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1ba371a3..e9ae3a85 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ Lines marked **Upgrade step** need you to act after updating.
- The API can copy a question (`POST /api/blocks/{block}/duplicate`) with its text, settings, answers and Required, but not its rules. The copy goes right after the original. (#262)
- The form's team can try a form in preview mode, also while it's a draft. Answers in preview mode are never stored or counted and send no webhooks or emails. (#266)
- Add `?preview=1` to your form's link to try it as a member of its team. Nothing is stored, files aren't uploaded and the form doesn't redirect at the end. (#270)
+- Add a question right where you need it: between questions, from a question's menu or with "/" in an empty question. A picker groups the answer types as Text, Choice, Rating, Other and Content, and the new question opens ready to type. The answer types have new names, like Single choice, Short text and Text only. (#276)
### Changed
From bfb6428c44e601ccc98a3fccb513be91cc7d6a38 Mon Sep 17 00:00:00 2001
From: Philipp Reinking
Date: Tue, 13 Oct 2026 02:56:56 +0200
Subject: [PATCH 06/14] docs: say that / picks the empty question's type
Co-Authored-By: Claude Opus 5.5
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c2799b9a..4b0b3a78 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,7 +14,7 @@ Lines marked **Upgrade step** need you to act after updating.
- The API can copy a question (`POST /api/blocks/{block}/duplicate`) with its text, settings, answers and Required, but not its rules. The copy goes right after the original. (#262)
- The form's team can try a form in preview mode, also while it's a draft. Answers in preview mode are never stored or counted and send no webhooks or emails. (#266)
- Add `?preview=1` to your form's link to try it as a member of its team. Nothing is stored, files aren't uploaded and the form doesn't redirect at the end. (#270)
-- Add a question right where you need it: between questions, from a question's menu or with "/" in an empty question. A picker groups the answer types as Text, Choice, Rating, Other and Content, and the new question opens ready to type. The answer types have new names, like Single choice, Short text and Text only. (#276)
+- Add a question right where you need it: between questions or from a question's menu, and type "/" in an empty question to pick its answer type. A picker groups the answer types as Text, Choice, Rating, Other and Content, and the new question opens ready to type. The answer types have new names, like Single choice, Short text and Text only. (#276)
### Changed
From 21be8501227ae0dc9265c1d2c7594c2b6547c477 Mon Sep 17 00:00:00 2001
From: Philipp Reinking
Date: Tue, 13 Oct 2026 02:58:03 +0200
Subject: [PATCH 07/14] docs: use the new answer type names and the picker
steps
Multiple Choice is Single choice, Secret is Hidden text, Information
Block is Text only, Phone Number is Phone. Adding a question names the
add line, the menu and the picker. Page links stay the same.
Co-Authored-By: Claude Opus 5.5
---
.vitepress/config.mts | 12 ++++++------
docs/input-types/checkboxes.md | 2 +-
docs/input-types/multiplechoice.md | 8 ++++----
docs/input-types/no-interaction.md | 12 ++++++------
docs/input-types/phone.md | 4 ++--
docs/input-types/secret.md | 12 ++++++------
docs/quick-start.md | 4 ++--
docs/workbench/form-blocks.md | 20 +++++++++++---------
docs/workbench/workbench.md | 2 +-
9 files changed, 39 insertions(+), 37 deletions(-)
diff --git a/.vitepress/config.mts b/.vitepress/config.mts
index d1dee7d2..f85b6807 100644
--- a/.vitepress/config.mts
+++ b/.vitepress/config.mts
@@ -43,26 +43,26 @@ export default defineConfig({
collapsed: true,
items: [
{
- text: "Information Block 📝",
+ text: "Text only 📝",
link: "/input-types/no-interaction",
},
{
- text: "Multiple Choice 🔘",
+ text: "Single choice 🔘",
link: "/input-types/multiplechoice",
},
{ text: "Checkboxes ✅", link: "/input-types/checkboxes" },
- { text: "Short Text 💬", link: "/input-types/short-text" },
- { text: "Long Text 📄", link: "/input-types/long-text" },
+ { text: "Short text 💬", link: "/input-types/short-text" },
+ { text: "Long text 📄", link: "/input-types/long-text" },
{ text: "Number 🔢", link: "/input-types/number" },
{ text: "Email 📧", link: "/input-types/email" },
{ text: "Link 🔗", link: "/input-types/link" },
{ text: "Phone ☎️", link: "/input-types/phone" },
{ text: "Date 📅", link: "/input-types/date" },
- { text: "Secret 🔒", link: "/input-types/secret" },
+ { text: "Hidden text 🔒", link: "/input-types/secret" },
{ text: "Rating ⭐", link: "/input-types/rating" },
{ text: "Scale 📊", link: "/input-types/scale" },
{
- text: "File Upload 📁",
+ text: "File upload 📁",
link: "/input-types/file-upload",
},
{ text: "Consent ✓", link: "/input-types/consent" },
diff --git a/docs/input-types/checkboxes.md b/docs/input-types/checkboxes.md
index 97ea775d..a7ef4565 100644
--- a/docs/input-types/checkboxes.md
+++ b/docs/input-types/checkboxes.md
@@ -36,5 +36,5 @@ Checkboxes allow users to select multiple options from a list, making them perfe
- When you want to offer comprehensive option coverage
::: warning Note
-If you want users to select only one option from a list, use the **Multiple Choice (Radio)** input type instead.
+If you want users to select only one option from a list, use the **Single choice** input type instead.
:::
diff --git a/docs/input-types/multiplechoice.md b/docs/input-types/multiplechoice.md
index 1ef81308..7e7e4a4b 100644
--- a/docs/input-types/multiplechoice.md
+++ b/docs/input-types/multiplechoice.md
@@ -1,10 +1,10 @@
-# Multiple Choice 🔘
+# Single choice 🔘
This input type allows users to select one option from a list of choices, perfect for questions with mutually exclusive answers.
## How to use:
-1. Choose **Multiple Choice (Radio)** as the input type
+1. Choose **Single choice** as the input type
2. Enter the question or prompt
3. Add your answer options, one per line
4. Toggle the "Required" option if necessary
@@ -28,7 +28,7 @@ This input type allows users to select one option from a list of choices, perfec
🌟 Keep options mutually exclusive and limit the number of choices to prevent overwhelming users. Consider adding an "Other" option for unexpected responses, and arrange options in a logical order (alphabetical, numerical, or by relevance).
:::
-## When to use Multiple Choice:
+## When to use Single choice:
- When users should select only one option from a list
- For questions with mutually exclusive answers
@@ -40,4 +40,4 @@ This input type allows users to select one option from a list of choices, perfec
If users might need to select more than one option, use the **Checkboxes** input type instead.
:::
-
+
diff --git a/docs/input-types/no-interaction.md b/docs/input-types/no-interaction.md
index 169035f2..af3f24c7 100644
--- a/docs/input-types/no-interaction.md
+++ b/docs/input-types/no-interaction.md
@@ -1,10 +1,10 @@
-# Information Block 📝
+# Text only 📝
-The Information Block displays information without requiring any user input. It's perfect for instructions, introductions, explanations, or content that doesn't need a response.
+A Text only block displays information without requiring any user input. It's perfect for instructions, introductions, explanations, or content that doesn't need a response.
## How to use:
-1. Choose **Information Block** as the block type
+1. Choose **Text only** as the block type
2. Enter the content you want to display
3. Format your text using markdown for headings, lists, links, and emphasis
4. Add buttons if you want users to acknowledge the information or make a choice
@@ -26,10 +26,10 @@ The Information Block displays information without requiring any user input. It'
- **Thank You Messages**: Acknowledgments for participation
::: tip Best Practice
-🌟 Keep information blocks concise and visually structured. Use headings, bullet points, and formatting to make content scannable and easy to understand.
+🌟 Keep Text only blocks concise and visually structured. Use headings, bullet points, and formatting to make content scannable and easy to understand.
:::
-## When to use Information Blocks:
+## When to use Text only blocks:
- When you need to provide context before asking questions
- For introductory or concluding information
@@ -38,5 +38,5 @@ The Information Block displays information without requiring any user input. It'
- For any informational content that doesn't require user input
::: warning Note
-Since Information Blocks don't collect any data, they won't appear in your submission results. They're purely for communication with your users.
+Since Text only blocks don't collect any data, they won't appear in your submission results. They're purely for communication with your users.
:::
diff --git a/docs/input-types/phone.md b/docs/input-types/phone.md
index 5ca4b158..e22fbb09 100644
--- a/docs/input-types/phone.md
+++ b/docs/input-types/phone.md
@@ -4,7 +4,7 @@ This input type is specifically designed for collecting phone numbers with appro
## How to use:
-1. Select **Phone Number** as the input type
+1. Select **Phone** as the input type
2. Enter the prompt asking for the phone number
3. Optionally, add a placeholder showing the expected format
4. Configure validation requirements if needed
@@ -28,7 +28,7 @@ This input type is specifically designed for collecting phone numbers with appro
🌟 Always specify what the phone number will be used for and include a privacy statement. For international forms, include example formatting or country code instructions.
:::
-## When to use Phone Number input:
+## When to use Phone input:
- When you need to contact users by phone or SMS
- For authentication or verification purposes
diff --git a/docs/input-types/secret.md b/docs/input-types/secret.md
index e75ac30d..737c06d8 100644
--- a/docs/input-types/secret.md
+++ b/docs/input-types/secret.md
@@ -1,15 +1,15 @@
-# Secret 🔒
+# Hidden text 🔒
-The Secret input type lets you securely collect sensitive information from users. It works like a password field, masking the entered text to protect it from view.
+The Hidden text input type lets you securely collect sensitive information from users. It works like a password field, masking the entered text to protect it from view.
## How to use:
-1. Choose **Secret** as the input type
+1. Choose **Hidden text** as the input type
2. Enter the question or prompt that asks for sensitive information
3. Optionally, add a placeholder text to guide users on what to enter
::: tip Best Practice
-< Only use Secret inputs when necessary for sensitive information like passwords, access codes, or PIN numbers.
+< Only use Hidden text inputs when necessary for sensitive information like passwords, access codes, or PIN numbers.
:::
## Features:
@@ -25,7 +25,7 @@ The Secret input type lets you securely collect sensitive information from users
- **Access Codes**: For private invitation codes or tokens
- **Private Information**: Any sensitive data that shouldn't be visible on screen
-## When to use Secret inputs:
+## When to use Hidden text inputs:
- When collecting information that should remain private
- When users are in public spaces and don't want others to see what they're typing
@@ -33,5 +33,5 @@ The Secret input type lets you securely collect sensitive information from users
- When compliance or security requirements mandate protected input fields
::: warning Security Note
-While Secret inputs hide information on screen, remember that this is a visual protection only. For truly sensitive information, ensure you have proper encryption and security measures in place for data transmission and storage.
+While Hidden text inputs hide information on screen, remember that this is a visual protection only. For truly sensitive information, ensure you have proper encryption and security measures in place for data transmission and storage.
:::
diff --git a/docs/quick-start.md b/docs/quick-start.md
index 96c8dcd8..a01571f7 100644
--- a/docs/quick-start.md
+++ b/docs/quick-start.md
@@ -110,8 +110,8 @@ Once you have Input up and running, follow these steps to create your first form
5. Preview your form to ensure it looks and works as expected
6. Publish your form when it's ready to share
-
-_Example: Adding a multiple choice question to your form_
+
+_Example: Adding a single choice question to your form_
## Troubleshooting Common Issues
diff --git a/docs/workbench/form-blocks.md b/docs/workbench/form-blocks.md
index 5422a717..591a82fa 100644
--- a/docs/workbench/form-blocks.md
+++ b/docs/workbench/form-blocks.md
@@ -8,20 +8,20 @@ Input offers a variety of block types to gather different kinds of information:
### Information Blocks
-- [**Information Block**](/input-types/no-interaction): Displays text without requiring user input, useful for instructions or explanations
+- [**Text only**](/input-types/no-interaction): Displays text without requiring user input, useful for instructions or explanations
### Text Input Blocks
-- [**Short Text**](/input-types/short-text): For collecting brief responses like names or titles
-- [**Long Text**](/input-types/long-text): For gathering detailed responses, comments, or feedback
+- [**Short text**](/input-types/short-text): For collecting brief responses like names or titles
+- [**Long text**](/input-types/long-text): For gathering detailed responses, comments, or feedback
- [**Email**](/input-types/email): Specifically for email addresses, with built-in validation
- [**Phone**](/input-types/phone): For phone numbers, with optional formatting
- [**Link**](/input-types/link): For collecting website URLs
-- [**Secret**](/input-types/secret): For sensitive information like passwords, with masked input
+- [**Hidden text**](/input-types/secret): For sensitive information like passwords, with masked input
### Choice Blocks
-- [**Multiple Choice**](/input-types/multiplechoice): Allows selection of one option from several choices
+- [**Single choice**](/input-types/multiplechoice): Allows selection of one option from several choices
- [**Checkboxes**](/input-types/checkboxes): Allows selection of multiple options from a list
- [**Scale**](/input-types/scale): Presents a numerical scale (e.g., 1-10) for rating questions
- [**Rating**](/input-types/rating): Star-based rating system for satisfaction or quality assessment
@@ -30,16 +30,18 @@ Input offers a variety of block types to gather different kinds of information:
- [**Number**](/input-types/number): For numerical input with optional min/max validation
- [**Date**](/input-types/date): For collecting calendar dates
-- [**File Upload**](/input-types/file-upload): Allows users to upload files or images
+- [**File upload**](/input-types/file-upload): Allows users to upload files or images
- [**Consent**](/input-types/consent): For collecting explicit consent with terms or policies
## Adding Blocks to Your Form
To add a new block to your form:
-1. Click the "+" button at the bottom of an existing block
-2. Choose the block type from the dropdown menu
-3. The new block will be inserted below the current block
+1. Point between two questions and click "Add question here", or choose "Add question below" in a question's menu
+2. Choose the type in the picker, or type to search for it
+3. The new question opens right there, ready to type
+
+To change the type of an empty question, type "/" in its text.
## Block Groups
diff --git a/docs/workbench/workbench.md b/docs/workbench/workbench.md
index 53cf41eb..dcd6eba3 100644
--- a/docs/workbench/workbench.md
+++ b/docs/workbench/workbench.md
@@ -23,7 +23,7 @@ The building blocks of your form are called "blocks." Each block represents a qu
When you select a block, you can configure its properties in the main workbench area:
-1. **Block Type** - Choose the type of interaction (text, multiple choice, etc.)
+1. **Block Type** - Choose the type of interaction (text, single choice, etc.)
2. **Question Text** - The main prompt or question for the user
3. **Description** - Optional additional information about the question
4. **Required Setting** - Toggle whether the user must complete this block
From 2f9036d1c84b90c55268f14b21a971422ae2828a Mon Sep 17 00:00:00 2001
From: Philipp Reinking
Date: Tue, 13 Oct 2026 03:00:25 +0200
Subject: [PATCH 08/14] feat: Add question at the end of the document adds at
the end
The line, the gutter + and the menu add after a given question. The end
button always adds after the last block, also while a question is open.
Co-Authored-By: Claude Opus 5.5
---
.../js/components/Factory/Document/FormDocument.test.ts | 5 +++--
.../js/components/Factory/Document/FormDocument.vue | 9 ++-------
2 files changed, 5 insertions(+), 9 deletions(-)
diff --git a/resources/js/components/Factory/Document/FormDocument.test.ts b/resources/js/components/Factory/Document/FormDocument.test.ts
index 65384a49..a7909ecf 100644
--- a/resources/js/components/Factory/Document/FormDocument.test.ts
+++ b/resources/js/components/Factory/Document/FormDocument.test.ts
@@ -89,7 +89,7 @@ describe("the form document", () => {
);
});
- it("adds a question after the open one, with the cursor in its text", async () => {
+ it("adds a question at the end, also with one open, with the cursor in its text", async () => {
const added = {
id: 3,
uuid: "b3",
@@ -110,11 +110,12 @@ describe("the form document", () => {
.findAll("button")
.find((button) => button.text() === "Add question")!
.trigger("click");
+ expect(wrapper.text()).toContain("Add a question at the end");
await option("Email").trigger("click");
await flushPromises();
expect(useForm().createFormBlock).toHaveBeenCalledWith(
- blocks[0],
+ null,
"input-email",
null,
);
diff --git a/resources/js/components/Factory/Document/FormDocument.vue b/resources/js/components/Factory/Document/FormDocument.vue
index 2bc8f4b6..d6da8aa2 100644
--- a/resources/js/components/Factory/Document/FormDocument.vue
+++ b/resources/js/components/Factory/Document/FormDocument.vue
@@ -61,7 +61,7 @@
icon="plus"
icon-position="left"
:aria-expanded="addAt?.key === 'end'"
- @click="openPicker({ key: 'end', after: workbench.block })"
+ @click="openPicker({ key: 'end', after: null })"
/>
@@ -131,7 +127,6 @@ import DocumentOutline from "./DocumentOutline.vue";
import TypePicker from "./TypePicker.vue";
import {
addAt,
- blockName,
closePicker,
openPicker,
useAddQuestion,
From 9b5a38db20dbacb6c63d09ef82bbe5fac32954c1 Mon Sep 17 00:00:00 2001
From: Philipp Reinking
Date: Tue, 13 Oct 2026 03:00:27 +0200
Subject: [PATCH 09/14] fix: an empty form doesn't say 0 questions
Co-Authored-By: Claude Opus 5.5
---
.../js/components/Factory/Document/FormDocument.test.ts | 1 +
resources/js/components/Factory/Document/FormDocument.vue | 6 ++++--
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/resources/js/components/Factory/Document/FormDocument.test.ts b/resources/js/components/Factory/Document/FormDocument.test.ts
index a7909ecf..c7b5225d 100644
--- a/resources/js/components/Factory/Document/FormDocument.test.ts
+++ b/resources/js/components/Factory/Document/FormDocument.test.ts
@@ -80,6 +80,7 @@ describe("the form document", () => {
});
expect(wrapper.text()).toContain("Add your first question");
+ expect(wrapper.text()).not.toContain("0 questions");
await option("Short text").trigger("click");
expect(useForm().createFormBlock).toHaveBeenCalledWith(
diff --git a/resources/js/components/Factory/Document/FormDocument.vue b/resources/js/components/Factory/Document/FormDocument.vue
index d6da8aa2..a1aa8560 100644
--- a/resources/js/components/Factory/Document/FormDocument.vue
+++ b/resources/js/components/Factory/Document/FormDocument.vue
@@ -24,11 +24,13 @@
Questions
-
+
{{ countLine }}
@@ -62,7 +64,9 @@
role="option"
:aria-selected="type.value === active"
class="flex h-11 cursor-pointer items-center gap-2.5 rounded-md px-2 text-base text-grey-900 hover:bg-grey-100 lg:h-9 lg:text-sm"
- :class="type.value === active && 'bg-grey-100'"
+ :class="
+ type.value === active && typing && 'ring-2 ring-inset ring-blue-500'
+ "
@click="emit('pick', type.value)"
>
(null);
const heading = ref(null);
const search = ref(null);
+// the highlight shows only while you type: Enter adds it
+const typing = ref(false);
const isLarge = useMediaQuery("(min-width: 1024px)");
const filter = ref("");
From 3b5d677c88dfe4755a71647d2873e2b43ae1bfd5 Mon Sep 17 00:00:00 2001
From: Philipp Reinking
Date: Tue, 13 Oct 2026 03:12:57 +0200
Subject: [PATCH 12/14] fix: a click outside the / picker leaves the cursor
where you clicked
Closing it by a click elsewhere put the cursor back into the question a
frame later, so typing in the end picker's search landed in the question
text. Only Esc and a pick give the cursor back now.
Co-Authored-By: Claude Opus 5.5
---
.../Factory/Document/QuestionCard.test.ts | 20 ++++++++++++++++++-
.../Factory/Document/QuestionCard.vue | 7 +++++--
.../Factory/Document/TypePicker.vue | 10 +++++++---
3 files changed, 31 insertions(+), 6 deletions(-)
diff --git a/resources/js/components/Factory/Document/QuestionCard.test.ts b/resources/js/components/Factory/Document/QuestionCard.test.ts
index 23c55241..4b1040f5 100644
--- a/resources/js/components/Factory/Document/QuestionCard.test.ts
+++ b/resources/js/components/Factory/Document/QuestionCard.test.ts
@@ -153,9 +153,10 @@ describe("an open question", () => {
});
describe("typing / in its text", () => {
+ const focus = vi.fn();
const editorStub = {
template: `
`,
- methods: { focus() {} },
+ methods: { focus },
};
const slash = async (message: string | null) => {
useWorkbench().block = { ...block, message };
@@ -179,6 +180,23 @@ describe("an open question", () => {
expect(wrapper!.find("[role=listbox]").exists()).toBe(false);
});
+ it("gives the cursor back after Esc, but not after a click elsewhere", async () => {
+ await slash(null);
+ focus.mockClear();
+
+ // the click's target takes the focus; tiptap would take it back a frame later
+ document.body.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+ await flushPromises();
+ expect(wrapper!.find("[role=listbox]").exists()).toBe(false);
+ expect(focus).not.toHaveBeenCalled();
+
+ await wrapper!
+ .find("[contenteditable]")
+ .trigger("keydown", { key: "/" });
+ await wrapper!.find("input").trigger("keydown", { key: "Escape" });
+ expect(focus).toHaveBeenCalledOnce();
+ });
+
it("stays a / in a question with text", async () => {
await slash("
();
-const emit = defineEmits<{ pick: [type: FormBlockType]; close: [] }>();
+// a click elsewhere leaves the focus where the click put it
+const emit = defineEmits<{
+ pick: [type: FormBlockType];
+ close: [returnFocus: boolean];
+}>();
const id = useId();
const { types } = useBlockTypes();
@@ -160,7 +164,7 @@ const onKey = (event: KeyboardEvent) => {
}
};
-onClickOutside(root, () => emit("close"));
+onClickOutside(root, () => emit("close", false));
onMounted(() => {
if (!autofocus) {
From 17d3ccc6d44b530360a1ca7d466c944c761cb79b Mon Sep 17 00:00:00 2001
From: Philipp Reinking
Date: Tue, 13 Oct 2026 03:13:11 +0200
Subject: [PATCH 13/14] docs: answer type names in sentence case, as in the
editor
Co-Authored-By: Claude Opus 5.5
---
docs/input-types/file-upload.md | 8 ++++----
docs/input-types/long-text.md | 12 ++++++------
docs/input-types/number.md | 2 +-
docs/input-types/short-text.md | 14 +++++++-------
docs/workbench/form-blocks.md | 2 +-
5 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/docs/input-types/file-upload.md b/docs/input-types/file-upload.md
index 2ed4154f..af5bc001 100644
--- a/docs/input-types/file-upload.md
+++ b/docs/input-types/file-upload.md
@@ -1,10 +1,10 @@
-# File Upload 📁
+# File upload 📁
-The File Upload input type allows users to attach files to their form responses. It supports multiple file types and offers a user-friendly drag-and-drop interface.
+The File upload input type allows users to attach files to their form responses. It supports multiple file types and offers a user-friendly drag-and-drop interface.
## How to use:
-1. Choose **File Upload** as the input type
+1. Choose **File upload** as the input type
2. Enter instructions for what files users should upload
3. Configure the upload settings:
- **Maximum Files**: Set how many files users can upload
@@ -40,7 +40,7 @@ The File Upload input type allows users to attach files to their form responses.
- **Document Collection**: ID verification or signed forms
- **Media Sharing**: Audio or video uploads for feedback
-## When to use File Upload:
+## When to use File upload:
- When you need users to submit existing files rather than typing information
- For collecting visual or media information that can't be entered as text
diff --git a/docs/input-types/long-text.md b/docs/input-types/long-text.md
index 7c0d2a8f..ca5e5ad0 100644
--- a/docs/input-types/long-text.md
+++ b/docs/input-types/long-text.md
@@ -1,10 +1,10 @@
-# Long Text 📄
+# Long text 📄
-The Long Text input type provides a multi-line text area for collecting detailed, open-ended responses from users. It's ideal for comments, feedback, descriptions, and any situation where users need to enter more than a brief phrase.
+The Long text input type provides a multi-line text area for collecting detailed, open-ended responses from users. It's ideal for comments, feedback, descriptions, and any situation where users need to enter more than a brief phrase.
## How to use:
-1. Select **Long Text** as the input type
+1. Select **Long text** as the input type
2. Enter the question or prompt
3. Configure the text area options:
- **Rows**: Number of visible text rows (1-10, default: 5)
@@ -35,7 +35,7 @@ The Long Text input type provides a multi-line text area for collecting detailed
💡 Set an appropriate character limit based on the expected response length. For short paragraphs, 500 characters is usually sufficient, while detailed feedback might require 1000+ characters. Add clear instructions about the level of detail you're looking for to guide users.
:::
-## When to use Long Text:
+## When to use Long text:
- When you need more than a single line of text
- For qualitative, open-ended responses
@@ -45,7 +45,7 @@ The Long Text input type provides a multi-line text area for collecting detailed
- For feedback that requires elaboration
::: warning Note
-While Long Text fields are great for detailed responses, very long responses may be difficult for users to type, especially on mobile devices. For extremely detailed information, consider breaking your questions into smaller, more focused parts.
+While Long text fields are great for detailed responses, very long responses may be difficult for users to type, especially on mobile devices. For extremely detailed information, consider breaking your questions into smaller, more focused parts.
:::
## Technical Details:
@@ -53,5 +53,5 @@ While Long Text fields are great for detailed responses, very long responses may
- Text can include line breaks and paragraphs
- The character counter updates in real-time as users type
- Maximum storage capacity is virtually unlimited
-- Long Text fields cannot include rich formatting (bold, italics, etc.)
+- Long text fields cannot include rich formatting (bold, italics, etc.)
diff --git a/docs/input-types/number.md b/docs/input-types/number.md
index 4cdbcb6f..1ac92c3d 100644
--- a/docs/input-types/number.md
+++ b/docs/input-types/number.md
@@ -45,7 +45,7 @@ The Number input type is specifically designed for collecting numerical data wit
- When you want proper numeric formatting (thousands separators)
::: warning Note
-Number inputs only accept numeric values. If users might need to add any text or explanations with their numbers, consider using a Short Text input instead.
+Number inputs only accept numeric values. If users might need to add any text or explanations with their numbers, consider using a Short text input instead.
:::
## Technical Details:
diff --git a/docs/input-types/short-text.md b/docs/input-types/short-text.md
index 0dbef05a..1b42b140 100644
--- a/docs/input-types/short-text.md
+++ b/docs/input-types/short-text.md
@@ -1,10 +1,10 @@
-# Short Text 💬
+# Short text 💬
-The Short Text input type provides a single-line text field for collecting brief responses from users. It's perfect for names, titles, brief answers, and any situation where responses are concise and fit on one line.
+The Short text input type provides a single-line text field for collecting brief responses from users. It's perfect for names, titles, brief answers, and any situation where responses are concise and fit on one line.
## How to use:
-1. Choose **Short Text** as the input type
+1. Choose **Short text** as the input type
2. Enter the question or prompt
3. Configure the input options:
- **Placeholder**: Hint text shown when the field is empty
@@ -29,10 +29,10 @@ The Short Text input type provides a single-line text field for collecting brief
- **Simple Data**: "What city do you live in?"
::: tip Best Practice
-🌟 Keep your questions specific to encourage brief responses. For any question where you expect answers might exceed a few words or require multiple sentences, use the Long Text input type instead.
+🌟 Keep your questions specific to encourage brief responses. For any question where you expect answers might exceed a few words or require multiple sentences, use the Long text input type instead.
:::
-## When to use Short Text:
+## When to use Short text:
- When you need a brief, concise response
- For single-line information collection
@@ -42,7 +42,7 @@ The Short Text input type provides a single-line text field for collecting brief
- When inputs should be quick and straightforward
::: warning Note
-Short Text fields have a hard limit of 100 characters. If users might need more space, consider using a Long Text input instead, which can accommodate more detailed responses.
+Short text fields have a hard limit of 100 characters. If users might need more space, consider using a Long text input instead, which can accommodate more detailed responses.
:::
## Technical Details:
@@ -50,4 +50,4 @@ Short Text fields have a hard limit of 100 characters. If users might need more
- Maximum character limit is fixed at 100 characters
- Input is single-line only (pressing Enter submits the form)
- Character counter updates in real-time as users type
-- Unlike specialized inputs (email, phone, etc.), Short Text has no format validation
\ No newline at end of file
+- Unlike specialized inputs (email, phone, etc.), Short text has no format validation
\ No newline at end of file
diff --git a/docs/workbench/form-blocks.md b/docs/workbench/form-blocks.md
index 591a82fa..3510e9a0 100644
--- a/docs/workbench/form-blocks.md
+++ b/docs/workbench/form-blocks.md
@@ -6,7 +6,7 @@ Form blocks are the fundamental building components of any form in Input. Each b
Input offers a variety of block types to gather different kinds of information:
-### Information Blocks
+### Content Blocks
- [**Text only**](/input-types/no-interaction): Displays text without requiring user input, useful for instructions or explanations
From e323b14b9b73ad2f6e547a7f984058c0423a5411 Mon Sep 17 00:00:00 2001
From: Philipp Reinking
Date: Tue, 13 Oct 2026 03:20:52 +0200
Subject: [PATCH 14/14] fix: a click elsewhere leaves the cursor there, also
for the end picker
With the end "Add question" picker open, a click into the open
question's text put the cursor back on "Add question", so typing went
nowhere. Only Esc and a pick give the cursor back now, as in the "/"
picker.
Co-Authored-By: Claude Opus 5.5
---
.../Factory/Document/FormDocument.test.ts | 32 +++++++++++++++++++
.../Factory/Document/addQuestion.ts | 7 +++-
2 files changed, 38 insertions(+), 1 deletion(-)
diff --git a/resources/js/components/Factory/Document/FormDocument.test.ts b/resources/js/components/Factory/Document/FormDocument.test.ts
index c7b5225d..42794653 100644
--- a/resources/js/components/Factory/Document/FormDocument.test.ts
+++ b/resources/js/components/Factory/Document/FormDocument.test.ts
@@ -123,6 +123,38 @@ describe("the form document", () => {
expect(cursorFor.value).toBe("b3");
});
+ it("gives the cursor back to the end button after Esc, but not after a click elsewhere", async () => {
+ useForm().blocks = blocks;
+ wrapper = mount(FormDocument, {
+ props: { status: "loaded" },
+ global: { stubs },
+ attachTo: document.body,
+ });
+ const button = wrapper
+ .findAll("button")
+ .find((item) => item.text() === "Add question")!;
+ const open = async () => {
+ (button.element as HTMLElement).focus();
+ await button.trigger("click");
+ };
+
+ // a click into the open question's text keeps the cursor there
+ const text = document.createElement("textarea");
+ document.body.append(text);
+ await open();
+ text.focus();
+ text.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+ await flushPromises();
+ expect(addAt.value).toBeNull();
+ expect(document.activeElement).toBe(text);
+ text.remove();
+
+ await open();
+ await wrapper.find("input").trigger("keydown", { key: "Escape" });
+ expect(addAt.value).toBeNull();
+ expect(document.activeElement).toBe(button.element);
+ });
+
it("reads out where a moved question went", async () => {
useForm().blocks = blocks;
wrapper = mount(FormDocument, {
diff --git a/resources/js/components/Factory/Document/addQuestion.ts b/resources/js/components/Factory/Document/addQuestion.ts
index ab2432be..d2833bda 100644
--- a/resources/js/components/Factory/Document/addQuestion.ts
+++ b/resources/js/components/Factory/Document/addQuestion.ts
@@ -25,10 +25,15 @@ export function openPicker(spot: AddSpot) {
addAt.value = spot;
}
-export function closePicker() {
+export function closePicker(returnFocus = true) {
const spot = addAt.value;
addAt.value = null;
+ // a click elsewhere leaves the focus where the click put it
+ if (!returnFocus) {
+ return;
+ }
+
if (opener?.isConnected) {
opener.focus();
} else if (spot) {