Skip to content

Commit 8c46bf3

Browse files
committed
Add support for custom actions in TurboStream and TurboStreamResponse
1 parent bf3a0f0 commit 8c46bf3

File tree

4 files changed

+71
-0
lines changed

4 files changed

+71
-0
lines changed

src/Turbo/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# CHANGELOG
2+
## 2.22.0
3+
4+
- Add support for custom actions in `TurboStream` and `TurboStreamResponse`
25

36
## 2.22.0
47

src/Turbo/src/Helper/TurboStream.php

+28
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,34 @@ public static function refresh(?string $requestId = null): string
8686
return \sprintf('<turbo-stream action="refresh" request-id="%s"></turbo-stream>', htmlspecialchars($requestId));
8787
}
8888

89+
/**
90+
* Custom action and attributes.
91+
*
92+
* Set boolean attributes (e.g., `disabled`) by providing the attribute name as key with `null` as value.
93+
*
94+
* @param array<string, string|int|float|null> $attr
95+
*/
96+
public static function action(string $action, string $target, string $html, array $attr = []): string
97+
{
98+
if (\array_key_exists('action', $attr) || \array_key_exists('targets', $attr)) {
99+
throw new \InvalidArgumentException('The "action" and "targets" attributes are reserved and cannot be used.');
100+
}
101+
102+
$attrString = '';
103+
foreach ($attr as $key => $value) {
104+
$key = htmlspecialchars($key);
105+
if (null === $value) {
106+
$attrString .= \sprintf(' %s', $key);
107+
} elseif (\is_int($value) || \is_float($value)) {
108+
$attrString .= \sprintf(' %s="%s"', $key, $value);
109+
} else {
110+
$attrString .= \sprintf(' %s="%s"', $key, htmlspecialchars($value));
111+
}
112+
}
113+
114+
return self::wrap(htmlspecialchars($action), $target, $html, $attrString);
115+
}
116+
89117
private static function wrap(string $action, string $target, string $html, string $attr = ''): string
90118
{
91119
return \sprintf(<<<EOHTML

src/Turbo/src/TurboStreamResponse.php

+12
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,16 @@ public function refresh(?string $requestId = null): static
104104

105105
return $this;
106106
}
107+
108+
/**
109+
* @param array<string, string|int|float|null> $attr
110+
*
111+
* @return $this
112+
*/
113+
public function action(string $action, string $target, string $html, array $attr = []): static
114+
{
115+
$this->setContent($this->getContent().TurboStream::action($action, $target, $html, $attr));
116+
117+
return $this;
118+
}
107119
}

src/Turbo/tests/Helper/TurboStreamTest.php

+28
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,32 @@ public function testRefreshWithId(): void
7676
TurboStream::refresh('a"b')
7777
);
7878
}
79+
80+
public function testCustom(): void
81+
{
82+
$this->assertSame(<<<EOHTML
83+
<turbo-stream action="customAction" targets="some[&quot;selector&quot;]" someAttr="someValue" boolAttr intAttr="0" floatAttr="3.14">
84+
<template><div>content</div></template>
85+
</turbo-stream>
86+
EOHTML,
87+
TurboStream::action('customAction', 'some["selector"]', '<div>content</div>', ['someAttr' => 'someValue', 'boolAttr' => null, 'intAttr' => 0, 'floatAttr' => 3.14])
88+
);
89+
}
90+
91+
/**
92+
* @dataProvider customThrowsExceptionDataProvider
93+
*
94+
* @param array<string, string|int|float|null> $attr
95+
*/
96+
public function testCustomThrowsException(string $action, string $target, string $html, array $attr): void
97+
{
98+
$this->expectException(\InvalidArgumentException::class);
99+
TurboStream::action($action, $target, $html, $attr);
100+
}
101+
102+
public static function customThrowsExceptionDataProvider(): \Generator
103+
{
104+
yield ['customAction', 'some["selector"]', '<div>content</div>', ['action' => 'someAction']];
105+
yield ['customAction', 'some["selector"]', '<div>content</div>', ['targets' => 'someTargets']];
106+
}
79107
}

0 commit comments

Comments
 (0)