diff --git a/rollup.config.js b/rollup.config.js index ce8fe67d42b..b930b6505ac 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -38,7 +38,7 @@ const wildcardExternalsPlugin = (peerDependencies) => ({ } }); -const files = glob.sync("src/React/assets/src/*controller.ts"); +const files = glob.sync('src/*/assets/src/*controller.ts'); const packages = files.map((file) => { const absolutePath = path.join(__dirname, file); const packageData = require(pkgUp.sync({ cwd: absolutePath })); diff --git a/src/Autocomplete/CHANGELOG.md b/src/Autocomplete/CHANGELOG.md index 5c07c3da3e9..fe58c2d3860 100644 --- a/src/Autocomplete/CHANGELOG.md +++ b/src/Autocomplete/CHANGELOG.md @@ -1,9 +1,5 @@ # CHANGELOG -## Unreleased - -- Add support for recent versions of Tom Select - ## 2.6.0 - [BC BREAK]: The path to `routes.php` changed and you should update your @@ -17,8 +13,12 @@ ux_autocomplete: prefix: '/autocomplete' ``` -- Fix issue where `max_results` was not passed as a Stimulus value (#538) -- Add all possible stylesheets for tom-select to the autoimport to choose from +- Add support for `tom-select` version `2.2.2` and made this the minimum-supported + version. +- Added support for the `preload` TomSelect option. +- Fix don't add WHERE IN criteria without params (#561). +- Fix issue where `max_results` was not passed as a Stimulus value (#538). +- Add all possible stylesheets for tom-select to the autoimport to choose from. ## 2.5.0 diff --git a/src/Autocomplete/assets/dist/controller.js b/src/Autocomplete/assets/dist/controller.js index 659be6669a0..547aa63d04d 100644 --- a/src/Autocomplete/assets/dist/controller.js +++ b/src/Autocomplete/assets/dist/controller.js @@ -22,11 +22,11 @@ function __classPrivateFieldGet(receiver, state, kind, f) { return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); } -var _default_1_instances, _default_1_getCommonConfig, _default_1_createAutocomplete, _default_1_createAutocompleteWithHtmlContents, _default_1_createAutocompleteWithRemoteData, _default_1_stripTags, _default_1_mergeObjects, _default_1_createTomSelect, _default_1_dispatchEvent; +var _instances, _getCommonConfig, _createAutocomplete, _createAutocompleteWithHtmlContents, _createAutocompleteWithRemoteData, _stripTags, _mergeObjects, _createTomSelect, _dispatchEvent; class default_1 extends Controller { constructor() { super(...arguments); - _default_1_instances.add(this); + _instances.add(this); } initialize() { this.element.setAttribute('data-live-ignore', ''); @@ -39,14 +39,14 @@ class default_1 extends Controller { } connect() { if (this.urlValue) { - this.tomSelect = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createAutocompleteWithRemoteData).call(this, this.urlValue, this.minCharactersValue); + this.tomSelect = __classPrivateFieldGet(this, _instances, "m", _createAutocompleteWithRemoteData).call(this, this.urlValue, this.minCharactersValue); return; } if (this.optionsAsHtmlValue) { - this.tomSelect = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createAutocompleteWithHtmlContents).call(this); + this.tomSelect = __classPrivateFieldGet(this, _instances, "m", _createAutocompleteWithHtmlContents).call(this); return; } - this.tomSelect = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createAutocomplete).call(this); + this.tomSelect = __classPrivateFieldGet(this, _instances, "m", _createAutocomplete).call(this); } disconnect() { this.tomSelect.revertSettings.innerHTML = this.element.innerHTML; @@ -65,14 +65,19 @@ class default_1 extends Controller { return this.element; } get preload() { - if (this.preloadValue == 'false') + if (!this.hasPreloadValue) { + return 'focus'; + } + if (this.preloadValue == 'false') { return false; - if (this.preloadValue == 'true') + } + if (this.preloadValue == 'true') { return true; + } return this.preloadValue; } } -_default_1_instances = new WeakSet(), _default_1_getCommonConfig = function _default_1_getCommonConfig() { +_instances = new WeakSet(), _getCommonConfig = function _getCommonConfig() { const plugins = {}; const isMultiple = !this.selectElement || this.selectElement.multiple; if (!this.formElement.disabled && !isMultiple) { @@ -87,7 +92,7 @@ _default_1_instances = new WeakSet(), _default_1_getCommonConfig = function _def const render = { no_results: () => { return `
${this.noResultsFoundTextValue}
`; - } + }, }; const config = { render: render, @@ -104,19 +109,19 @@ _default_1_instances = new WeakSet(), _default_1_getCommonConfig = function _def if (!this.selectElement && !this.urlValue) { config.shouldLoad = () => false; } - return __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_mergeObjects).call(this, config, this.tomSelectOptionsValue); -}, _default_1_createAutocomplete = function _default_1_createAutocomplete() { - const config = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_mergeObjects).call(this, __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_getCommonConfig).call(this), { + return __classPrivateFieldGet(this, _instances, "m", _mergeObjects).call(this, config, this.tomSelectOptionsValue); +}, _createAutocomplete = function _createAutocomplete() { + const config = __classPrivateFieldGet(this, _instances, "m", _mergeObjects).call(this, __classPrivateFieldGet(this, _instances, "m", _getCommonConfig).call(this), { maxOptions: this.selectElement ? this.selectElement.options.length : 50, }); - return __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createTomSelect).call(this, config); -}, _default_1_createAutocompleteWithHtmlContents = function _default_1_createAutocompleteWithHtmlContents() { - const config = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_mergeObjects).call(this, __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_getCommonConfig).call(this), { + return __classPrivateFieldGet(this, _instances, "m", _createTomSelect).call(this, config); +}, _createAutocompleteWithHtmlContents = function _createAutocompleteWithHtmlContents() { + const config = __classPrivateFieldGet(this, _instances, "m", _mergeObjects).call(this, __classPrivateFieldGet(this, _instances, "m", _getCommonConfig).call(this), { maxOptions: this.selectElement ? this.selectElement.options.length : 50, score: (search) => { const scoringFunction = this.tomSelect.getScoreFunction(search); return (item) => { - return scoringFunction(Object.assign(Object.assign({}, item), { text: __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_stripTags).call(this, item.text) })); + return scoringFunction(Object.assign(Object.assign({}, item), { text: __classPrivateFieldGet(this, _instances, "m", _stripTags).call(this, item.text) })); }; }, render: { @@ -125,12 +130,12 @@ _default_1_instances = new WeakSet(), _default_1_getCommonConfig = function _def }, option: function (item) { return `
${item.text}
`; - } + }, }, }); - return __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createTomSelect).call(this, config); -}, _default_1_createAutocompleteWithRemoteData = function _default_1_createAutocompleteWithRemoteData(autocompleteEndpointUrl, minCharacterLength) { - const config = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_mergeObjects).call(this, __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_getCommonConfig).call(this), { + return __classPrivateFieldGet(this, _instances, "m", _createTomSelect).call(this, config); +}, _createAutocompleteWithRemoteData = function _createAutocompleteWithRemoteData(autocompleteEndpointUrl, minCharacterLength) { + const config = __classPrivateFieldGet(this, _instances, "m", _mergeObjects).call(this, __classPrivateFieldGet(this, _instances, "m", _getCommonConfig).call(this), { firstUrl: (query) => { const separator = autocompleteEndpointUrl.includes('?') ? '&' : '?'; return `${autocompleteEndpointUrl}${separator}query=${encodeURIComponent(query)}`; @@ -138,8 +143,11 @@ _default_1_instances = new WeakSet(), _default_1_getCommonConfig = function _def load: function (query, callback) { const url = this.getUrl(query); fetch(url) - .then(response => response.json()) - .then(json => { this.setNextUrl(query, json.next_page); callback(json.results); }) + .then((response) => response.json()) + .then((json) => { + this.setNextUrl(query, json.next_page); + callback(json.results); + }) .catch(() => callback()); }, shouldLoad: function (query) { @@ -167,17 +175,17 @@ _default_1_instances = new WeakSet(), _default_1_getCommonConfig = function _def }, preload: this.preload, }); - return __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createTomSelect).call(this, config); -}, _default_1_stripTags = function _default_1_stripTags(string) { + return __classPrivateFieldGet(this, _instances, "m", _createTomSelect).call(this, config); +}, _stripTags = function _stripTags(string) { return string.replace(/(<([^>]+)>)/gi, ''); -}, _default_1_mergeObjects = function _default_1_mergeObjects(object1, object2) { +}, _mergeObjects = function _mergeObjects(object1, object2) { return Object.assign(Object.assign({}, object1), object2); -}, _default_1_createTomSelect = function _default_1_createTomSelect(options) { - __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_dispatchEvent).call(this, 'autocomplete:pre-connect', { options }); +}, _createTomSelect = function _createTomSelect(options) { + __classPrivateFieldGet(this, _instances, "m", _dispatchEvent).call(this, 'autocomplete:pre-connect', { options }); const tomSelect = new TomSelect(this.formElement, options); - __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_dispatchEvent).call(this, 'autocomplete:connect', { tomSelect, options }); + __classPrivateFieldGet(this, _instances, "m", _dispatchEvent).call(this, 'autocomplete:connect', { tomSelect, options }); return tomSelect; -}, _default_1_dispatchEvent = function _default_1_dispatchEvent(name, payload) { +}, _dispatchEvent = function _dispatchEvent(name, payload) { this.element.dispatchEvent(new CustomEvent(name, { detail: payload, bubbles: true })); }; default_1.values = { diff --git a/src/Autocomplete/assets/package.json b/src/Autocomplete/assets/package.json index 3c6e0526e65..d47facf1cc6 100644 --- a/src/Autocomplete/assets/package.json +++ b/src/Autocomplete/assets/package.json @@ -14,7 +14,6 @@ "enabled": true, "autoimport": { "tom-select/dist/css/tom-select.default.css": true, - "tom-select/dist/css/tom-select.bootstrap4.css": false, "tom-select/dist/css/tom-select.bootstrap5.css": false } } @@ -22,7 +21,7 @@ }, "peerDependencies": { "@hotwired/stimulus": "^3.0.0", - "tom-select": "^2.0.1" + "tom-select": "^2.2.2" }, "devDependencies": { "@hotwired/stimulus": "^3.0.0", diff --git a/src/Autocomplete/assets/src/controller.ts b/src/Autocomplete/assets/src/controller.ts index 24c50850a52..c83922162bd 100644 --- a/src/Autocomplete/assets/src/controller.ts +++ b/src/Autocomplete/assets/src/controller.ts @@ -19,6 +19,7 @@ export default class extends Controller { readonly noResultsFoundTextValue: string; readonly minCharactersValue: number; readonly tomSelectOptionsValue: object; + readonly hasPreloadValue: boolean; readonly preloadValue: string; tomSelect: TomSelect; @@ -225,6 +226,10 @@ export default class extends Controller { } get preload() { + if (!this.hasPreloadValue) { + return 'focus'; + } + if (this.preloadValue == 'false') { return false; } diff --git a/src/Autocomplete/doc/index.rst b/src/Autocomplete/doc/index.rst index ea1acc8ae08..2b0a7e790aa 100644 --- a/src/Autocomplete/doc/index.rst +++ b/src/Autocomplete/doc/index.rst @@ -167,28 +167,16 @@ Styling Tom Select In your ``assets/controllers.json`` file, you should see a line that automatically includes a CSS file for Tom Select which will give you basic styles. -.. code-block:: text - - "autoimport": { - "tom-select/dist/css/tom-select.default.css": true - } - -If you're using Bootstrap, you can get Bootstrap-ready styling by -changing this line to ``false``: +If you're using Bootstrap, set ``tom-select.default.css`` to false +and ``tom-select.bootstrap5.css`` to true: .. code-block:: text "autoimport": { - "tom-select/dist/css/tom-select.default.css": false + "tom-select/dist/css/tom-select.default.css": false, + "tom-select/dist/css/tom-select.bootstrap5.css": true } -And then importing the Bootstrap CSS file: - -.. code-block:: css - - /* assets/styles/app.css */ - @import 'tom-select/dist/css/tom-select.bootstrap5.css'; - To further customize things, you can override the classes with your own custom CSS and even control how individual parts of Tom Select render. See `Tom Select Render Templates`_. @@ -382,7 +370,7 @@ This only works for Doctrine entities: see `Manually using the Stimulus Controll if you're autocompleting something other than an entity. To expose the endpoint, create a class that implements ``Symfony\UX\Autocomplete\EntityAutocompleterInterface`` -and tag this service with ``ux.entity_autocompleter`` and include an ``alias``:: +and tag this service with ``ux.entity_autocompleter``, including an ``alias`` option:: namespace App\Autocompleter; diff --git a/src/LiveComponent/CHANGELOG.md b/src/LiveComponent/CHANGELOG.md index 3d35f84b8c2..b8461efee92 100644 --- a/src/LiveComponent/CHANGELOG.md +++ b/src/LiveComponent/CHANGELOG.md @@ -14,6 +14,9 @@ live_component: + prefix: /_components ``` +- Removed `Content-Type` header when returning the empty response redirect. +- Fixed bug when re-rendering SVG's (.#557) + ## 2.5.0 - [BEHAVIOR CHANGE] Previously, Ajax calls could happen in parallel (if diff --git a/src/LiveComponent/assets/dist/live_controller.js b/src/LiveComponent/assets/dist/live_controller.js index d812af0f6d0..ee6267767df 100644 --- a/src/LiveComponent/assets/dist/live_controller.js +++ b/src/LiveComponent/assets/dist/live_controller.js @@ -144,13 +144,13 @@ function combineSpacedArray(parts) { return finalParts; } function normalizeModelName(model) { - return model + return (model .replace(/\[]$/, '') .split('[') .map(function (s) { return s.replace(']', ''); }) - .join('.'); + .join('.')); } function getValueFromElement(element, valueStore) { @@ -170,7 +170,7 @@ function getValueFromElement(element, valueStore) { } if (element instanceof HTMLSelectElement) { if (element.multiple) { - return Array.from(element.selectedOptions).map(el => el.value); + return Array.from(element.selectedOptions).map((el) => el.value); } return element.value; } @@ -197,7 +197,7 @@ function setValueOnElement(element, value) { if (element.type === 'checkbox') { if (Array.isArray(value)) { let valueFound = false; - value.forEach(val => { + value.forEach((val) => { if (val == element.value) { valueFound = true; } @@ -211,10 +211,10 @@ function setValueOnElement(element, value) { } } if (element instanceof HTMLSelectElement) { - const arrayWrappedValue = [].concat(value).map(value => { + const arrayWrappedValue = [].concat(value).map((value) => { return value + ''; }); - Array.from(element.options).forEach(option => { + Array.from(element.options).forEach((option) => { option.selected = arrayWrappedValue.includes(option.value); }); return; @@ -242,7 +242,7 @@ function getModelDirectiveFromElement(element, throwOnMissing = true) { } if (element.getAttribute('name')) { const formElement = element.closest('form'); - if (formElement && ('model' in formElement.dataset)) { + if (formElement && 'model' in formElement.dataset) { const directives = parseDirectives(formElement.dataset.model || '*'); const directive = directives[0]; if (directive.args.length > 0 || directive.named.length > 0) { @@ -301,13 +301,13 @@ function cloneElementWithNewTagName(element, newTag) { const endRX = new RegExp(originalTag + '>$', 'i'); const startSubst = '<' + newTag; const endSubst = newTag + '>'; - const newHTML = element.outerHTML - .replace(startRX, startSubst) - .replace(endRX, endSubst); + const newHTML = element.outerHTML.replace(startRX, startSubst).replace(endRX, endSubst); return htmlToElement(newHTML); } function getElementAsTagText(element) { - return element.innerHTML ? element.outerHTML.slice(0, element.outerHTML.indexOf(element.innerHTML)) : element.outerHTML; + return element.innerHTML + ? element.outerHTML.slice(0, element.outerHTML.indexOf(element.innerHTML)) + : element.outerHTML; } const getMultipleCheckboxValue = function (element, currentValues) { const value = inputValue(element); @@ -346,7 +346,7 @@ const parseDeepData = function (data, propertyPath) { currentLevelData, finalData, finalKey, - parts + parts, }; }; function setDeepData(data, propertyPath, value) { @@ -1208,7 +1208,8 @@ function executeMorphdom(rootFromElement, rootToElement, modifiedFieldElements, if (fromEl === rootFromElement) { return true; } - if (!(fromEl instanceof HTMLElement || fromEl instanceof SVGElement) || !(toEl instanceof HTMLElement || toEl instanceof SVGElement)) { + if (!(fromEl instanceof HTMLElement || fromEl instanceof SVGElement) || + !(toEl instanceof HTMLElement || toEl instanceof SVGElement)) { return false; } const childComponent = childComponentMap.get(fromEl) || false; @@ -1234,7 +1235,7 @@ function executeMorphdom(rootFromElement, rootToElement, modifiedFieldElements, return true; } return !node.hasAttribute('data-live-ignore'); - } + }, }); } @@ -1682,10 +1683,10 @@ class BackendRequest { this.updatedModels = updateModels; } containsOneOfActions(targetedActions) { - return (this.actions.filter(action => targetedActions.includes(action))).length > 0; + return this.actions.filter((action) => targetedActions.includes(action)).length > 0; } areAnyModelsUpdated(targetedModels) { - return (this.updatedModels.filter(model => targetedModels.includes(model))).length > 0; + return this.updatedModels.filter((model) => targetedModels.includes(model)).length > 0; } } @@ -1701,11 +1702,12 @@ class Backend { const params = new URLSearchParams(queryString || ''); const fetchOptions = {}; fetchOptions.headers = { - 'Accept': 'application/vnd.live-component+html', + Accept: 'application/vnd.live-component+html', }; const hasFingerprints = Object.keys(childrenFingerprints).length > 0; const hasUpdatedModels = Object.keys(updatedModels).length > 0; - if (actions.length === 0 && this.willDataFitInUrl(JSON.stringify(data), params, JSON.stringify(childrenFingerprints))) { + if (actions.length === 0 && + this.willDataFitInUrl(JSON.stringify(data), params, JSON.stringify(childrenFingerprints))) { params.set('data', JSON.stringify(data)); if (hasFingerprints) { params.set('childrenFingerprints', JSON.stringify(childrenFingerprints)); @@ -2312,7 +2314,7 @@ class default_1 extends Controller { return this.element.dispatchEvent(new CustomEvent(name, { bubbles: canBubble, cancelable, - detail + detail, })); } } diff --git a/src/LiveComponent/tests/Fixtures/Kernel.php b/src/LiveComponent/tests/Fixtures/Kernel.php index 4af38d8a2b5..b630f76ca14 100644 --- a/src/LiveComponent/tests/Fixtures/Kernel.php +++ b/src/LiveComponent/tests/Fixtures/Kernel.php @@ -51,7 +51,6 @@ public function registerBundles(): iterable yield new FrameworkBundle(); yield new TwigBundle(); yield new DoctrineBundle(); - yield new ZenstruckFoundryBundle(); yield new TwigComponentBundle(); yield new LiveComponentBundle(); yield new ZenstruckFoundryBundle(); diff --git a/src/React/assets/dist/render_controller.js b/src/React/assets/dist/render_controller.js index 6009b0f7744..3d45be3c826 100644 --- a/src/React/assets/dist/render_controller.js +++ b/src/React/assets/dist/render_controller.js @@ -24,6 +24,9 @@ class default_1 extends Controller { connect() { const props = this.propsValue ? this.propsValue : null; this._dispatchEvent('react:connect', { component: this.componentValue, props: props }); + if (!this.componentValue) { + throw new Error('No component specified.'); + } const component = window.resolveReactComponent(this.componentValue); this._renderReactElement(React.createElement(component, props, null)); this._dispatchEvent('react:mount', {