Skip to content

Commit 5503274

Browse files
committed
[Map] Rework SvgIcon and factory method name, UX Icon to get generated HTML, and icons rendering
1 parent 819542b commit 5503274

10 files changed

+120
-43
lines changed

assets/dist/abstract_map_controller.d.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,23 @@ export type WithIdentifier<T extends Record<string, unknown>> = T & {
99
};
1010
export declare const IconTypes: {
1111
readonly Url: "url";
12-
readonly InlineSvg: "inline-svg";
12+
readonly Svg: "svg";
1313
readonly UxIcon: "ux-icon";
1414
};
15-
export type IconType = (typeof IconTypes)[keyof typeof IconTypes];
1615
export type Icon = {
17-
content: string;
18-
type: IconType;
1916
width: number;
2017
height: number;
21-
};
18+
} & ({
19+
type: typeof IconTypes.UxIcon;
20+
name: string;
21+
_generated_html: string;
22+
} | {
23+
type: typeof IconTypes.Url;
24+
url: string;
25+
} | {
26+
type: typeof IconTypes.Svg;
27+
html: string;
28+
});
2229
export type MarkerDefinition<MarkerOptions, InfoWindowOptions> = WithIdentifier<{
2330
position: Point;
2431
title: string | null;

assets/dist/abstract_map_controller.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Controller } from '@hotwired/stimulus';
22

