/** * What the form controls need from script, which is little: both are kept off Alpine so they work * in any markup and before Alpine starts. * * `` grows with its text. CSS does that where the browser can size a field to its * content (`field-sizing: content`, components/field.css); elsewhere the height is set here from * the scroll height, on every input and whenever a Livewire render or navigation puts a new value * in. * * ``: HTML has no attribute for a checkbox's mixed state, only the * `indeterminate` property, so the component marks the input `data-md-indeterminate` and this keeps * the property in step with the mark — when the page loads, and when a render adds or removes the * mark. A person pressing the box clears the property as the browser always does; the mark stays * until the server decides again. */ const GROWING = 'textarea[data-md-autogrow]' const MIXED = 'input[type="checkbox"][data-md-indeterminate]' const growsByItself = window.CSS?.supports?.('field-sizing', 'content') ?? false function grow(textarea) { textarea.style.height = 'auto' textarea.style.height = `${textarea.scrollHeight + textarea.offsetHeight - textarea.clientHeight}px` } function growAll(root) { if (! growsByItself) { root.querySelectorAll(GROWING).forEach(grow) } } function markAll(root) { root.querySelectorAll(MIXED).forEach((input) => (input.indeterminate = true)) } if (! growsByItself) { document.addEventListener('input', (event) => { if (event.target instanceof HTMLTextAreaElement && event.target.matches(GROWING)) { grow(event.target) } }) document.addEventListener('livewire:init', () => { // A render that only puts a new value in a textarea changes no attribute or node. window.Livewire.hook('morphed', ({ el }) => growAll(el)) }) } new MutationObserver((records) => { for (const record of records) { if (record.type === 'attributes') { if (record.attributeName === 'data-md-indeterminate' && record.target instanceof HTMLInputElement) { record.target.indeterminate = record.target.hasAttribute('data-md-indeterminate') } else if (! growsByItself && record.target.matches(GROWING)) { grow(record.target) } continue } for (const node of record.addedNodes) { if (node.nodeType !== Node.ELEMENT_NODE) { continue } for (const input of [node, ...node.querySelectorAll(MIXED)].filter((element) => element.matches(MIXED))) { input.indeterminate = true } if (! growsByItself) { ;[node, ...node.querySelectorAll(GROWING)].filter((element) => element.matches(GROWING)).forEach(grow) } } } }).observe(document.documentElement, { subtree: true, childList: true, attributes: true, attributeFilter: ['data-md-indeterminate', 'data-md-autogrow'], }) markAll(document) growAll(document)