feat(element-style): adopt hand-written customization blocks, and widen the font list

Nineteen plugins spell their style elements out longhand instead of
declaring them -- football's block is 701 lines for seven elements -- and
predate this system entirely. Core now recognises that shape, so they pick
up the row-per-element editor and the real font picker on a core update
rather than on a plugin release. Checked against every published schema:
21 plugins adopt, and the defaults of each still validate against the
schema generated for it.

Detection requires *every* field in a block to be one this system
understands. A looser "has at least one style field" rule sweeps in
baseball's `count`, which carries a text_color beside geometry that means
nothing here. That distinction took three attempts to test: the first two
assertions passed under both rules, because an over-eager rule leaves a
fontless block looking untouched and only surfaces as an extra row in the
editor.

The hardcoded font enum is replaced rather than extended. Football lists
five of the thirty-five installed fonts, which is why a font a user
uploads can never appear in one. It is not a curated safe set -- it omits
some twenty other faces that fit the declared size cap just as well -- it
is the fonts that happened to exist when it was written.

Widening it does need a guard, though, and not the one the schema already
has: a bitmap font ignores font_size and renders at its size baked into
the file, so `maximum: 16` cannot stop a 27px face. The picker now filters
out fixed-size fonts taller than the element's own declared ceiling, which
drops exactly the four that would overflow a 32px panel and keeps the
other thirty.

Per-mode overrides stay opt-in: core cannot invent a plugin's display
modes, so `x-style-modes` remains the one line that unlocks them. Their
layout half covers every positionable element rather than only those with
a style block -- the two namespaces do not line up in a hand-written
schema, and football positions six things (logos, timeouts, possession)
that have no style block at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-11 11:01:01 -04:00
co-authored by Claude Opus 5
parent 6cd886cd78
commit e86dc9c27b
4 changed files with 423 additions and 5 deletions
@@ -174,6 +174,12 @@
const xOptions = config['x-options'] || config['x_options'] || {};
const placeholder = xOptions.placeholder || 'Select a font...';
const filterTypes = xOptions.filterTypes || null; // e.g., ['ttf', 'bdf']
// Bitmap (BDF) fonts render at the one size baked into the file
// and ignore the size setting entirely, so a field that caps size
// at 16 can still be handed a 27px face. maxFixedSize hides the
// ones that cannot honour the cap, rather than offering a choice
// that silently overflows the panel.
const maxFixedSize = Number(xOptions.maxFixedSize) || null;
const showPreview = xOptions.showPreview === true;
const disabled = xOptions.disabled === true;
const required = xOptions.required === true;
@@ -204,6 +210,16 @@
return filterTypes.some(t => t.toLowerCase() === fontType);
});
}
if (maxFixedSize) {
filteredFonts = filteredFonts.filter(font => {
// A scalable face can always meet the cap.
if (font.scalable !== false) { return true; }
// An unknown native size is not evidence it is too
// big; keep it rather than hiding a usable font.
if (!font.native_size) { return true; }
return font.native_size <= maxFixedSize;
});
}
// Build select HTML
let html = `<div id="${fieldId}_widget" class="font-selector-widget" data-field-id="${fieldId}">`;