33
const IconTypes = {
44
Url: 'url',
5-
InlineSvg: 'inline-svg',
5+
Svg: 'svg',
66
UxIcon: 'ux-icon',
77
};
88
class default_1 extends Controller {

assets/src/abstract_map_controller.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,27 @@ export type WithIdentifier<T extends Record<string, unknown>> = T & { '@id': Ide
66

77
export const IconTypes = {
88
Url: 'url',
9-
InlineSvg: 'inline-svg',
9+
Svg: 'svg',
1010
UxIcon: 'ux-icon',
1111
} as const;
12-
export type IconType = (typeof IconTypes)[keyof typeof IconTypes];
1312
export type Icon = {
14-
content: string;
15-
type: IconType;
1613
width: number;
1714
height: number;
18-
};
15+
} & (
16+
| {
17+
type: typeof IconTypes.UxIcon;
18+
name: string;
19+
_generated_html: string,
20+
}
21+
| {
22+
type: typeof IconTypes.Url;
23+
url: string;
24+
}
25+
| {
26+
type: typeof IconTypes.Svg;
27+
html: string;
28+
}
29+
);
1930

2031
export type MarkerDefinition<MarkerOptions, InfoWindowOptions> = WithIdentifier<{
2132
position: Point;

doc/index.rst

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,12 @@ Add Marker icons
141141

142142
A ``Marker`` can be customized with an ``Icon`` instance, which can either be an UX Icon, an URL, or a SVG content::
143143

144-
// It can be a UX Icon (requires `symfony/ux-icons` package)
145-
$icon = Icon::ux('fa:map-marker');
146-
// Or an URL pointing to an image
147-
$icon = Icon::url('https://example.com/marker.png');
148-
// Or a plain SVG content
149-
$icon = Icon::svg('<svg>(...)</svg>');
150-
151-
// Configure the icon size with the `width()` and `height()` methods
152-
$icon = Icon::ux('fa:map-marker')->width(48)->height(48);
144+
// It can be a UX Icon (requires `symfony/ux-icons` package)...
145+
$icon = Icon::ux('fa:map-marker')->width(24)->height(24);
146+
// ... or an URL pointing to an image
147+
$icon = Icon::url('https://example.com/marker.png')->width(24)->height(24);
148+
// ... or a plain SVG string
149+
$icon = Icon::svg('<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">...</svg>');
153150

154151
$map->addMarker(new Marker(
155152
// ...

src/Icon/Icon.php

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
abstract class Icon
2323
{
2424
/**
25+
* Creates a new icon based on a URL (e.g.: `https://cdn.jsdelivr.net/npm/[email protected]/icons/geo-alt.svg`).
26+
*
2527
* @param non-empty-string $url
2628
*/
2729
public static function url(string $url): UrlIcon
@@ -30,6 +32,10 @@ public static function url(string $url): UrlIcon
3032
}
3133

3234
/**
35+
* Creates a new icon based on an SVG string (e.g.: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">...</svg>`).
36+
* Using an SVG string may not be the best option if you want to customize the icon afterward,
37+
* it would be preferable to use {@see Icon::ux()} or {@see Icon::url()} instead.
38+
*
3339
* @param non-empty-string $html
3440
*/
3541
public static function svg(string $html): SvgIcon
@@ -38,6 +44,8 @@ public static function svg(string $html): SvgIcon
3844
}
3945

4046
/**
47+
* Creates a new icon based on a UX icon name (e.g.: `fa:map-marker`).
48+
*
4149
* @param non-empty-string $name
4250
*/
4351
public static function ux(string $name): UxIcon
@@ -56,23 +64,25 @@ protected function __construct(
5664
) {
5765
}
5866

67+
/**
68+
* Sets the width of the icon.
69+
*
70+
* @param positive-int $width
71+
*/
5972
public function width(int $width): static
6073
{
61-
if ($width <= 0) {
62-
throw new InvalidArgumentException('Width must be greater than 0.');
63-
}
64-
6574
$this->width = $width;
6675

6776
return $this;
6877
}
6978

79+
/**
80+
* Sets the height of the icon.
81+
*
82+
* @param positive-int $height
83+
*/
7084
public function height(int $height): static
7185
{
72-
if ($height <= 0) {
73-
throw new InvalidArgumentException('Height must be greater than 0.');
74-
}
75-
7686
$this->height = $height;
7787

7888
return $this;

src/Icon/SvgIcon.php

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,39 @@ class SvgIcon extends Icon
2323
{
2424
/**
2525
* @param non-empty-string $html
26-
* @param positive-int $width
27-
* @param positive-int $height
2826
*/
2927
protected function __construct(
3028
protected string $html,
31-
int $width = 24,
32-
int $height = 24,
3329
) {
34-
parent::__construct(IconType::Svg, $width, $height);
30+
parent::__construct(IconType::Svg);
3531
}
3632

3733
/**
38-
* @param array{ html: string, width: positive-int, height: positive-int } $data
34+
* @param array{ html: string } $data
3935
*/
4036
public static function fromArray(array $data): static
4137
{
4238
return new self(
4339
html: $data['html'],
44-
width: $data['width'],
45-
height: $data['height'],
4640
);
4741
}
4842

43+
/**
44+
* @throws \LogicException the SvgIcon can not be customized
45+
*/
46+
public function width(int $width): never
47+
{
48+
throw new \LogicException('Unable to configure the SvgIcon width, please configure it in the HTML with the "width" attribute on the root element instead.');
49+
}
50+
51+
/**
52+
* @throws \LogicException the SvgIcon can not be customized
53+
*/
54+
public function height(int $height): never
55+
{
56+
throw new \LogicException('Unable to configure the SvgIcon height, please configure it in the HTML with the "height" attribute on the root element instead.');
57+
}
58+
4959
public function toArray(): array
5060
{
5161
return [

src/Icon/UxIconRenderer.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,18 @@ public function __construct(
2525
) {
2626
}
2727

28-
public function render(string $name): string
28+
/**
29+
* @param array<string, string|bool> $attributes
30+
*/
31+
public function render(string $name, array $attributes = []): string
2932
{
3033
if (null === $this->renderer) {
3134
throw new \LogicException('You cannot use an UX Icon as the "UX Icons" package is not installed. Try running "composer require symfony/ux-icons" to install it.');
3235
}
3336

3437
return $this->renderer->renderIcon($name, [
3538
'xmlns' => 'http://www.w3.org/2000/svg',
39+
...$attributes,
3640
]);
3741
}
3842
}

src/Marker.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\UX\Map\Exception\InvalidArgumentException;
1515
use Symfony\UX\Map\Icon\Icon;
16+
use Symfony\UX\Map\Icon\IconType;
1617

1718
/**
1819
* Represents a marker on a map.
@@ -40,7 +41,7 @@ public function __construct(
4041
* position: array{lat: float, lng: float},
4142
* title: string|null,
4243
* infoWindow: array<string, mixed>|null,
43-
* icon: Icon|null,
44+
* icon: array{type: value-of<IconType>, width: positive-int, height: positive-int, ...}|null,
4445
* extra: array,
4546
* id: string|null
4647
* }
@@ -62,7 +63,7 @@ public function toArray(): array
6263
* position: array{lat: float, lng: float},
6364
* title: string|null,
6465
* infoWindow: array<string, mixed>|null,
65-
* icon: array<string, mixed>||null,
66+
* icon: array{type: value-of<IconType>, width: positive-int, height: positive-int, ...}|null,
6667
* extra: array,
6768
* id: string|null
6869
* } $marker

src/Renderer/AbstractRenderer.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\UX\Map\Renderer;
1313

14+
use Symfony\UX\Map\Icon\IconType;
1415
use Symfony\UX\Map\Icon\UxIconRenderer;
1516
use Symfony\UX\Map\Map;
1617
use Symfony\UX\Map\MapOptionsInterface;
@@ -23,7 +24,7 @@
2324
{
2425
public function __construct(
2526
private StimulusHelper $stimulus,
26-
private ?UxIconRenderer $uxIconRenderer = null,
27+
private UxIconRenderer $uxIconRenderer,
2728
) {
2829
}
2930

@@ -92,9 +93,11 @@ private function getMapAttributes(Map $map): array
9293

9394
foreach ($attrs['markers'] as $key => $marker) {
9495
$attrs['markers'][$key]['@id'] = $computeId($marker);
95-
if (null !== $this->uxIconRenderer && null !== $marker['icon'] && 'ux-icon' === $marker['icon']['type']) {
96-
$attrs['markers'][$key]['icon']['content'] = $this->uxIconRenderer->render($marker['icon']['content']);
97-
$attrs['markers'][$key]['icon']['type'] = 'inline-svg';
96+
if (isset($marker['icon']['type']) && IconType::UxIcon->value === $marker['icon']['type']) {
97+
$attrs['markers'][$key]['icon']['_generated_html'] = $this->uxIconRenderer->render($marker['icon']['name'], [
98+
'width' => $marker['icon']['width'],
99+
'height' => $marker['icon']['height'],
100+
]);
98101
}
99102
}
100103
foreach ($attrs['polygons'] as $key => $polygon) {

tests/IconTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,38 @@ public function testFromArray(Icon $icon, string $expectedInstance, array $expec
6363
{
6464
self::assertEquals($icon, Icon::fromArray($expectedToArray));
6565
}
66+
67+
public static function dataProviderForTestSvgIconCustomizationMethodsCanNotBeCalled(): iterable
68+
{
69+
$refl = new \ReflectionClass(SvgIcon::class);
70+
$customizationMethods = array_diff(
71+
array_map(
72+
fn (\ReflectionMethod $method) => $method->name,
73+
array_filter($refl->getMethods(\ReflectionMethod::IS_PUBLIC), fn (\ReflectionMethod $method) => SvgIcon::class === $method->getDeclaringClass()->getName())
74+
),
75+
['toArray', 'fromArray']
76+
);
77+
78+
foreach ($customizationMethods as $method) {
79+
if (\in_array($method, ['width', 'height'], true)) {
80+
yield $method => [$method, 12];
81+
} elseif (\in_array($method, $customizationMethods, true)) {
82+
throw new \LogicException(\sprintf('The "%s" method is not supposed to be called on the SvgIcon, please modify the test provider.', $method));
83+
}
84+
}
85+
}
86+
87+
/**
88+
* @dataProvider dataProviderForTestSvgIconCustomizationMethodsCanNotBeCalled
89+
*/
90+
public function testSvgIconCustomizationMethodsCanNotBeCalled(string $method, mixed ...$args): void
91+
{
92+
$this->expectException(\LogicException::class);
93+
if (\in_array($method, ['width', 'height'], true)) {
94+
$this->expectExceptionMessage(\sprintf('Unable to configure the SvgIcon %s, please configure it in the HTML with the "%s" attribute on the root element instead.', $method, $method));
95+
}
96+
97+
$icon = Icon::svg('<svg></svg>');
98+
$icon->{$method}(...$args);
99+
}
66100
}

0 commit comments

Comments
 (0)