Badges: fix print page svelte-check error — extract print CSS to static file
Svelte 5 does not support <style> or conditional {#if} blocks wrapping
<style> tags inside <svelte:head>. The parser treats them as raw-text
elements and reports '<script> was left open' at EOF.
Fix:
- Print media CSS moved to static/ae-print-badge.css (plain static file,
no framework magic needed — all selectors target global elements).
- svelte:head now uses a simple <link> to that file.
- $effect injects the @page size dynamically per template layout field,
avoiding the Svelte 5 parser limitation for conditional style injection.
- Badge_template interface in db_events.ts: added cfg_json / data_json
(standard Aether object fields that were missing from the type).
This commit is contained in:
@@ -254,6 +254,9 @@ export interface Badge_template {
|
|||||||
style_href?: null | string;
|
style_href?: null | string;
|
||||||
duplex?: null | number | boolean;
|
duplex?: null | number | boolean;
|
||||||
|
|
||||||
|
cfg_json?: null | string;
|
||||||
|
data_json?: null | string;
|
||||||
|
|
||||||
enable?: null | boolean;
|
enable?: null | boolean;
|
||||||
hide?: null | boolean;
|
hide?: null | boolean;
|
||||||
priority?: null | boolean;
|
priority?: null | boolean;
|
||||||
|
|||||||
@@ -118,13 +118,27 @@
|
|||||||
// When set, overrides the default @page margin: 0 for per-template positioning.
|
// When set, overrides the default @page margin: 0 for per-template positioning.
|
||||||
// Expected format in cfg_json: { "print_margin": { "top": "0.25in", "right": "0.25in", "bottom": "0.25in", "left": "0.25in" } }
|
// Expected format in cfg_json: { "print_margin": { "top": "0.25in", "right": "0.25in", "bottom": "0.25in", "left": "0.25in" } }
|
||||||
// All four edges are optional; omitted edges fall back to 0.
|
// All four edges are optional; omitted edges fall back to 0.
|
||||||
// TODO: inject into @page rule via a dynamic <style> block once a UI exists to set this value.
|
|
||||||
let print_margin_cfg = $derived.by(() => {
|
let print_margin_cfg = $derived.by(() => {
|
||||||
try {
|
try {
|
||||||
const cfg = JSON.parse($lq__event_badge_template_obj?.cfg_json ?? '{}');
|
const cfg = JSON.parse($lq__event_badge_template_obj?.cfg_json ?? '{}');
|
||||||
return (cfg?.print_margin as { top?: string; right?: string; bottom?: string; left?: string } | null) ?? null;
|
return (cfg?.print_margin as { top?: string; right?: string; bottom?: string; left?: string } | null) ?? null;
|
||||||
} catch { return null; }
|
} catch { return null; }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// @page size injection: Svelte 5 does not allow <style> inside {#if} blocks in
|
||||||
|
// <svelte:head> — the parser treats them as raw-text elements and loses document
|
||||||
|
// structure. Inject programmatically via $effect instead.
|
||||||
|
let page_size_css = $derived(
|
||||||
|
$lq__event_badge_template_obj?.layout === 'badge_3.5x5.5_pvc' ? '3.5in 5.5in' :
|
||||||
|
$lq__event_badge_template_obj?.layout === 'badge_4x5_fanfold' ? '4in 10in' :
|
||||||
|
'4in 12in' // Default: badge_4x6_fanfold or layout not yet set
|
||||||
|
);
|
||||||
|
$effect(() => {
|
||||||
|
const el = document.createElement('style');
|
||||||
|
el.textContent = `@page { size: ${page_size_css}; margin: 0; }`;
|
||||||
|
document.head.appendChild(el);
|
||||||
|
return () => el.remove();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -158,129 +172,12 @@
|
|||||||
For PVC / fanfold: the @page size below matches the badge exactly, so
|
For PVC / fanfold: the @page size below matches the badge exactly, so
|
||||||
margin: 0 fills the page cleanly. If per-template margins are needed,
|
margin: 0 fills the page cleanly. If per-template margins are needed,
|
||||||
set cfg_json: { "print_margin": { "top": "0.25in", ... } } on the template
|
set cfg_json: { "print_margin": { "top": "0.25in", ... } } on the template
|
||||||
and a dynamic @page rule can be injected here via print_margin_cfg. -->
|
and a dynamic @page rule can be injected here via print_margin_cfg.
|
||||||
<style>
|
|
||||||
@media print {
|
|
||||||
/* Full-page reset.
|
|
||||||
app.css sets overflow:hidden on html + body — override so they don't
|
|
||||||
create BFCs that shrink to content. */
|
|
||||||
html, body {
|
|
||||||
display: block !important;
|
|
||||||
width: 100% !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide all app chrome */
|
Print media CSS lives in static/ae-print-badge.css (loaded below).
|
||||||
.submenu { display: none !important; }
|
@page size is injected via $effect in the script block (dynamic per template layout).
|
||||||
|
-->
|
||||||
/* #ae_main_content: make it a transparent, non-interfering passthrough block.
|
<link rel="stylesheet" href="/ae-print-badge.css" />
|
||||||
We cannot use display:contents here — it has overflow:auto, and per spec
|
|
||||||
display:contents cannot override overflow-clipping elements (Firefox
|
|
||||||
enforces this strictly). We just strip its visual/layout effects instead. */
|
|
||||||
#ae_main_content {
|
|
||||||
display: block !important;
|
|
||||||
position: static !important;
|
|
||||||
width: 100% !important;
|
|
||||||
max-width: none !important;
|
|
||||||
height: auto !important;
|
|
||||||
min-height: 0 !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
background: transparent !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .main_content and #badge_render_area have no overflow constraints,
|
|
||||||
so display:contents safely dissolves their boxes in all browsers. */
|
|
||||||
.main_content,
|
|
||||||
#badge_render_area {
|
|
||||||
display: contents !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Center badge using position:fixed.
|
|
||||||
Why fixed instead of absolute/flex:
|
|
||||||
- position:fixed in print positions relative to the @page content area,
|
|
||||||
completely bypassing the ancestor hierarchy (no containing-block height
|
|
||||||
dependency, no overflow-clip interference, no flex-parent issues).
|
|
||||||
- top/left:50% + translate(-50%,-50%) centers within the page content area.
|
|
||||||
- Firefox applies physical printer hardware margins even with @page{margin:0};
|
|
||||||
this shrinks the content area slightly but top:50%/left:50% still centers
|
|
||||||
within whatever area the browser gives us — it's immune to asymmetric margins.
|
|
||||||
- Chrome: identical behavior to absolute+flex centering but more robust.
|
|
||||||
- min-height:0 overrides the Tailwind min-h-[6.0in] class on this wrapper;
|
|
||||||
badge_front/back provide their own dimensions. */
|
|
||||||
.event_badge_wrapper {
|
|
||||||
position: fixed !important;
|
|
||||||
top: 50% !important;
|
|
||||||
left: 50% !important;
|
|
||||||
transform: translate(-50%, -50%) !important;
|
|
||||||
min-height: 0 !important;
|
|
||||||
max-width: none !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
gap: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Never split front/back across pages */
|
|
||||||
.badge_front,
|
|
||||||
.badge_back {
|
|
||||||
break-inside: avoid;
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================
|
|
||||||
TEMPORARY DEBUG OUTLINES — remove before going live
|
|
||||||
============================================================ */
|
|
||||||
html {
|
|
||||||
outline: 3px dashed lime !important;
|
|
||||||
outline-offset: -3px !important;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
outline: 4px solid blue !important;
|
|
||||||
outline-offset: -4px !important;
|
|
||||||
}
|
|
||||||
/* Red = #ae_main_content — block passthrough, should fill page width/height. */
|
|
||||||
#ae_main_content {
|
|
||||||
outline: 3px dashed red !important;
|
|
||||||
outline-offset: -6px !important;
|
|
||||||
}
|
|
||||||
/* Orange + purple = display:contents — should be INVISIBLE (no box). */
|
|
||||||
.main_content {
|
|
||||||
outline: 3px dashed orange !important;
|
|
||||||
outline-offset: -9px !important;
|
|
||||||
}
|
|
||||||
#badge_render_area {
|
|
||||||
outline: 3px dashed purple !important;
|
|
||||||
outline-offset: -12px !important;
|
|
||||||
}
|
|
||||||
/* Cyan = the actual badge — should be dead-center on page */
|
|
||||||
.event_badge_wrapper {
|
|
||||||
outline: 3px solid cyan !important;
|
|
||||||
outline-offset: 2px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<!-- @page paper size: matched to the badge stock per template layout.
|
|
||||||
margin: 0 so the badge occupies the full printable area.
|
|
||||||
If per-template offsets are needed, inject a dynamic style element
|
|
||||||
here driven by print_margin_cfg (parsed from template cfg_json). -->
|
|
||||||
{#if $lq__event_badge_template_obj?.layout === 'badge_3.5x5.5_pvc'}
|
|
||||||
<style>
|
|
||||||
@page { size: 3.5in 5.5in; margin: 0; }
|
|
||||||
</style>
|
|
||||||
{:else if $lq__event_badge_template_obj?.layout === 'badge_4x5_fanfold'}
|
|
||||||
<style>
|
|
||||||
@page { size: 4in 10in; margin: 0; }
|
|
||||||
</style>
|
|
||||||
{:else}
|
|
||||||
<!-- Default: badge_4x6_fanfold or layout not yet set -->
|
|
||||||
<style>
|
|
||||||
@page { size: 4in 12in; margin: 0; }
|
|
||||||
</style>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- External client CSS: brand colors, logo sizing, footer stripe colors per event.
|
<!-- External client CSS: brand colors, logo sizing, footer stripe colors per event.
|
||||||
Set style_href on the badge template. Edit the file on the static server and
|
Set style_href on the badge template. Edit the file on the static server and
|
||||||
|
|||||||
112
static/ae-print-badge.css
Normal file
112
static/ae-print-badge.css
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Badge Print Page — Print Media CSS
|
||||||
|
*
|
||||||
|
* Loaded via <link> in <svelte:head> on the badge print page only.
|
||||||
|
* Cannot be a component <style> block: Svelte 5 does not allow <style> inside
|
||||||
|
* <svelte:head>, and scoped component styles can't target html/body/global IDs.
|
||||||
|
*
|
||||||
|
* @page size is NOT here — it is injected dynamically via $effect in the page
|
||||||
|
* script because the size depends on the badge template layout field.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
/* Full-page reset.
|
||||||
|
app.css sets overflow:hidden on html + body — override so they don't
|
||||||
|
create BFCs that shrink to content. */
|
||||||
|
html, body {
|
||||||
|
display: block !important;
|
||||||
|
width: 100% !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide all app chrome */
|
||||||
|
.submenu { display: none !important; }
|
||||||
|
|
||||||
|
/* #ae_main_content: make it a transparent, non-interfering passthrough block.
|
||||||
|
We cannot use display:contents here — it has overflow:auto, and per spec
|
||||||
|
display:contents cannot override overflow-clipping elements (Firefox
|
||||||
|
enforces this strictly). We just strip its visual/layout effects instead. */
|
||||||
|
#ae_main_content {
|
||||||
|
display: block !important;
|
||||||
|
position: static !important;
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: none !important;
|
||||||
|
height: auto !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
background: transparent !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .main_content and #badge_render_area have no overflow constraints,
|
||||||
|
so display:contents safely dissolves their boxes in all browsers. */
|
||||||
|
.main_content,
|
||||||
|
#badge_render_area {
|
||||||
|
display: contents !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Center badge using position:fixed.
|
||||||
|
Why fixed instead of absolute/flex:
|
||||||
|
- position:fixed in print positions relative to the @page content area,
|
||||||
|
completely bypassing the ancestor hierarchy (no containing-block height
|
||||||
|
dependency, no overflow-clip interference, no flex-parent issues).
|
||||||
|
- top/left:50% + translate(-50%,-50%) centers within the page content area.
|
||||||
|
- Firefox applies physical printer hardware margins even with @page{margin:0};
|
||||||
|
this shrinks the content area slightly but top:50%/left:50% still centers
|
||||||
|
within whatever area the browser gives us — it's immune to asymmetric margins.
|
||||||
|
- Chrome: identical behavior to absolute+flex centering but more robust.
|
||||||
|
- min-height:0 overrides the Tailwind min-h-[6.0in] class on this wrapper;
|
||||||
|
badge_front/back provide their own dimensions. */
|
||||||
|
.event_badge_wrapper {
|
||||||
|
position: fixed !important;
|
||||||
|
top: 50% !important;
|
||||||
|
left: 50% !important;
|
||||||
|
transform: translate(-50%, -50%) !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
max-width: none !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
gap: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Never split front/back across pages */
|
||||||
|
.badge_front,
|
||||||
|
.badge_back {
|
||||||
|
break-inside: avoid;
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
TEMPORARY DEBUG OUTLINES — remove before going live
|
||||||
|
============================================================ */
|
||||||
|
html {
|
||||||
|
outline: 3px dashed lime !important;
|
||||||
|
outline-offset: -3px !important;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
outline: 4px solid blue !important;
|
||||||
|
outline-offset: -4px !important;
|
||||||
|
}
|
||||||
|
/* Red = #ae_main_content — block passthrough, should fill page width/height. */
|
||||||
|
#ae_main_content {
|
||||||
|
outline: 3px dashed red !important;
|
||||||
|
outline-offset: -6px !important;
|
||||||
|
}
|
||||||
|
/* Orange + purple = display:contents — should be INVISIBLE (no box). */
|
||||||
|
.main_content {
|
||||||
|
outline: 3px dashed orange !important;
|
||||||
|
outline-offset: -9px !important;
|
||||||
|
}
|
||||||
|
#badge_render_area {
|
||||||
|
outline: 3px dashed purple !important;
|
||||||
|
outline-offset: -12px !important;
|
||||||
|
}
|
||||||
|
/* Cyan = the actual badge — should be dead-center on page */
|
||||||
|
.event_badge_wrapper {
|
||||||
|
outline: 3px solid cyan !important;
|
||||||
|
outline-offset: 2px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user