@transtyle/exporter-bootstrap 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +43 -0
- package/src/components.js +326 -0
- package/src/descriptors.js +665 -0
- package/src/index.js +686 -0
- package/surface-inventory.json +12855 -0
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AL1.2 (docs/plan/bootstrap-component-tier.md): the binding cross-walk —
|
|
3
|
+
* which meaning each in-inventory Bootstrap variable reads. Data only; the
|
|
4
|
+
* emission semantics are AL1.3's. Completeness is enforced by
|
|
5
|
+
* scripts/check-bootstrap-surface.mjs: every in-inventory variable must be
|
|
6
|
+
* bound here (emit), explicitly classified (drop), or mechanically covered
|
|
7
|
+
* (chained / aliases-global / Bootstrap `null`-family marker).
|
|
8
|
+
*
|
|
9
|
+
* Recipe forms in `emit`:
|
|
10
|
+
* { comp: 'button.padding-x' } — component-tier slot (COMPONENT_CATALOG)
|
|
11
|
+
* { sem: 'space.2' } — exporter-private semantic binding
|
|
12
|
+
* { sem: ..., cls: 'approximated', note } — inexact binding, honestly classed
|
|
13
|
+
* { trans: { duration, easing } } — transition shorthand recipe:
|
|
14
|
+
* AL1.3 keeps Bootstrap's default property list and substitutes timing —
|
|
15
|
+
* always classed `approximated` (one duration/easing for a compound value).
|
|
16
|
+
*
|
|
17
|
+
* `drop` entries: { cls: 'dropped' | 'unsupported', note } per
|
|
18
|
+
* validation-and-coverage.md's class directions — `dropped` = has no token
|
|
19
|
+
* meaning / deliberately not driven; `unsupported` = a real themable slot the
|
|
20
|
+
* IR has no vocabulary for yet (catalog-growth data, by construction).
|
|
21
|
+
*
|
|
22
|
+
* Mechanical classes (computed in coverageForVariable, not listed per name):
|
|
23
|
+
* chained — the value references other `$` variables; Bootstrap's own
|
|
24
|
+
* `!default` expression chains it from roots we do drive
|
|
25
|
+
* (the plan's "bind the root decision" rule).
|
|
26
|
+
* follows-global — aliases a global var(--bs-*) custom property, which the
|
|
27
|
+
* semantic tier drives today.
|
|
28
|
+
* inherits-driven — a cascade no-op marker on an *inherited* property whose
|
|
29
|
+
* effective value still comes from something we drive (see
|
|
30
|
+
* INHERITS_DRIVEN). Real coverage, exactly as PrimeNG's
|
|
31
|
+
* `inherited` reason is.
|
|
32
|
+
* inherit-default — the remaining `null`/`inherit`/`transparent`/`currentcolor`
|
|
33
|
+
* no-op markers; setting them would change cascade
|
|
34
|
+
* semantics, not theme values.
|
|
35
|
+
*
|
|
36
|
+
* Recipe `part`: 'alpha' | 'opaque' for colors; a composite member name
|
|
37
|
+
* ('fontSize' | 'fontWeight' | 'lineHeight') for `semantic.type.role.*`.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
// Shared honest notes (referenced by many entries — keep the wording in one place).
|
|
41
|
+
const N_PARAM =
|
|
42
|
+
'Bootstrap state/subtle-color derivation knob (shade/tint/scale %). Transtyle derives those result colors directly (grid -hover/-active cells, tint/outline prominences); the percentage itself has no token meaning — the derived colors reach the target via the CSS-variable path (AL1.4).';
|
|
43
|
+
const N_ASSET =
|
|
44
|
+
'embedded SVG asset (icon/glyph) — the IR has no asset/icon vocabulary yet. AL2 growth signal: component icon slots.';
|
|
45
|
+
const N_OPACITY =
|
|
46
|
+
'opacity value with no shared meaning: AL2 promoted `semantic.opacity.disabled` (the one both reference targets needed) but this slot is a different concept — a veil strength, a shimmer range, or a glyph-specific alpha. Growth signal only if a second target needs the identical one.';
|
|
47
|
+
// Probed against PrimeNG's real inventory in proposal 0004 — the earlier wording
|
|
48
|
+
// ("the IR has no component-size vocabulary yet") promised a vocabulary the
|
|
49
|
+
// evidence says nobody needs. Of the ten concepts in this bucket, six are
|
|
50
|
+
// one-sided (PrimeNG hard-codes what Bootstrap tokenizes: spinner, popover,
|
|
51
|
+
// modal ladder, offcanvas) or false friends ($toast-max-width 350px vs
|
|
52
|
+
// PrimeNG's fixed `width: 22rem`; $dropdown-min-width is the menu, PrimeNG's
|
|
53
|
+
// `dropdown.width` is the trigger), and two more share the concept while
|
|
54
|
+
// disagreeing on its architecture. A note that names a growth signal that isn't
|
|
55
|
+
// there is the coverage-report version of over-claiming.
|
|
56
|
+
const N_BESPOKE =
|
|
57
|
+
'bespoke component geometry with no shared meaning — measured against the second reference target (proposal 0004), this is a slot PrimeNG either hard-codes or models differently, not a gap in the IR. Not a growth signal.';
|
|
58
|
+
// The two shapes proposal 0004 separated out of N_BESPOKE, because they are
|
|
59
|
+
// genuinely different situations and a single note flattened them.
|
|
60
|
+
const N_ARCH_DISAGREE =
|
|
61
|
+
'both reference targets have this concept and model it differently, so no single authored value satisfies both: Bootstrap sizes it in `em` (proportional to local font size, one dimension), PrimeNG in absolute `rem` with independent width/height and an sm/md/lg ladder. The disagreement is the finding (proposal 0004) — same shape as the size ladder AL2 rejected.';
|
|
62
|
+
const N_ICON =
|
|
63
|
+
'component icon size. The strongest open catalog-growth signal, and still one-sided: PrimeNG models this systematically (50 `icon.size` slots across 31 families, with a shared value set), Bootstrap has three ad-hoc ones with no common root. Promoting today would export one target’s architecture into the catalog — revisit when a third target arrives with a systematic icon-size concept (proposal 0004).';
|
|
64
|
+
const N_STRUCT =
|
|
65
|
+
'structural/behavioral option (layout, cursor, transform, ordering, animation mechanics), not a theme value.';
|
|
66
|
+
const N_FILTER = 'CSS filter trick — presentation mechanics, not a token meaning.';
|
|
67
|
+
const N_EM =
|
|
68
|
+
'em-relative in Bootstrap (proportional to local font size); bound to the nearest rem rung — proportional intent is approximated, not preserved.';
|
|
69
|
+
|
|
70
|
+
const T_FAST = { trans: { duration: 'duration.fast', easing: 'easing.standard' } };
|
|
71
|
+
|
|
72
|
+
export const DESCRIPTORS = {
|
|
73
|
+
// ---- content-ish micro components -------------------------------------
|
|
74
|
+
caret: {
|
|
75
|
+
drop: { 'caret-width': { cls: 'dropped', note: N_STRUCT + ' (caret glyph geometry)' } },
|
|
76
|
+
},
|
|
77
|
+
hr: { drop: { 'hr-opacity': { cls: 'unsupported', note: N_OPACITY } } },
|
|
78
|
+
legend: {
|
|
79
|
+
emit: {
|
|
80
|
+
'legend-margin-bottom': { sem: 'space.2' },
|
|
81
|
+
'legend-font-size': {
|
|
82
|
+
sem: 'type.size.xl',
|
|
83
|
+
cls: 'approximated',
|
|
84
|
+
note: '1.5rem vs the 1.563rem scale rung — nearest meaning',
|
|
85
|
+
},
|
|
86
|
+
// Bootstrap nulls the weight (inherit body 400), which is incoherent with
|
|
87
|
+
// the 1.5rem size it does set: a legend is a fieldset's title. Bound to
|
|
88
|
+
// the title role's weight so size and weight come from the same meaning.
|
|
89
|
+
'legend-font-weight': { sem: 'type.role.title.md', part: 'fontWeight' },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
mark: { emit: { 'mark-padding': { sem: 'space.1', cls: 'approximated', note: N_EM } } },
|
|
93
|
+
kbd: {
|
|
94
|
+
emit: {
|
|
95
|
+
'kbd-padding-y': {
|
|
96
|
+
sem: 'space.1',
|
|
97
|
+
cls: 'approximated',
|
|
98
|
+
note: '.1875rem — sub-rung; nearest meaning',
|
|
99
|
+
},
|
|
100
|
+
'kbd-padding-x': {
|
|
101
|
+
sem: 'space.1',
|
|
102
|
+
cls: 'approximated',
|
|
103
|
+
note: '.375rem — between rungs; nearest meaning',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
pre: {},
|
|
108
|
+
code: {},
|
|
109
|
+
dt: {},
|
|
110
|
+
figure: {},
|
|
111
|
+
blockquote: {},
|
|
112
|
+
thumbnail: { emit: { 'thumbnail-padding': { sem: 'space.1' } } },
|
|
113
|
+
placeholder: {
|
|
114
|
+
drop: {
|
|
115
|
+
'placeholder-opacity-max': { cls: 'unsupported', note: N_OPACITY },
|
|
116
|
+
'placeholder-opacity-min': { cls: 'unsupported', note: N_OPACITY },
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
// ---- table --------------------------------------------------------------
|
|
121
|
+
table: {
|
|
122
|
+
emit: {
|
|
123
|
+
'table-cell-padding-y': { sem: 'space.2' },
|
|
124
|
+
'table-cell-padding-x': { sem: 'space.2' },
|
|
125
|
+
'table-cell-padding-y-sm': { sem: 'space.1' },
|
|
126
|
+
'table-cell-padding-x-sm': { sem: 'space.1' },
|
|
127
|
+
},
|
|
128
|
+
drop: {
|
|
129
|
+
'table-cell-vertical-align': { cls: 'dropped', note: N_STRUCT },
|
|
130
|
+
'table-th-font-weight': {
|
|
131
|
+
cls: 'dropped',
|
|
132
|
+
note: 'Bootstrap null marker with no non-null meaning to bind',
|
|
133
|
+
},
|
|
134
|
+
'table-striped-bg-factor': { cls: 'dropped', note: N_PARAM },
|
|
135
|
+
'table-active-bg-factor': { cls: 'dropped', note: N_PARAM },
|
|
136
|
+
'table-hover-bg-factor': { cls: 'dropped', note: N_PARAM },
|
|
137
|
+
'table-border-factor': { cls: 'dropped', note: N_PARAM },
|
|
138
|
+
'table-bg-scale': { cls: 'dropped', note: N_PARAM },
|
|
139
|
+
'table-striped-order': { cls: 'dropped', note: N_STRUCT },
|
|
140
|
+
'table-striped-columns-order': { cls: 'dropped', note: N_STRUCT },
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
// ---- buttons + the shared input-btn root --------------------------------
|
|
145
|
+
input: {
|
|
146
|
+
emit: {
|
|
147
|
+
// AL2 RESOLVED the AL1.2 contested call: Bootstrap's shared root now
|
|
148
|
+
// binds to the promoted `component.control.*` slots — the shared
|
|
149
|
+
// interactive-control geometry — and `$btn-padding-*` below binds to
|
|
150
|
+
// `component.button.*`, which defaults from control. That reproduces
|
|
151
|
+
// Bootstrap's own two-level model exactly ($btn-padding-y defaults to
|
|
152
|
+
// $input-btn-padding-y), so authoring `control.*` moves buttons and
|
|
153
|
+
// fields together while authoring `button.*` moves only buttons.
|
|
154
|
+
'input-btn-padding-y': { comp: 'control.padding-y' },
|
|
155
|
+
'input-btn-padding-x': { comp: 'control.padding-x' },
|
|
156
|
+
// The sm/lg ladder stays exporter-private: both targets have one, but
|
|
157
|
+
// they disagree on the rungs (PrimeNG sm-x = space.3, Bootstrap = space.2),
|
|
158
|
+
// so there is no convergent value to promote — proposal 0003 §deferred.
|
|
159
|
+
'input-btn-padding-y-sm': { sem: 'space.1' },
|
|
160
|
+
'input-btn-padding-x-sm': { sem: 'space.2' },
|
|
161
|
+
'input-btn-padding-y-lg': { sem: 'space.2' },
|
|
162
|
+
'input-btn-padding-x-lg': { sem: 'space.4' },
|
|
163
|
+
'input-border-radius': { comp: 'control.radius' },
|
|
164
|
+
'input-transition': T_FAST,
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
btn: {
|
|
168
|
+
emit: {
|
|
169
|
+
// Review finding (2026-07-23): $btn-border-radius defaults to the global
|
|
170
|
+
// var(--bs-border-radius) — without an explicit binding, an authored
|
|
171
|
+
// component.button.radius (the catalog's flagship slot) would be
|
|
172
|
+
// silently ignored on Bootstrap. Bound; unauthored builds emit the
|
|
173
|
+
// resolved default (≡ what the chain produced) — explicit, not drifted.
|
|
174
|
+
'btn-border-radius': { comp: 'button.radius' },
|
|
175
|
+
// AL2: Bootstrap chains these from the shared $input-btn-* root by
|
|
176
|
+
// default; binding them explicitly to the button layer is what makes
|
|
177
|
+
// per-component authoring reach buttons without moving form fields.
|
|
178
|
+
'btn-padding-x': { comp: 'button.padding-x' },
|
|
179
|
+
'btn-padding-y': { comp: 'button.padding-y' },
|
|
180
|
+
'btn-disabled-opacity': {
|
|
181
|
+
sem: 'opacity.disabled',
|
|
182
|
+
cls: 'approximated',
|
|
183
|
+
note: "Bootstrap's own default is .65; the promoted catalog slot is the shared disabled-state opacity (AL2)",
|
|
184
|
+
},
|
|
185
|
+
'btn-transition': T_FAST,
|
|
186
|
+
},
|
|
187
|
+
drop: {
|
|
188
|
+
'btn-hover-bg-shade-amount': { cls: 'dropped', note: N_PARAM },
|
|
189
|
+
'btn-hover-bg-tint-amount': { cls: 'dropped', note: N_PARAM },
|
|
190
|
+
'btn-hover-border-shade-amount': { cls: 'dropped', note: N_PARAM },
|
|
191
|
+
'btn-hover-border-tint-amount': { cls: 'dropped', note: N_PARAM },
|
|
192
|
+
'btn-active-bg-shade-amount': { cls: 'dropped', note: N_PARAM },
|
|
193
|
+
'btn-active-bg-tint-amount': { cls: 'dropped', note: N_PARAM },
|
|
194
|
+
'btn-active-border-shade-amount': { cls: 'dropped', note: N_PARAM },
|
|
195
|
+
'btn-active-border-tint-amount': { cls: 'dropped', note: N_PARAM },
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
'btn-close': {
|
|
199
|
+
drop: {
|
|
200
|
+
'btn-close-width': { cls: 'unsupported', note: N_ICON + ' (close glyph)' },
|
|
201
|
+
'btn-close-padding-x': { cls: 'unsupported', note: N_BESPOKE + ' ' + N_EM },
|
|
202
|
+
'btn-close-bg': { cls: 'unsupported', note: N_ASSET },
|
|
203
|
+
'btn-close-opacity': { cls: 'unsupported', note: N_OPACITY },
|
|
204
|
+
'btn-close-hover-opacity': { cls: 'unsupported', note: N_OPACITY },
|
|
205
|
+
'btn-close-focus-opacity': { cls: 'unsupported', note: N_OPACITY },
|
|
206
|
+
'btn-close-disabled-opacity': {
|
|
207
|
+
cls: 'unsupported',
|
|
208
|
+
note: 'the same concept as `semantic.opacity.disabled`, but not bindable to it: Bootstrap composes this against the glyph\'s own resting alpha ($btn-close-opacity: .5), so writing the catalog value (0.6) here would make the disabled close button MORE visible than the enabled one. Needs a compositional recipe (catalog factor x the target\'s resting alpha) the cross-walk has no form for yet — a real AL2-style growth signal, unlike the other -opacity rows in this family.',
|
|
209
|
+
},
|
|
210
|
+
'btn-close-white-filter': { cls: 'dropped', note: N_FILTER },
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
// ---- forms ---------------------------------------------------------------
|
|
215
|
+
'form-text': { emit: { 'form-text-margin-top': { sem: 'space.1' } } },
|
|
216
|
+
'form-label': {
|
|
217
|
+
emit: {
|
|
218
|
+
'form-label-margin-bottom': { sem: 'space.2' },
|
|
219
|
+
// Bootstrap leaves these `null` (labels inherit body type). The IR's type
|
|
220
|
+
// roles say a label is its own role — and Bootstrap has the exact slots
|
|
221
|
+
// to say so, so this is a binding, not an invention.
|
|
222
|
+
'form-label-font-size': { sem: 'type.role.label.md', part: 'fontSize' },
|
|
223
|
+
'form-label-font-weight': { sem: 'type.role.label.md', part: 'fontWeight' },
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
'form-color': { drop: { 'form-color-width': { cls: 'unsupported', note: N_BESPOKE } } },
|
|
227
|
+
'form-check': {
|
|
228
|
+
emit: {
|
|
229
|
+
'form-check-margin-bottom': {
|
|
230
|
+
sem: 'space.1',
|
|
231
|
+
cls: 'approximated',
|
|
232
|
+
note: '.125rem — sub-rung; nearest meaning',
|
|
233
|
+
},
|
|
234
|
+
'form-check-input-border-radius': { sem: 'radius.sm', cls: 'approximated', note: N_EM },
|
|
235
|
+
'form-check-inline-margin-end': { sem: 'space.4' },
|
|
236
|
+
'form-check-input-disabled-opacity': {
|
|
237
|
+
sem: 'opacity.disabled',
|
|
238
|
+
cls: 'approximated',
|
|
239
|
+
note: "Bootstrap's own default is .5; unified onto the shared disabled-state opacity (AL2)",
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
drop: {
|
|
243
|
+
'form-check-input-width': {
|
|
244
|
+
cls: 'unsupported',
|
|
245
|
+
note: N_ARCH_DISAGREE + ' (checkbox/radio box)',
|
|
246
|
+
},
|
|
247
|
+
'form-check-input-active-filter': { cls: 'dropped', note: N_FILTER },
|
|
248
|
+
'form-check-radio-border-radius': {
|
|
249
|
+
cls: 'dropped',
|
|
250
|
+
note: N_STRUCT + ' (50% is the radio shape identity, not a radius token)',
|
|
251
|
+
},
|
|
252
|
+
'form-check-input-checked-bg-image': { cls: 'unsupported', note: N_ASSET },
|
|
253
|
+
'form-check-radio-checked-bg-image': { cls: 'unsupported', note: N_ASSET },
|
|
254
|
+
'form-check-input-indeterminate-bg-image': { cls: 'unsupported', note: N_ASSET },
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
'form-switch': {
|
|
258
|
+
emit: { 'form-switch-transition': T_FAST },
|
|
259
|
+
drop: {
|
|
260
|
+
'form-switch-width': { cls: 'unsupported', note: N_ARCH_DISAGREE + ' (switch track)' },
|
|
261
|
+
'form-switch-bg-image': { cls: 'unsupported', note: N_ASSET },
|
|
262
|
+
'form-switch-focus-bg-image': { cls: 'unsupported', note: N_ASSET },
|
|
263
|
+
'form-switch-checked-bg-image': { cls: 'unsupported', note: N_ASSET },
|
|
264
|
+
'form-switch-checked-bg-position': { cls: 'dropped', note: N_STRUCT },
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
'form-select': {
|
|
268
|
+
drop: {
|
|
269
|
+
'form-select-bg-size': { cls: 'dropped', note: N_STRUCT + ' (indicator geometry)' },
|
|
270
|
+
'form-select-indicator': { cls: 'unsupported', note: N_ASSET },
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
'form-range': {
|
|
274
|
+
emit: {
|
|
275
|
+
'form-range-track-height': { sem: 'space.2' },
|
|
276
|
+
'form-range-track-border-radius': {
|
|
277
|
+
sem: 'radius.full',
|
|
278
|
+
cls: 'approximated',
|
|
279
|
+
note: 'Bootstrap says 1rem; the intent (pill track) maps to radius.full',
|
|
280
|
+
},
|
|
281
|
+
'form-range-thumb-border-radius': {
|
|
282
|
+
sem: 'radius.full',
|
|
283
|
+
cls: 'approximated',
|
|
284
|
+
note: 'pill/circular thumb intent',
|
|
285
|
+
},
|
|
286
|
+
'form-range-thumb-transition': T_FAST,
|
|
287
|
+
},
|
|
288
|
+
drop: {
|
|
289
|
+
'form-range-track-width': { cls: 'dropped', note: N_STRUCT },
|
|
290
|
+
'form-range-track-cursor': { cls: 'dropped', note: N_STRUCT },
|
|
291
|
+
'form-range-thumb-width': { cls: 'unsupported', note: N_BESPOKE + ' (thumb size)' },
|
|
292
|
+
'form-range-thumb-border': { cls: 'dropped', note: N_STRUCT },
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
'form-floating': {
|
|
296
|
+
emit: {
|
|
297
|
+
'form-floating-line-height': { sem: 'type.leading.tight' },
|
|
298
|
+
'form-floating-padding-y': { sem: 'space.4' },
|
|
299
|
+
'form-floating-transition': {
|
|
300
|
+
trans: { duration: 'duration.fast', easing: 'easing.standard' },
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
drop: {
|
|
304
|
+
'form-floating-input-padding-t': {
|
|
305
|
+
cls: 'dropped',
|
|
306
|
+
note: N_STRUCT + ' (float-label mechanics: padding split is coupled to the transform math)',
|
|
307
|
+
},
|
|
308
|
+
'form-floating-input-padding-b': {
|
|
309
|
+
cls: 'dropped',
|
|
310
|
+
note: N_STRUCT + ' (float-label mechanics)',
|
|
311
|
+
},
|
|
312
|
+
'form-floating-label-height': { cls: 'dropped', note: N_STRUCT + ' (float-label mechanics)' },
|
|
313
|
+
'form-floating-label-opacity': { cls: 'unsupported', note: N_OPACITY },
|
|
314
|
+
'form-floating-label-transform': { cls: 'dropped', note: N_STRUCT },
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
'form-feedback': {
|
|
318
|
+
drop: {
|
|
319
|
+
'form-feedback-icon-valid': { cls: 'unsupported', note: N_ASSET },
|
|
320
|
+
'form-feedback-icon-invalid': { cls: 'unsupported', note: N_ASSET },
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
'form-valid': {},
|
|
324
|
+
'form-invalid': {},
|
|
325
|
+
'form-validation': {},
|
|
326
|
+
'form-file': {},
|
|
327
|
+
'input-group': {},
|
|
328
|
+
|
|
329
|
+
// ---- navigation -----------------------------------------------------------
|
|
330
|
+
'nav-link': {
|
|
331
|
+
emit: {
|
|
332
|
+
'nav-link-padding-y': { sem: 'space.2' },
|
|
333
|
+
'nav-link-padding-x': { sem: 'space.4' },
|
|
334
|
+
'nav-link-transition': T_FAST,
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
nav: {
|
|
338
|
+
emit: {
|
|
339
|
+
'nav-underline-gap': { sem: 'space.4' },
|
|
340
|
+
'nav-underline-border-width': {
|
|
341
|
+
sem: 'border-width.medium',
|
|
342
|
+
cls: 'approximated',
|
|
343
|
+
note: 'medium rung (2px) ≡ Bootstrap’s .125rem at a 16px root; unit semantics differ (px vs rem-relative)',
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
navbar: {
|
|
348
|
+
emit: {
|
|
349
|
+
'navbar-nav-link-padding-x': { sem: 'space.2' },
|
|
350
|
+
'navbar-brand-margin-end': { sem: 'space.4' },
|
|
351
|
+
'navbar-toggler-padding-y': { sem: 'space.1' },
|
|
352
|
+
'navbar-toggler-padding-x': { sem: 'space.3' },
|
|
353
|
+
'navbar-toggler-transition': T_FAST,
|
|
354
|
+
},
|
|
355
|
+
drop: {
|
|
356
|
+
'navbar-light-toggler-icon-bg': { cls: 'unsupported', note: N_ASSET },
|
|
357
|
+
'navbar-dark-toggler-icon-bg': { cls: 'unsupported', note: N_ASSET },
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
breadcrumb: {
|
|
361
|
+
emit: {
|
|
362
|
+
'breadcrumb-padding-y': { sem: 'space.0' },
|
|
363
|
+
'breadcrumb-padding-x': { sem: 'space.0' },
|
|
364
|
+
'breadcrumb-item-padding-x': { sem: 'space.2' },
|
|
365
|
+
'breadcrumb-margin-bottom': { sem: 'space.4' },
|
|
366
|
+
},
|
|
367
|
+
drop: { 'breadcrumb-divider': { cls: 'dropped', note: N_STRUCT + ' (divider glyph)' } },
|
|
368
|
+
},
|
|
369
|
+
pagination: {
|
|
370
|
+
emit: {
|
|
371
|
+
'pagination-padding-y': {
|
|
372
|
+
sem: 'space.2',
|
|
373
|
+
cls: 'approximated',
|
|
374
|
+
note: '.375rem — between rungs; matches the button/field padding intent',
|
|
375
|
+
},
|
|
376
|
+
'pagination-padding-x': { sem: 'space.3' },
|
|
377
|
+
'pagination-padding-y-sm': { sem: 'space.1' },
|
|
378
|
+
'pagination-padding-x-sm': { sem: 'space.2' },
|
|
379
|
+
'pagination-padding-y-lg': { sem: 'space.3' },
|
|
380
|
+
'pagination-padding-x-lg': { sem: 'space.6' },
|
|
381
|
+
'pagination-transition': T_FAST,
|
|
382
|
+
},
|
|
383
|
+
drop: {
|
|
384
|
+
'pagination-focus-outline': {
|
|
385
|
+
cls: 'dropped',
|
|
386
|
+
note: N_STRUCT + ' (focus handled by focus-ring, driven semantically)',
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
// ---- surfaces / overlays ---------------------------------------------------
|
|
392
|
+
card: {},
|
|
393
|
+
accordion: {
|
|
394
|
+
emit: {
|
|
395
|
+
'accordion-padding-y': { sem: 'space.4' },
|
|
396
|
+
'accordion-padding-x': { sem: 'space.5' },
|
|
397
|
+
'accordion-icon-transition': {
|
|
398
|
+
trans: { duration: 'duration.normal', easing: 'easing.standard' },
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
drop: {
|
|
402
|
+
'accordion-icon-width': { cls: 'unsupported', note: N_ICON + ' (chevron)' },
|
|
403
|
+
'accordion-icon-transform': { cls: 'dropped', note: N_STRUCT },
|
|
404
|
+
'accordion-button-icon': { cls: 'unsupported', note: N_ASSET },
|
|
405
|
+
'accordion-button-active-icon': { cls: 'unsupported', note: N_ASSET },
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
dropdown: {
|
|
409
|
+
emit: {
|
|
410
|
+
'dropdown-padding-x': { sem: 'space.0' },
|
|
411
|
+
'dropdown-padding-y': { sem: 'space.2' },
|
|
412
|
+
'dropdown-spacer': {
|
|
413
|
+
sem: 'space.1',
|
|
414
|
+
cls: 'approximated',
|
|
415
|
+
note: '.125rem — sub-rung; nearest meaning',
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
drop: { 'dropdown-min-width': { cls: 'unsupported', note: N_BESPOKE } },
|
|
419
|
+
},
|
|
420
|
+
modal: {
|
|
421
|
+
emit: {
|
|
422
|
+
'modal-footer-margin-between': { sem: 'space.2' },
|
|
423
|
+
'modal-dialog-margin': { sem: 'space.2' },
|
|
424
|
+
'modal-dialog-margin-y-sm-up': {
|
|
425
|
+
sem: 'space.6',
|
|
426
|
+
cls: 'approximated',
|
|
427
|
+
note: '1.75rem — between rungs; nearest meaning',
|
|
428
|
+
},
|
|
429
|
+
'modal-transition': { trans: { duration: 'duration.normal', easing: 'easing.enter' } },
|
|
430
|
+
// Overlay pass (proposal 0003): Bootstrap splits the veil into a colour
|
|
431
|
+
// and an opacity; the IR's `scrim` already carries BOTH (its value has an
|
|
432
|
+
// alpha channel), so this needs no new vocabulary — just the two halves
|
|
433
|
+
// read out separately. `$offcanvas-backdrop-*` chains from these, so the
|
|
434
|
+
// offcanvas veil follows for free. Was `unsupported` on the claim that
|
|
435
|
+
// "scrim covers the color, not the alpha" — that claim was wrong.
|
|
436
|
+
'modal-backdrop-bg': { sem: 'color.scrim', part: 'opaque' },
|
|
437
|
+
'modal-backdrop-opacity': { sem: 'color.scrim', part: 'alpha' },
|
|
438
|
+
},
|
|
439
|
+
drop: {
|
|
440
|
+
'modal-sm': { cls: 'unsupported', note: N_BESPOKE + ' (dialog width steps)' },
|
|
441
|
+
'modal-md': { cls: 'unsupported', note: N_BESPOKE + ' (dialog width steps)' },
|
|
442
|
+
'modal-lg': { cls: 'unsupported', note: N_BESPOKE + ' (dialog width steps)' },
|
|
443
|
+
'modal-xl': { cls: 'unsupported', note: N_BESPOKE + ' (dialog width steps)' },
|
|
444
|
+
'modal-fade-transform': { cls: 'dropped', note: N_STRUCT },
|
|
445
|
+
'modal-show-transform': { cls: 'dropped', note: N_STRUCT },
|
|
446
|
+
'modal-scale-transform': { cls: 'dropped', note: N_STRUCT },
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
offcanvas: {
|
|
450
|
+
emit: {
|
|
451
|
+
'offcanvas-transition-duration': {
|
|
452
|
+
sem: 'duration.normal',
|
|
453
|
+
cls: 'approximated',
|
|
454
|
+
note: '.3s vs 250ms — nearest rung',
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
drop: {
|
|
458
|
+
'offcanvas-horizontal-width': { cls: 'unsupported', note: N_BESPOKE + ' (panel width)' },
|
|
459
|
+
'offcanvas-vertical-height': { cls: 'unsupported', note: N_BESPOKE + ' (panel height)' },
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
tooltip: {
|
|
463
|
+
emit: {
|
|
464
|
+
// Promoted by proposal 0004 on the strongest two-target evidence yet:
|
|
465
|
+
// PrimeNG constrains the same element the same way at the same measure
|
|
466
|
+
// (`tooltip.root.maxWidth: 12.5rem` — exactly Bootstrap's 200px), and it
|
|
467
|
+
// is one of only two `maxWidth` slots in its whole 2759-slot surface.
|
|
468
|
+
// The catalog slot has no default, so this only emits when authored;
|
|
469
|
+
// otherwise Bootstrap's own 200px stands (resolveEmits' absent-source
|
|
470
|
+
// path, AL5).
|
|
471
|
+
'tooltip-max-width': { comp: 'tooltip.max-width' },
|
|
472
|
+
},
|
|
473
|
+
drop: {
|
|
474
|
+
'tooltip-opacity': { cls: 'unsupported', note: N_OPACITY },
|
|
475
|
+
'tooltip-arrow-width': { cls: 'dropped', note: N_STRUCT + ' (arrow geometry)' },
|
|
476
|
+
'tooltip-arrow-height': { cls: 'dropped', note: N_STRUCT + ' (arrow geometry)' },
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
popover: {
|
|
480
|
+
emit: { 'popover-header-padding-y': { sem: 'space.2' } },
|
|
481
|
+
drop: {
|
|
482
|
+
'popover-max-width': { cls: 'unsupported', note: N_BESPOKE },
|
|
483
|
+
'popover-arrow-width': { cls: 'dropped', note: N_STRUCT + ' (arrow geometry)' },
|
|
484
|
+
'popover-arrow-height': { cls: 'dropped', note: N_STRUCT + ' (arrow geometry)' },
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
toast: {
|
|
488
|
+
emit: {
|
|
489
|
+
'toast-padding-x': { sem: 'space.3' },
|
|
490
|
+
'toast-padding-y': { sem: 'space.2' },
|
|
491
|
+
'toast-font-size': {
|
|
492
|
+
sem: 'type.size.sm',
|
|
493
|
+
cls: 'approximated',
|
|
494
|
+
note: '.875rem vs the 0.8rem scale rung — nearest meaning',
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
drop: { 'toast-max-width': { cls: 'unsupported', note: N_BESPOKE } },
|
|
498
|
+
},
|
|
499
|
+
|
|
500
|
+
// ---- feedback / status -------------------------------------------------------
|
|
501
|
+
alert: {
|
|
502
|
+
emit: { 'alert-margin-bottom': { sem: 'space.4' } },
|
|
503
|
+
drop: {
|
|
504
|
+
'alert-bg-scale': { cls: 'dropped', note: N_PARAM },
|
|
505
|
+
'alert-border-scale': { cls: 'dropped', note: N_PARAM },
|
|
506
|
+
'alert-color-scale': { cls: 'dropped', note: N_PARAM },
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
'list-group': {
|
|
510
|
+
drop: {
|
|
511
|
+
'list-group-item-bg-scale': { cls: 'dropped', note: N_PARAM },
|
|
512
|
+
'list-group-item-color-scale': { cls: 'dropped', note: N_PARAM },
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
badge: {
|
|
516
|
+
emit: {
|
|
517
|
+
'badge-font-size': { sem: 'type.size.xs', cls: 'approximated', note: N_EM },
|
|
518
|
+
'badge-padding-y': { sem: 'space.1', cls: 'approximated', note: N_EM },
|
|
519
|
+
'badge-padding-x': { sem: 'space.2', cls: 'approximated', note: N_EM },
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
progress: {
|
|
523
|
+
emit: {
|
|
524
|
+
'progress-height': { sem: 'space.4' },
|
|
525
|
+
'progress-bar-transition': {
|
|
526
|
+
trans: { duration: 'duration.slower', easing: 'easing.standard' },
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
drop: {
|
|
530
|
+
'progress-bar-animation-timing': {
|
|
531
|
+
cls: 'dropped',
|
|
532
|
+
note: N_STRUCT + ' (stripe animation mechanics)',
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
spinner: {
|
|
537
|
+
emit: {
|
|
538
|
+
'spinner-animation-speed': {
|
|
539
|
+
sem: 'duration.slower',
|
|
540
|
+
cls: 'approximated',
|
|
541
|
+
note: '.75s rotation period vs 600ms — nearest rung',
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
drop: {
|
|
545
|
+
'spinner-width': { cls: 'unsupported', note: N_BESPOKE },
|
|
546
|
+
'spinner-width-sm': { cls: 'unsupported', note: N_BESPOKE },
|
|
547
|
+
'spinner-border-width': {
|
|
548
|
+
cls: 'unsupported',
|
|
549
|
+
note: N_BESPOKE + ' (em-relative ring thickness)',
|
|
550
|
+
},
|
|
551
|
+
'spinner-border-width-sm': {
|
|
552
|
+
cls: 'unsupported',
|
|
553
|
+
note: N_BESPOKE + ' (em-relative ring thickness)',
|
|
554
|
+
},
|
|
555
|
+
'spinner-vertical-align': { cls: 'dropped', note: N_STRUCT },
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
carousel: {
|
|
559
|
+
emit: {
|
|
560
|
+
'carousel-caption-padding-y': { sem: 'space.5' },
|
|
561
|
+
'carousel-caption-spacer': { sem: 'space.5' },
|
|
562
|
+
'carousel-control-transition': T_FAST,
|
|
563
|
+
'carousel-indicator-transition': {
|
|
564
|
+
trans: { duration: 'duration.slower', easing: 'easing.standard' },
|
|
565
|
+
},
|
|
566
|
+
'carousel-transition-duration': { sem: 'duration.slower' },
|
|
567
|
+
},
|
|
568
|
+
drop: {
|
|
569
|
+
'carousel-control-width': { cls: 'dropped', note: N_STRUCT },
|
|
570
|
+
'carousel-control-opacity': { cls: 'unsupported', note: N_OPACITY },
|
|
571
|
+
'carousel-control-hover-opacity': { cls: 'unsupported', note: N_OPACITY },
|
|
572
|
+
'carousel-indicator-width': { cls: 'unsupported', note: N_ARCH_DISAGREE + ' (PrimeNG has indicator width+height too, but 28x8 rounded vs Bootstrap 30x3 hairline — one value would give two differently-wrong carousels)' },
|
|
573
|
+
'carousel-indicator-height': {
|
|
574
|
+
cls: 'unsupported',
|
|
575
|
+
note: N_ARCH_DISAGREE + ' (indicator, height axis)',
|
|
576
|
+
},
|
|
577
|
+
'carousel-indicator-hit-area-height': {
|
|
578
|
+
cls: 'dropped',
|
|
579
|
+
note: N_STRUCT + ' (touch target mechanics)',
|
|
580
|
+
},
|
|
581
|
+
'carousel-indicator-spacer': {
|
|
582
|
+
cls: 'unsupported',
|
|
583
|
+
note: N_BESPOKE + ' (gap between indicators — PrimeNG has no counterpart at all)',
|
|
584
|
+
},
|
|
585
|
+
'carousel-indicator-opacity': { cls: 'unsupported', note: N_OPACITY },
|
|
586
|
+
'carousel-indicator-active-opacity': { cls: 'unsupported', note: N_OPACITY },
|
|
587
|
+
'carousel-caption-width': { cls: 'dropped', note: N_STRUCT },
|
|
588
|
+
'carousel-control-icon-width': { cls: 'unsupported', note: N_ICON + ' (prev/next control)' },
|
|
589
|
+
'carousel-control-prev-icon-bg': { cls: 'unsupported', note: N_ASSET },
|
|
590
|
+
'carousel-control-next-icon-bg': { cls: 'unsupported', note: N_ASSET },
|
|
591
|
+
'carousel-dark-control-icon-filter': { cls: 'dropped', note: N_FILTER },
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
/** Bootstrap's cascade no-op markers — mechanically classified. */
|
|
597
|
+
const INHERIT_MARKERS = new Set(['null', 'inherit', 'transparent', 'currentcolor']);
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* The Bootstrap counterpart of PrimeNG's `inherited` reason (AL3,
|
|
601
|
+
* surface-coverage.js): a slot left at a cascade no-op whose EFFECTIVE value
|
|
602
|
+
* still comes from something this exporter drives. PrimeNG counts those as real
|
|
603
|
+
* coverage (`derived`) because that is how PrimeNG is designed to be themed;
|
|
604
|
+
* Bootstrap's `null` markers on inherited CSS properties work identically, and
|
|
605
|
+
* classifying them as `dropped` under-reported the same mechanism on the other
|
|
606
|
+
* target. Splitting them is a correction, not an inflation.
|
|
607
|
+
*
|
|
608
|
+
* Membership is per-name and deliberately conservative — the split is a real
|
|
609
|
+
* judgment (does this property inherit at all, and is the ancestor value one we
|
|
610
|
+
* drive?), so it is not inferred from the marker. Anything not listed here keeps
|
|
611
|
+
* falling through to `inherit-default` (dropped), which is the safe direction:
|
|
612
|
+
* a new upstream `null` variable is never silently claimed as covered.
|
|
613
|
+
*
|
|
614
|
+
* The two rejected shapes, for the record:
|
|
615
|
+
* - non-inherited properties (box-shadow, background, border-radius, height,
|
|
616
|
+
* margin, transition, filter): `null` means "no declaration", so nothing
|
|
617
|
+
* reaches them — those stay dropped.
|
|
618
|
+
* - inherited-but-structural (white-space, cursor): the value that reaches
|
|
619
|
+
* them is real, but it is not a theme value.
|
|
620
|
+
*/
|
|
621
|
+
const INHERITS_DRIVEN = {
|
|
622
|
+
'hr-color': 'the body text color (driven) — the divider is currentColor at $hr-opacity',
|
|
623
|
+
'hr-border-color': 'currentColor via $hr-color, so it follows the driven body color per mode',
|
|
624
|
+
'input-btn-font-family': 'the driven body font stack',
|
|
625
|
+
'form-text-font-style': 'the driven body type',
|
|
626
|
+
'form-text-font-weight': 'the driven body type',
|
|
627
|
+
'form-label-font-style': 'the driven body type',
|
|
628
|
+
'form-label-color': 'the driven body text color',
|
|
629
|
+
'input-disabled-color': 'the driven $input-color (Bootstrap keeps the enabled color when disabled)',
|
|
630
|
+
'form-check-label-color': 'the driven body text color',
|
|
631
|
+
'form-select-disabled-color': 'the driven $form-select-color',
|
|
632
|
+
'nav-link-font-size': 'the driven body type',
|
|
633
|
+
'nav-link-font-weight': 'the driven body type',
|
|
634
|
+
'card-title-color': 'the driven card/body text color',
|
|
635
|
+
'card-subtitle-color': 'the driven card/body text color',
|
|
636
|
+
'card-cap-color': 'the driven card/body text color',
|
|
637
|
+
'card-color': 'the driven body text color',
|
|
638
|
+
'toast-color': 'the driven body text color',
|
|
639
|
+
'pre-color': 'the driven body text color',
|
|
640
|
+
'breadcrumb-font-size': 'the driven body type',
|
|
641
|
+
'form-feedback-tooltip-line-height': 'the driven body line height',
|
|
642
|
+
'tooltip-arrow-color': 'the driven --bs-tooltip-bg (Bootstrap points the arrow at the bubble color)',
|
|
643
|
+
'table-group-separator-color': 'currentColor — the driven table text color',
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Classify one inventory variable (scope === 'component').
|
|
648
|
+
* Returns { emit } | { drop } | { mech: 'chained'|'follows-global'|'inherit-default' } | null.
|
|
649
|
+
* null = unclassified — check-bootstrap-surface.mjs fails the build on it.
|
|
650
|
+
*/
|
|
651
|
+
export function coverageForVariable(v) {
|
|
652
|
+
const d = DESCRIPTORS[v.family] ?? {};
|
|
653
|
+
const emit = d.emit?.[v.name];
|
|
654
|
+
const drop = d.drop?.[v.name];
|
|
655
|
+
if (emit && drop) return { emit, drop };
|
|
656
|
+
if (emit) return { emit };
|
|
657
|
+
if (drop) return { drop };
|
|
658
|
+
if (v.refs.length > 0) return { mech: 'chained' };
|
|
659
|
+
if (v.aliasesGlobalCssVar) return { mech: 'follows-global' };
|
|
660
|
+
if (INHERIT_MARKERS.has(v.value)) {
|
|
661
|
+
const from = INHERITS_DRIVEN[v.name];
|
|
662
|
+
return from ? { mech: 'inherits-driven', from } : { mech: 'inherit-default' };
|
|
663
|
+
}
|
|
664
|
+
return null;
|
|
665
|
+
}
|