{#
inlines.html — the shared inline-formset markup.
ONE source of truth for the inline formset, used by BOTH
the full-page change form (`admin/form.html`) and the slide-over sheet
(`admin/_macros/sheet.html`). Rendering the same markup in both places
guarantees the formset field NAMES are byte-identical, so the single
`crud::create` / `crud::update` handler parses + saves them unchanged
(the `inline-<child>-<i>-<field>` naming scheme).
Public macro: `inline_formset(inlines)` where `inlines` is the list of
`InlineView` structs built by `inlines::build_inline_views`. Each view
carries its child table name, layout kind ("tabular"|"stacked"),
delete flag, header fields, and the existing+blank rows.
The "Add another" JS is scoped per-formset and re-binds on every
render (it keys off `data-inline-bound` so an HTMX-swapped sheet that
re-loads the partial wires the buttons exactly once).
#}
{# Per-field input widget for one inline cell. `idx` may be a real index
or the `__IDX__` placeholder (string) for the blank template; the
value is then empty. Compact inputs (no label row) so the tabular
layout stays a tidy grid; the stacked layout adds its own labels. #}
{% macro inline_field_input(name, idx, f, blank=false) %}
{% set fname = "inline-" ~ name ~ "-" ~ idx ~ "-" ~ f.name %}
{% set fval = "" if blank else f.value %}
{% if f.kind == "bool" %}
<input type="checkbox" name="{{ fname }}" value="true"{% if fval == "true" %} checked{% endif %}{% if f.readonly %} disabled{% endif %}>
{% elif f.kind == "select" %}
<select name="{{ fname }}"{% if f.readonly %} disabled{% endif %} style="width:100%;padding:4px;">
{% if f.nullable %}<option value="">—</option>{% endif %}
{% for opt in f.choices %}<option value="{{ opt.value }}"{% if fval == opt.value %} selected{% endif %}>{{ opt.label }}</option>{% endfor %}
</select>
{% elif f.kind == "textarea" or f.kind == "json" %}
<textarea name="{{ fname }}" rows="2"{% if f.readonly %} readonly{% endif %} style="width:100%;padding:4px;">{{ fval }}</textarea>
{% else %}
<input type="{% if f.kind == "number" %}number{% elif f.kind == "date" %}date{% elif f.kind == "time" %}time{% elif f.kind == "datetime-local" %}datetime-local{% else %}text{% endif %}" name="{{ fname }}" value="{{ fval }}"{% if f.readonly %} readonly{% endif %} style="width:100%;padding:4px;">
{% endif %}
{% endmacro %}
{% macro inline_formset(inlines) %}
{% if inlines %}
{% for inline in inlines %}
<fieldset class="inline-group" data-inline="{{ inline.name }}" style="margin:1.25rem 0;border:1px solid #ddd;border-radius:8px;padding:0.75rem 1rem;">
<legend style="font-weight:600;padding:0 0.4rem;">{{ inline.label }}</legend>
<input type="hidden" name="inline-{{ inline.name }}-TOTAL" value="{{ inline.total }}" data-inline-total>
{% if inline.kind == "tabular" %}
<table class="inline-table" style="width:100%;border-collapse:collapse;">
<thead>
<tr>
{% for f in inline.fields %}<th style="text-align:left;padding:0.3rem;font-size:0.85rem;">{{ f.name }}</th>{% endfor %}
{% if inline.can_delete %}<th style="padding:0.3rem;font-size:0.85rem;">Delete</th>{% endif %}
</tr>
</thead>
<tbody data-inline-body>
{% for r in inline.rows %}
{% set row_idx = loop.index0 %}
<tr data-inline-row data-index="{{ row_idx }}">
<input type="hidden" name="inline-{{ inline.name }}-{{ row_idx }}-id" value="{{ r.id }}">
{% for f in r.fields %}
<td style="padding:0.3rem;">{{ inline_field_input(inline.name, row_idx, f) }}</td>
{% endfor %}
{% if inline.can_delete %}
<td style="padding:0.3rem;text-align:center;">
{% if r.id %}<input type="checkbox" name="inline-{{ inline.name }}-{{ row_idx }}-DELETE" value="on">{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div data-inline-body>
{% for r in inline.rows %}
{% set row_idx = loop.index0 %}
<div data-inline-row data-index="{{ row_idx }}" style="border:1px solid #eee;border-radius:6px;padding:0.5rem 0.75rem;margin-bottom:0.5rem;">
<input type="hidden" name="inline-{{ inline.name }}-{{ row_idx }}-id" value="{{ r.id }}">
{% for f in r.fields %}
<div class="field" style="margin-bottom:0.5rem;">
<label style="display:block;font-size:0.85rem;">{{ f.name }}</label>
{{ inline_field_input(inline.name, row_idx, f) }}
</div>
{% endfor %}
{% if inline.can_delete and r.id %}
<label style="font-size:0.85rem;"><input type="checkbox" name="inline-{{ inline.name }}-{{ row_idx }}-DELETE" value="on"> Delete</label>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{# Blank-row template for the "Add another" JS. Index placeholder
__IDX__ is substituted on clone. #}
<template data-inline-template>
{% if inline.kind == "tabular" %}
<tr data-inline-row>
<input type="hidden" name="inline-{{ inline.name }}-__IDX__-id" value="">
{% for f in inline.fields %}
<td style="padding:0.3rem;">{{ inline_field_input(inline.name, "__IDX__", f, true) }}</td>
{% endfor %}
{% if inline.can_delete %}<td></td>{% endif %}
</tr>
{% else %}
<div data-inline-row style="border:1px solid #eee;border-radius:6px;padding:0.5rem 0.75rem;margin-bottom:0.5rem;">
<input type="hidden" name="inline-{{ inline.name }}-__IDX__-id" value="">
{% for f in inline.fields %}
<div class="field" style="margin-bottom:0.5rem;">
<label style="display:block;font-size:0.85rem;">{{ f.name }}</label>
{{ inline_field_input(inline.name, "__IDX__", f, true) }}
</div>
{% endfor %}
</div>
{% endif %}
</template>
<button type="button" class="btn" data-inline-add style="margin-top:0.5rem;">+ Add another {{ inline.label }}</button>
</fieldset>
{% endfor %}
{# Scoped "Add another" wiring. Idempotent: each `.inline-group` is
flagged `data-inline-bound` once so re-running this block (the HTMX
sheet re-loads the partial on every open / save-and-continue) never
double-binds the click handler. Plain DOM cloning — no dependency. #}
<script>
(function() {
document.querySelectorAll('.inline-group').forEach(function(group) {
if (group.getAttribute('data-inline-bound') === '1') return;
var totalInput = group.querySelector('[data-inline-total]');
var body = group.querySelector('[data-inline-body]');
var tpl = group.querySelector('[data-inline-template]');
var addBtn = group.querySelector('[data-inline-add]');
if (!addBtn || !tpl || !body || !totalInput) return;
group.setAttribute('data-inline-bound', '1');
addBtn.addEventListener('click', function() {
var idx = parseInt(totalInput.value, 10) || 0;
var html = tpl.innerHTML.replace(/__IDX__/g, String(idx));
var wrap = document.createElement('tbody');
wrap.innerHTML = html.trim();
var node = wrap.firstElementChild;
body.appendChild(node);
totalInput.value = String(idx + 1);
});
});
})();
</script>
{% endif %}
{% endmacro %}