Skip to content

Commit 64558e9

Browse files
authored
Add custom attributes support and clear placeholder content (#6)
- Clear placeholder content before mounting (innerHTML = '') - Add support for custom HTML attributes on wrapper elements - Support both static strings and dynamic Arizona templates - Add comprehensive wrapper element documentation to README - Explain DOM structure and why wrapper is necessary - Show layout problems and solutions with visual examples - Document display: contents for layout transparency - Update all Erlang module docs with examples - Enhance JavaScript JSDoc with DOM structure examples - Add Common Test suite with organized groups - Test static attributes (class, id, etc.) - Test dynamic attributes (Arizona templates) - All tests passing (3/3) - README.md: +125 lines (wrapper docs, layout examples) - arizona-svelte-lifecycle.js: Enhanced mountComponents() docs - arizona_svelte.erl: Added render_component/3 with full docs - arizona_svelte_template.erl: Added attributes() type docs - arizona_svelte_components.erl: Documented wrapper purpose - arizona_svelte_SUITE.erl: +86 lines (2 new test groups)
1 parent c3663a0 commit 64558e9

File tree

10 files changed

+385
-60
lines changed

10 files changed

+385
-60
lines changed

README.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,130 @@ arizonaSvelte.startMonitoring();
168168
</div>
169169
```
170170

171+
## Understanding Wrapper Elements
172+
173+
Components are rendered inside a wrapper `<div>` element that serves as the mount
174+
target for Svelte components. This wrapper is necessary for the JavaScript side
175+
to discover and manage component lifecycle.
176+
177+
### DOM Structure Example
178+
179+
When you render a component:
180+
181+
```erlang
182+
arizona_svelte:render_component("Counter", #{initialCount => 5})
183+
```
184+
185+
The resulting DOM structure is:
186+
187+
```html
188+
<!-- Wrapper element (created by Arizona) -->
189+
<div
190+
data-svelte-component="Counter"
191+
data-svelte-props='{"initialCount":5}'
192+
data-arizona-update="false"
193+
>
194+
<!-- Component content (rendered by Svelte) -->
195+
<div class="counter">
196+
<h2>Count: 5</h2>
197+
<button>Increment</button>
198+
</div>
199+
</div>
200+
```
201+
202+
**Why the wrapper exists:**
203+
204+
- `data-svelte-component` - JavaScript identifies which component to mount
205+
- `data-svelte-props` - Props are passed to the component
206+
- `data-arizona-update="false"` - Prevents Arizona from interfering with Svelte's
207+
DOM management
208+
- Provides a stable mount target for component lifecycle management
209+
210+
### Customizing Wrapper Elements
211+
212+
You can add custom HTML attributes to the wrapper for styling and layout control.
213+
214+
### Basic Usage
215+
216+
```erlang
217+
% Simple string attributes
218+
arizona_svelte:render_component("Card", #{}, ~"class=\"flex-1 p-4\" id=\"main-card\"")
219+
```
220+
221+
### Dynamic Attributes with Arizona Templates
222+
223+
```erlang
224+
% Conditional attributes based on bindings
225+
arizona_svelte:render_component("Widget", #{}, arizona_template:from_string(~"""
226+
class="widget"
227+
{case arizona_template:get_binding(hidden, Bindings) of
228+
true -> ~"hidden";
229+
false -> ~""
230+
end}
231+
"""))
232+
```
233+
234+
### Layout: Making Wrappers Transparent
235+
236+
The wrapper div can interfere with flexbox/grid layouts:
237+
238+
```erlang
239+
% Without custom attributes - wrapper breaks layout
240+
<div class="flex gap-4">
241+
{arizona_svelte:render_component("Card", #{})}
242+
{arizona_svelte:render_component("Card", #{})}
243+
</div>
244+
```
245+
246+
**Problem:** The wrapper `<div>` becomes the flex child, not the Card component itself.
247+
248+
```html
249+
<!-- Resulting DOM - wrapper is the flex child -->
250+
<div class="flex gap-4">
251+
<div data-svelte-component="Card"> <!-- This is the flex child -->
252+
<div class="card">...</div> <!-- Card content is nested -->
253+
</div>
254+
<div data-svelte-component="Card">
255+
<div class="card">...</div>
256+
</div>
257+
</div>
258+
```
259+
260+
**Solution:** Use `display: contents` to make the wrapper transparent to layout:
261+
262+
```erlang
263+
% Wrapper becomes invisible - parent sees Card's children directly
264+
<div class="flex gap-4">
265+
{arizona_svelte:render_component("Card", #{}, ~"style=\"display: contents\"")}
266+
{arizona_svelte:render_component("Card", #{}, ~"style=\"display: contents\"")}
267+
</div>
268+
```
269+
270+
```html
271+
<!-- Resulting behavior - Card's root is the flex child -->
272+
<div class="flex gap-4">
273+
<div data-svelte-component="Card" style="display: contents">
274+
<div class="card">...</div> <!-- This behaves as the flex child -->
275+
</div>
276+
<div data-svelte-component="Card" style="display: contents">
277+
<div class="card">...</div>
278+
</div>
279+
</div>
280+
```
281+
282+
**Other layout options:**
283+
284+
```erlang
285+
% Add flex/grid classes to the wrapper itself
286+
arizona_svelte:render_component("Card", #{}, ~"class=\"flex-1\"")
287+
arizona_svelte:render_component("GridItem", #{}, ~"class=\"col-span-2\"")
288+
```
289+
290+
**Note:** `display: contents` has
291+
[accessibility limitations](https://caniuse.com/css-display-contents) with
292+
buttons and certain ARIA roles. Test with screen readers if accessibility is
293+
critical.
294+
171295
## Features
172296

173297
- **🔄 Automatic Lifecycle Management**: Components mount/unmount automatically
@@ -177,6 +301,7 @@ when DOM changes
177301
- **🎯 Simple Setup**: Register components and start monitoring in a few lines
178302
- **🧪 Development Friendly**: Built-in logging and debugging support
179303
- **⚡ High Performance**: Zero-debounce monitoring for immediate responsiveness
304+
- **🎨 Customizable Wrappers**: Add classes, IDs, styles, and dynamic attributes
180305

181306
## Important: State Independence
182307

assets/js/arizona-svelte-lifecycle.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,19 @@ class ArizonaSvelteLifecycle {
4949

5050
/**
5151
* Mount Svelte components from DOM data attributes
52-
* Searches for elements with data-svelte-component attribute and mounts the corresponding components
52+
* Searches for elements with data-svelte-component attribute and mounts the corresponding components.
53+
* Clears any placeholder content in the wrapper before mounting.
54+
*
55+
* The wrapper element (target) is preserved and serves as the mount container.
56+
* Components are mounted inside the wrapper, not replacing it.
57+
*
5358
* @returns {Promise<number>} Number of components successfully mounted
5459
* @example
5560
* // HTML: <div data-svelte-component="Counter" data-svelte-props='{"count": 0}'></div>
5661
* const mounted = await lifecycle.mountComponents();
62+
* // Result: <div data-svelte-component="Counter" data-svelte-props='{"count": 0}'>
63+
* // <div class="counter">...</div> <!-- Component rendered here -->
64+
* // </div>
5765
*/
5866
async mountComponents() {
5967
const svelteTargets = document.querySelectorAll('[data-svelte-component]');
@@ -72,6 +80,11 @@ class ArizonaSvelteLifecycle {
7280

7381
if (ComponentClass) {
7482
try {
83+
// Clear any placeholder content before mounting.
84+
// This removes server-rendered loading states or placeholders
85+
// while preserving the wrapper element and its attributes.
86+
target.innerHTML = '';
87+
7588
const instance = mount(ComponentClass, { target, props });
7689
this.mountedComponents.set(target, instance);
7790
mountedCount++;

elvis.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
filter => "*.erl",
2121
ruleset => erl_files_strict,
2222
rules => [
23+
{elvis_style, dont_repeat_yourself, disable},
2324
{elvis_style, atom_naming_convention, #{
2425
regex => "^[a-z][a-z_@0-9]*(_SUITE)?$"
2526
}},

priv/static/assets/js/arizona-svelte.min.js

Lines changed: 34 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

priv/static/assets/js/arizona-svelte.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rebar.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{"1.2.0",
22
[{<<"arizona">>,
33
{git,"https://github.com/arizona-framework/arizona.git",
4-
{ref,"9fc15a2fcb829a343bc5d721eb53401c391eccbb"}},
4+
{ref,"d6552d52d3af4f4feab2e2bcd6a53e857009d569"}},
55
0},
66
{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.14.0">>},1},
77
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.16.0">>},2},

0 commit comments

Comments
 (0)