Skip to content

Commit 0f4e70e

Browse files
committed
Merge branch '2.x' into fix-expanded-choices
2 parents a17870f + 112ed03 commit 0f4e70e

File tree

40 files changed

+559
-210
lines changed

40 files changed

+559
-210
lines changed

.github/workflows/test.yaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@ jobs:
99
- uses: actions/checkout@master
1010
- uses: shivammathur/setup-php@v2
1111
with:
12-
php-version: '7.4'
12+
php-version: '8.0'
13+
tools: php-cs-fixer, cs2pr
1314
- name: php-cs-fixer
14-
run: |
15-
wget https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v2.18.2/php-cs-fixer.phar -q
16-
php php-cs-fixer.phar fix --dry-run --diff
15+
run: php-cs-fixer fix --dry-run --format=checkstyle | cs2pr
1716

1817
coding-style-js:
1918
runs-on: ubuntu-latest

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.php_cs.cache
1+
.php-cs-fixer.cache
22
node_modules
33
yarn.lock
44
yarn-error.log

.php_cs.dist renamed to .php-cs-fixer.dist.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
exit(0);
55
}
66

7-
return PhpCsFixer\Config::create()
7+
return (new PhpCsFixer\Config())
88
->setRules([
99
'@Symfony' => true,
1010
'@Symfony:risky' => true,

src/Chartjs/Resources/assets/src/controller.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { Controller } from '@hotwired/stimulus';
1313
import Chart from 'chart.js/auto';
1414

1515
export default class extends Controller {
16+
readonly viewValue: any;
17+
1618
static values = {
1719
view: Object,
1820
};

src/Cropperjs/Resources/assets/src/controller.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import Cropper from 'cropperjs';
1414
import CropEvent = Cropper.CropEvent;
1515

1616
export default class CropperController extends Controller {
17+
readonly publicUrlValue: string;
18+
readonly optionsValue: object;
19+
1720
static values = {
1821
publicUrl: String,
1922
options: Object,

src/Dropzone/Resources/assets/src/controller.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
import { Controller } from '@hotwired/stimulus';
1313

1414
export default class extends Controller {
15+
readonly inputTarget: HTMLInputElement;
16+
readonly placeholderTarget: HTMLDivElement;
17+
readonly previewTarget: HTMLDivElement;
18+
readonly previewClearButtonTarget: HTMLButtonElement;
19+
readonly previewFilenameTarget: HTMLDivElement;
20+
readonly previewImageTarget: HTMLDivElement;
21+
1522
static targets = ['input', 'placeholder', 'preview', 'previewClearButton', 'previewFilename', 'previewImage'];
1623

1724
connect() {
@@ -39,7 +46,7 @@ export default class extends Controller {
3946
this._dispatchEvent('dropzone:clear');
4047
}
4148

42-
onInputChange(event) {
49+
onInputChange(event: any) {
4350
const file = event.target.files[0];
4451
if (typeof file === 'undefined') {
4552
return;
@@ -62,23 +69,23 @@ export default class extends Controller {
6269
this._dispatchEvent('dropzone:change', file);
6370
}
6471

65-
_populateImagePreview(file) {
72+
_populateImagePreview(file: Blob) {
6673
if (typeof FileReader === 'undefined') {
6774
// FileReader API not available, skip
6875
return;
6976
}
7077

7178
const reader = new FileReader();
7279

73-
reader.addEventListener('load', (event) => {
80+
reader.addEventListener('load', (event: any) => {
7481
this.previewImageTarget.style.display = 'block';
7582
this.previewImageTarget.style.backgroundImage = 'url("' + event.target.result + '")';
7683
});
7784

7885
reader.readAsDataURL(file);
7986
}
8087

81-
_dispatchEvent(name: string, payload: any) {
88+
_dispatchEvent(name: string, payload: any = {}) {
8289
this.element.dispatchEvent(new CustomEvent(name, { detail: payload }));
8390
}
8491
}

src/LazyImage/Resources/assets/src/controller.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,25 @@
1212
import { Controller } from '@hotwired/stimulus';
1313

1414
export default class extends Controller {
15+
readonly srcValue: string;
16+
readonly srcsetValue: any;
17+
readonly hasSrcsetValue: boolean;
18+
1519
static values = {
1620
src: String,
1721
srcset: Object,
1822
};
1923

2024
connect() {
2125
const hd = new Image();
26+
const element = this.element as HTMLImageElement;
2227

2328
const srcsetString = this._calculateSrcsetString();
2429

2530
hd.addEventListener('load', () => {
26-
this.element.src = this.srcValue;
31+
element.src = this.srcValue;
2732
if (srcsetString) {
28-
this.element.srcset = srcsetString;
33+
element.srcset = srcsetString;
2934
}
3035
this._dispatchEvent('lazy-image:ready', { image: hd });
3136
});
@@ -43,7 +48,7 @@ export default class extends Controller {
4348
return '';
4449
}
4550

46-
const sets = Object.keys(this.srcsetValue).map((size) => {
51+
const sets = Object.keys(this.srcsetValue).map((size: string) => {
4752
return `${this.srcsetValue[size]} ${size}`;
4853
});
4954

src/LazyImage/Resources/doc/index.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ There is also support for the ``srcset`` attribute by passing an
6969
}) }}
7070
/>
7171
72-
**Note** The ``stimulus_controller()`` function comes from
73-
`WebpackEncoreBundle v1.10`_.
72+
.. note::
73+
74+
The ``stimulus_controller()`` function comes from `WebpackEncoreBundle v1.10`_.
7475

7576
Instead of using a generated thumbnail that would exist on your
7677
filesystem, you can use the BlurHash algorithm to create a light,

src/LiveComponent/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# CHANGELOG
22

3+
## 2.1.0
4+
5+
- The Live Component AJAX endpoints now return HTML in all situations
6+
instead of JSON.
7+
8+
- Send live action arguments to backend
9+
310
## 2.0.0
411

512
- Support for `stimulus` version 2 was removed and support for `@hotwired/stimulus`

src/LiveComponent/assets/dist/live_controller.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,7 @@ class default_1 extends Controller {
10881088
directives.forEach((directive) => {
10891089
const _executeAction = () => {
10901090
this._clearWaitingDebouncedRenders();
1091-
this._makeRequest(directive.action);
1091+
this._makeRequest(directive.action, directive.named);
10921092
};
10931093
let handled = false;
10941094
directive.modifiers.forEach((modifier) => {
@@ -1192,11 +1192,14 @@ class default_1 extends Controller {
11921192
}, this.debounceValue || DEFAULT_DEBOUNCE);
11931193
}
11941194
}
1195-
_makeRequest(action) {
1195+
_makeRequest(action, args) {
11961196
const splitUrl = this.urlValue.split('?');
11971197
let [url] = splitUrl;
11981198
const [, queryString] = splitUrl;
11991199
const params = new URLSearchParams(queryString || '');
1200+
if (typeof args === 'object' && Object.keys(args).length > 0) {
1201+
params.set('args', new URLSearchParams(args).toString());
1202+
}
12001203
const fetchOptions = {};
12011204
fetchOptions.headers = {
12021205
'Accept': 'application/vnd.live-component+json',

src/LiveComponent/assets/src/live_controller.ts

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,6 @@ import { buildFormData, buildSearchParams } from './http_data_helper';
66
import { setDeepData, doesDeepPropertyExist, normalizeModelName } from './set_deep_data';
77
import { haveRenderedValuesChanged } from './have_rendered_values_changed';
88

9-
interface LiveResponseData {
10-
redirect_url?: string,
11-
html?: string,
12-
data?: any,
13-
}
14-
159
interface ElementLoadingDirectives {
1610
element: HTMLElement,
1711
directives: Directive[]
@@ -152,7 +146,7 @@ export default class extends Controller {
152146
// taking precedence
153147
this._clearWaitingDebouncedRenders();
154148

155-
this._makeRequest(directive.action);
149+
this._makeRequest(directive.action, directive.named);
156150
}
157151

158152
let handled = false;
@@ -203,20 +197,8 @@ export default class extends Controller {
203197
this._makeRequest(null);
204198
}
205199

206-
_getValueFromElement(element: HTMLElement){
207-
const value = element.dataset.value || element.value;
208-
209-
if (!value) {
210-
const clonedElement = (element.cloneNode());
211-
// helps typescript know this is an HTMLElement
212-
if (!(clonedElement instanceof HTMLElement)) {
213-
throw new Error('cloneNode() produced incorrect type');
214-
}
215-
216-
throw new Error(`The update() method could not be called for "${clonedElement.outerHTML}": the element must either have a "data-value" or "value" attribute set.`);
217-
}
218-
219-
return value;
200+
_getValueFromElement(element: HTMLElement) {
201+
return element.dataset.value || (element as any).value;
220202
}
221203

222204
_updateModelFromElement(element: HTMLElement, value: string, shouldRender: boolean) {
@@ -322,15 +304,19 @@ export default class extends Controller {
322304
}
323305
}
324306

325-
_makeRequest(action: string|null) {
307+
_makeRequest(action: string|null, args: Record<string,unknown>) {
326308
const splitUrl = this.urlValue.split('?');
327309
let [url] = splitUrl
328310
const [, queryString] = splitUrl;
329311
const params = new URLSearchParams(queryString || '');
330312

313+
if (typeof args === 'object' && Object.keys(args).length > 0) {
314+
params.set('args', new URLSearchParams(args).toString());
315+
}
316+
331317
const fetchOptions: RequestInit = {};
332318
fetchOptions.headers = {
333-
'Accept': 'application/vnd.live-component+json',
319+
'Accept': 'application/vnd.live-component+html',
334320
};
335321

336322
if (action) {
@@ -361,8 +347,8 @@ export default class extends Controller {
361347

362348
const isMostRecent = this.renderPromiseStack.removePromise(thisPromise);
363349
if (isMostRecent) {
364-
response.json().then((data) => {
365-
this._processRerender(data)
350+
response.text().then((html) => {
351+
this._processRerender(html, response);
366352
});
367353
}
368354
})
@@ -373,24 +359,24 @@ export default class extends Controller {
373359
*
374360
* @private
375361
*/
376-
_processRerender(data: LiveResponseData) {
362+
_processRerender(html: string, response: Response) {
377363
// check if the page is navigating away
378364
if (this.isWindowUnloaded) {
379365
return;
380366
}
381367

382-
if (data.redirect_url) {
368+
if (response.headers.get('Location')) {
383369
// action returned a redirect
384370
if (typeof Turbo !== 'undefined') {
385-
Turbo.visit(data.redirect_url);
371+
Turbo.visit(response.headers.get('Location'));
386372
} else {
387-
window.location.href = data.redirect_url;
373+
window.location.href = response.headers.get('Location') || '';
388374
}
389375

390376
return;
391377
}
392378

393-
if (!this._dispatchEvent('live:render', data, true, true)) {
379+
if (!this._dispatchEvent('live:render', html, true, true)) {
394380
// preventDefault() was called
395381
return;
396382
}
@@ -400,15 +386,8 @@ export default class extends Controller {
400386
// elements to appear different unnecessarily
401387
this._onLoadingFinish();
402388

403-
if (!data.html) {
404-
throw new Error('Missing html key on response JSON');
405-
}
406-
407389
// merge/patch in the new HTML
408-
this._executeMorphdom(data.html);
409-
410-
// "data" holds the new, updated data
411-
this.dataValue = data.data;
390+
this._executeMorphdom(html);
412391
}
413392

414393
_clearWaitingDebouncedRenders() {
@@ -640,7 +619,7 @@ export default class extends Controller {
640619
let callback: () => void;
641620
if (actionName.charAt(0) === '$') {
642621
callback = () => {
643-
this[actionName]();
622+
(this as any)[actionName]();
644623
}
645624
} else {
646625
callback = () => {
@@ -654,7 +633,7 @@ export default class extends Controller {
654633
this.pollingIntervals.push(timer);
655634
}
656635

657-
_dispatchEvent(name: string, payload: object | null = null, canBubble = true, cancelable = false) {
636+
_dispatchEvent(name: string, payload: object | string | null = null, canBubble = true, cancelable = false) {
658637
return this.element.dispatchEvent(new CustomEvent(name, {
659638
bubbles: canBubble,
660639
cancelable,

src/LiveComponent/assets/test/controller/action.test.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ describe('LiveController Action Tests', () => {
3535
data-action="live#action"
3636
data-action-name="save"
3737
>Save</button>
38+
39+
<button data-action="live#action" data-action-name="sendNamedArgs(a=1, b=2, c=3)">Send named args</button>
3840
</div>
3941
`;
4042

@@ -48,10 +50,10 @@ describe('LiveController Action Tests', () => {
4850
const { element } = await startStimulus(template(data));
4951

5052
// ONLY a post is sent, not a re-render GET
51-
const postMock = fetchMock.postOnce('http://localhost/_components/my_component/save', {
52-
html: template({ comments: 'hi weaver', isSaved: true }),
53-
data: { comments: 'hi weaver', isSaved: true }
54-
});
53+
const postMock = fetchMock.postOnce(
54+
'http://localhost/_components/my_component/save',
55+
template({ comments: 'hi weaver', isSaved: true })
56+
);
5557

5658
await userEvent.type(getByLabelText(element, 'Comments:'), ' WEAVER');
5759

@@ -64,4 +66,15 @@ describe('LiveController Action Tests', () => {
6466

6567
expect(postMock.lastOptions().body.get('comments')).toEqual('hi WEAVER');
6668
});
69+
70+
it('Sends action named args', async () => {
71+
const data = { comments: 'hi' };
72+
const { element } = await startStimulus(template(data));
73+
74+
fetchMock.postOnce('http://localhost/_components/my_component/sendNamedArgs?values=a%3D1%26b%3D2%26c%3D3', {
75+
html: template({ comments: 'hi' }),
76+
});
77+
78+
getByText(element, 'Send named args').click();
79+
});
6780
});

src/LiveComponent/assets/test/controller/child.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ describe('LiveController parent -> child component tests', () => {
133133
const inputElement = getByLabelText(element, 'Content:');
134134
await userEvent.clear(inputElement);
135135
await userEvent.type(inputElement, 'changed content');
136+
// change the rows on the server
136137
mockRerender({'value': 'changed content'}, childTemplate, (data) => {
137138
data.rows = 5;
138139
});

src/LiveComponent/assets/test/controller/csrf.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ describe('LiveController CSRF Tests', () => {
4747
const data = { comments: 'hi' };
4848
const { element } = await startStimulus(template(data));
4949

50-
const postMock = fetchMock.postOnce('http://localhost/_components/my_component/save', {
51-
html: template({ comments: 'hi', isSaved: true }),
52-
data: { comments: 'hi', isSaved: true }
53-
});
50+
const postMock = fetchMock.postOnce(
51+
'http://localhost/_components/my_component/save',
52+
template({ comments: 'hi', isSaved: true })
53+
);
5454
getByText(element, 'Save').click();
5555

5656
await waitFor(() => expect(element).toHaveTextContent('Comment Saved!'));

0 commit comments

Comments
 (0)