PromptModal reusable provides a single, theme-wide modal container that can render dynamic prompt forms (inputs, groups, buttons) driven by the global Alpine modal store.$store.modal.current.Structure/LayoutA.liquid:{% render 'Reusables\\PromptModal\\Default' %}$store.modal.$store.modal Alpine store. To document the contract correctly we need a real sample of the modal store shape at runtime (especially $store.modal.current).{
"title": "Sample title",
"groups": [
{
"description": "Sample description",
"inputs": [
{
"type": "checkbox",
"label": "Sample text",
"labelHidden": true,
"name": "shoppingLists",
"value": "",
"required": false,
"options": [
{
"key": "Shopping List Id",
"value": "Sample text",
"sticky": false
},
...
]
},
{
"type": "text",
"label": "Sample text",
"isHidden": true,
"placeholder": "Sample text",
"name": "newShoppingList",
"value": "",
"required": false,
"options": [
{
"key": "Shopping List Id",
"value": "Sample text"
},
...
],
"button": {
"label": "Sample button text",
"ariaLabel": "Sample text",
"class": "btn--primary"
}
}
]
}
],
"data": [
{
"key": "shoppingLists",
"value": "",
"type": "checkbox",
"required": false
},
{
"key": "newShoppingList",
"value": "",
"type": "text",
"isHidden": true,
"required": false
}
],
"buttons": [
{
"label": "Sample button text",
"class": "btn btn--lg btn--primary"
}
]
}$store.modal.visible: boolean$store.modal.close(): function$store.modal.current: objectcurrent.groups: array (required if you want inputs)group.inputs: arraytype: stringname: string (used as key for $store.modal.errors[name] and current.data lookup)required: boolean$store.modal.current.data: array of { key, value } entries$store.modal.current.data.find(field => field.key === input.name).value$store.modal.current.title: string$store.modal.current.message: string (HTML)group.header: stringgroup.description: string (HTML)label: stringlabelHidden: booleanisHidden: booleanplaceholder: stringhelperText: string$store.modal.visible is true.$store.modal.close().@after-leave) the template triggers $store.modal.close() again, which is expected to fully reset modal state.$store.modal.current?.title exists.$store.modal.current?.message exists.$store.modal.current?.groups is present and non-empty, the template renders a dynamic form driven by those groups.x-data="promptmodalreusabledefault".$store.modal.shouldReinitialize becomes true:if ($store.modal.shouldReinitialize) {
reInit();
$store.modal.shouldReinitialize = false;
}input.type values:texttextareaemailfilecheckbox (renders a checkbox group and maintains a -required hidden input)selectradio (through a .radio-wrap + -required hidden input pattern)hidden inputs used as validation proxies for checkbox/radio groupsbutton: object (text input supports an Enter key handler that triggers button.action)acceptedFileTypes: string[] (used for accept)multipleFiles: booleanoptions: array (checkbox/radio/select)onchange(event): function (checkbox)promptmodalreusabledefault (in Reusables/PromptModal/Default.js).x-data="promptmodalreusabledefault"isFormValid: booleanfields: array of DOM elements (initialized in reInit())[name]:not(.skipped)blur listener to validate.SELECT:Alpine.store('modal').current.data.isEmpty class on .select-wrap.change.checkbox, radio, file:change..switch-wrap), also validates when the switch UI is clicked..radio-wrap groups:${groupName}-required to validate required selection.change, copies the selected value into the hidden input and also updates Alpine.store('modal').current.data[0].value.${groupName}-required field.Alpine.store('modal').current.data (as an array of selected values).updateFormValidity().SELECT controls..isEmpty class on the closest .select-wrap when the selected value is empty or the first option is selected..checkbox-wrap:Alpine.store('modal').errors[hiddenInputName]..invalid / .valid on .checkbox-wrap..radio-wrap:Alpine.store('modal').errors[hiddenInputName]..invalid / .valid on .radio-wrap.updateFormValidity() and returns.radio:Alpine.store('modal').errors[field.name].checkbox:Alpine.store('modal').errors[groupName]..consent, also updates Alpine.store('modal').current.data[0].value to boolean..switch-wrap, toggles .invalid / .valid on the switch wrapper.SELECT:file:accept attribute.invalidFileType, fileTooLarge.email:invalidEmail.invalid / valid classes on wrappers..icon-state to show warning/check icons.updateFormValidity() at the end.isFormValid to true if every tracked field is valid.Alpine.store('modal').errors[field.name] is emptyvalidateField for every field.isFormValid is false.$store.modalvisiblecurrenterrorsshouldReinitializeclose()$store.modal.current button actions (e.g. button.action(modalData) in other reusables).Reusables/PromptModal/Default.liquidReusables/PromptModal/Default.jsReusables/PromptModal/Default.json$store.modal$store.modal.current.data for matching keys. If an input exists in groups.inputs but no matching { key, value } exists in current.data, the template will throw at runtime.-required input for validationcurrent.data for data semanticsisDataValid() is used as a cross-reusable validation gate (e.g. in Reusables/ShoppingLists/Default.js, Reusables/Users/Default.js, and Reusables/ShoppingListsButton/Default.js).