Variables
Index
input/mask-presets/registry.ts
MaskPresetKey[]['phone', 'date', 'iban', 'zip']unknownnew Map<MaskPresetKey, Promise<void>>()unknownsignal<MaskPresetTables>({})forms/input/password-strength.directive.ts
unknown(a: PasswordStrengthResult, b: PasswordStrengthResult): boolean =>
a.score === b.score && a.label === b.labelaudio/tone-generator/tone-generator.ts
Attack ramp, seconds — lifts gain from 0 so the oscillator start is click-free.
0.005CngxAudioToneGeneratorFactoryPure OscillatorNode synthesis — no audio assets. Each tone is an
oscillator through a per-note gain envelope (5 ms attack, 30 ms release)
into the engine's master-volume node. The envelope is not decoration: a
bare start()/stop() on a full-amplitude oscillator produces an audible
click at both ends; ramping in and out removes it.
create* pure factory (no inject()), so it composes inside the engine and
runs against the shared createAudioContextMock in specs.
(deps) => {
function schedule(
freq: number,
durationMs: number,
opts: ToneOptions | undefined,
whenSec: number,
): void {
const ctx = deps.context();
const dest = deps.destination();
const start = ctx.currentTime + whenSec + (opts?.when ?? 0);
const durSec = Math.max(durationMs / 1000, MIN_DURATION_SEC);
const peak = opts?.gain ?? DEFAULT_TONE_GAIN;
const osc = ctx.createOscillator();
osc.type = opts?.type ?? 'sine';
osc.frequency.setValueAtTime(freq, start);
const env = ctx.createGain();
const holdStart = start + ATTACK_SEC;
const releaseStart = start + durSec - RELEASE_SEC;
env.gain.setValueAtTime(0, start);
env.gain.linearRampToValueAtTime(peak, holdStart);
env.gain.setValueAtTime(peak, releaseStart);
env.gain.linearRampToValueAtTime(0, start + durSec);
osc.connect(env);
env.connect(dest);
osc.start(start);
osc.stop(start + durSec);
osc.onended = () => {
osc.disconnect();
env.disconnect();
};
}
return {
tone(freq, durationMs, opts) {
schedule(freq, durationMs, opts, 0);
},
sequence(steps) {
let offsetSec = 0;
for (const step of steps) {
offsetSec += (step.delay ?? 0) / 1000;
schedule(step.freq, step.duration, { type: step.type, gain: step.gain }, offsetSec);
offsetSec += Math.max(step.duration / 1000, MIN_DURATION_SEC);
}
},
};
}Default peak gain for a single tone (before the engine's master volume). Exported so a per-element volume multiplier can scale against the same baseline the generator uses.
0.2unknownA tone shorter than attack+release cannot host both ramps; clamp up to this floor.
ATTACK_SEC + RELEASE_SECchart/chart/template-slots.ts
audio/config/audio-config.ts
CngxAudioConfigLibrary defaults — English, browser-native.
{
muted: false,
volume: 1,
respectReducedMotion: true,
debounceMs: 100,
earcons: {},
}audio/event-mode/parse-bindings.ts
unknownDOM events [cngxAudio] may bind an earcon to.
[
'click',
'focus',
'blur',
'pointerenter',
'pointerleave',
'submit',
'change',
'input',
] as constunknownnew Set<string>(CNGX_AUDIO_DOM_EVENTS)unknownLifecycle keys that belong to [cngxAudioStatus], rejected here.
[
'pending',
'succeeded',
'failed',
'loading',
'refreshing',
'success',
'error',
'idle',
] as constunknownnew Set<string>(LIFECYCLE_KEYS)audio/status-mode/parse-status-bindings.ts
unknownDOM-event keys that belong to [cngxAudio], rejected here.
[
'click',
'focus',
'blur',
'pointerenter',
'pointerleave',
'submit',
'change',
'input',
] as constunknownnew Set<string>(CNGX_AUDIO_STATUS_DOM_EVENTS)Lifecycle keys [cngxAudioStatus] accepts, mapped to the canonical
AsyncStatus the transition tracker emits. Two friendly aliases are folded
in: succeeded -> success and failed -> error, so both the raw status and
the past-tense reading parse to the same earcon.
{
idle: 'idle',
loading: 'loading',
pending: 'pending',
refreshing: 'refreshing',
success: 'success',
succeeded: 'success',
error: 'error',
failed: 'error',
}a11y/focus/focus-restore.directive.ts
CSS selector matching natively-focusable elements: links with an
href, the enabled form controls, and anything with an explicit
non-negative tabindex. Form controls carry :not(:disabled) because
a disabled control is not in the tab order - without it a descendant
probe would count a disabled button as focusable and a restore target
could land on an unfocusable element. Single source of truth for "is
this focusable" - the element-level CngxFocusRestore predicate
matches against it, and descendant probes (e.g. "does this panel
contain a focusable child?") query against it. Keeping one selector
stops the two uses from drifting apart.
'a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"])'ui/collection/incremental-list-config.ts
CngxIncrementalListConfigLibrary defaults - English. Override via provideIncrementalListConfig.
{
ariaLabels: {
loading: 'Loading',
empty: 'Nothing here yet',
error: 'Failed to load',
pageError: 'Failed to load more',
retry: 'Retry',
endReached: (total) => `All ${total} loaded`,
loadedMore: (count, total) => `${count} more loaded. ${total} total.`,
},
}interactive/nav/nav-config.ts
ui/paginator/paginator-config.ts
CngxPaginatorConfigLibrary defaults - English. Override via provideCngxPaginatorConfig.
{
ariaLabels: {
label: 'Pagination',
first: 'First page',
previous: 'Previous page',
next: 'Next page',
last: 'Last page',
page: (page) => `Page ${page}`,
morePages: 'More pages',
itemsPerPage: 'Items per page',
goToPage: 'Go to page',
pageOfPages: 'Select page',
loadMore: 'Load more',
allLoaded: (total) => `All ${total} loaded`,
bucket: (label) => label,
emptyBucket: (label) => `${label}, no items`,
bucketGroup: 'Categories',
railPosition: 'Page position',
},
announcements: {
pageChange: (page, totalPages) => `Page ${page} of ${totalPages}`,
loading: 'Loading',
updated: 'Updated',
},
formats: {
// The readout segments render the formatter output as sanitised HTML, so the
// current value (the page, or the item range) is emphasised with `<b>`;
// Angular's sanitiser keeps `<b>` and strips anything dangerous.
range: (start, end, total) => `<b>${start}-${end}</b> of ${total}`,
pageStatus: (page, totalPages) => `Page <b>${page}</b> of ${totalPages}`,
},
// Includes the brain's default pageSize (10) so the trigger value is always a
// member of the panel; a common data-table ladder, locale-neutral.
pageSizeOptions: [10, 25, 50, 100],
}ui/paginator/paginator-glyphs.ts
unknownDefault text glyphs for the paginator segments. Internal and non-exported -
mirrors CNGX_SELECT_GLYPHS. The library ships no icon font; these unicode
marks are the out-of-the-box chevrons and the overflow ellipsis. Consumers
restyle visually via CSS rather than by swapping these characters.
Centralised so the chevron / ellipsis literals live in one place instead of being duplicated across the segment classes.
{
/** First-page nav (double left-pointing angle quotation mark). */
first: '«',
/** Previous-page nav (single left-pointing angle quotation mark). */
previous: '‹',
/** Next-page nav (single right-pointing angle quotation mark). */
next: '›',
/** Last-page nav (double right-pointing angle quotation mark). */
last: '»',
/** Ellipsis trigger that opens the hidden-pages overflow menu. */
more: '…',
/** Dropdown caret on the page-size / page-of-pages select triggers. */
caret: '▾',
} as constinteractive/menu/submenu-defaults.ts
unknownRecommended position-try-fallbacks chain for menu submenu popovers.
Consumers wire the const on the submenu's cngxPopover element:
<div cngxPopover [positionTryFallbacks]="CNGX_SUBMENU_TRY_FALLBACKS">
<!-- submenu items -->
</div>The chain matches the
projects/forms/select/shared/select-base.css:759 precedent
(flip-block, flip-inline, flip-block flip-inline) — flip-inline
is the dominant submenu clip case (right submenu hits the viewport
right edge → flips to the left side of its parent item),
flip-block covers the vertical case, and the composed form
covers diagonal clipping.
CngxMenuItemSubmenu emits a one-shot dev-mode warning when a
submenu popover ships without positionTryFallbacks — the const
is documented as the canonical opt-in.
Typed as a string-literal tuple via as const rather than the
PopoverPositionTryFallback union from @cngx/common/popover —
the cross-entry-point import would reverse the existing
popover -> interactive dependency direction and break ng-packagr.
Consumers binding the const to [positionTryFallbacks] get
compile-time type checking from the input's typed signature on the
popover side.
Object.freeze([
'flip-inline',
'flip-block',
'flip-block flip-inline',
] as const)audio/engine/audio-engine.ts
CngxAudioEngineFactoryCreate the audio engine: composes the autoplay gate, tone generator, and
debouncer over one lazily-created shared AudioContext. Reduced-motion muting
reuses the existing injectMediaQuery('(prefers-reduced-motion: reduce)')
primitive rather than adding another matchMedia reader.
The four mute conditions are enforced centrally in play()/tone()/
sequence(): global mute, reduced-motion, and autoplay-not-armed here;
per-element [audioDisabled] in the directives.
Runs in an injection context (calls inject() for DOCUMENT, DestroyRef, the
tone-generator factory, and the reduced-motion query). The engine instance is
resolved through CNGX_AUDIO_ENGINE_FACTORY; scope it to a subtree with
provideCngxAudioAt(...) rather than calling this directly.
(options) => {
const doc = inject(DOCUMENT);
const destroyRef = inject(DestroyRef);
const toneGeneratorFactory = inject(CNGX_AUDIO_TONE_GENERATOR_FACTORY);
const config = injectAudioConfig();
const reducedMotion = injectMediaQuery('(prefers-reduced-motion: reduce)');
const muted = signal(config.muted);
const volume = signal(clampVolume(config.volume));
const status = signal<AudioStatus>('idle');
const lastPlayed = signal<string | null>(null);
const gate = createAutoplayGate({ target: doc, destroyRef });
const debouncer = createDebouncer({ windowMs: config.debounceMs });
const earcons = new Map<string, EarconConfig>(
Object.entries({ ...CNGX_AUDIO_DEFAULT_EARCONS, ...config.earcons }),
);
let context: BaseAudioContext | null = null;
let masterGain: GainNode | null = null;
const contextFactory =
options?.contextFactory ??
((): BaseAudioContext | null => {
const view = doc.defaultView as
| (Window & {
AudioContext?: typeof AudioContext;
webkitAudioContext?: typeof AudioContext;
})
| null;
const Ctor = view?.AudioContext ?? view?.webkitAudioContext;
return Ctor ? new Ctor() : null;
});
const toneGenerator = toneGeneratorFactory({
context: () => context!,
destination: () => masterGain!,
});
function resumeIfArmed(): void {
if (context && gate.armed() && context.state === 'suspended') {
void (context as AudioContext).resume().then(
() => status.set('running'),
() => undefined,
);
}
}
function ensureContext(): BaseAudioContext | null {
if (context) {
return context;
}
const created = contextFactory();
if (!created) {
status.set('unsupported');
return null;
}
context = created;
masterGain = created.createGain();
masterGain.gain.value = volume();
masterGain.connect(created.destination);
status.set(created.state as AudioStatus);
resumeIfArmed();
return context;
}
function blocked(): boolean {
return muted() || (config.respectReducedMotion && reducedMotion()) || !gate.armed();
}
// Volume is communicated to the master node reactively — the single writer is
// setVolume(), but binding through an effect keeps the node in sync from any
// future volume source too.
effect(() => {
const v = volume();
if (masterGain) {
masterGain.gain.value = v;
}
});
// Suspend on tab hide (frees the audio hardware); resume on return if armed.
const onVisibility = (): void => {
if (!context) {
return;
}
if (doc.hidden) {
if (context.state === 'running') {
void (context as AudioContext).suspend().then(
() => status.set('suspended'),
() => undefined,
);
}
} else {
resumeIfArmed();
}
};
doc.addEventListener('visibilitychange', onVisibility);
destroyRef.onDestroy(() => {
doc.removeEventListener('visibilitychange', onVisibility);
// close() rejects on an already-closed context; swallow it so a double
// destroy does not surface an unhandled rejection.
const closing = (context as AudioContext | null)?.close?.();
void closing?.then(
() => undefined,
() => undefined,
);
});
return {
play(name, scale) {
if (blocked()) {
return;
}
if (!ensureContext()) {
return;
}
resumeIfArmed();
if (!debouncer.shouldFire(name)) {
return;
}
const earcon = earcons.get(name);
if (!earcon) {
if (isDevMode()) {
console.warn(
`[cngxAudio] Unknown earcon "${name}". Register it via withEarcons({...}) or engine.register(name, config).`,
);
}
return;
}
toneGenerator.sequence(scaleSteps(earcon.sequence, scale));
lastPlayed.set(name);
},
tone(freq, durationMs, opts, scale) {
if (blocked() || !ensureContext()) {
return;
}
resumeIfArmed();
toneGenerator.tone(
freq,
durationMs,
scale === undefined ? opts : { ...opts, gain: scaleGain(opts?.gain, scale) },
);
},
sequence(steps, scale) {
if (blocked() || !ensureContext()) {
return;
}
resumeIfArmed();
toneGenerator.sequence(scaleSteps(steps, scale));
},
register(name, earconConfig) {
earcons.set(name, earconConfig);
},
armAutoplay() {
gate.arm();
resumeIfArmed();
},
setMuted(next) {
muted.set(next);
},
setVolume(next) {
volume.set(clampVolume(next));
},
muted: muted.asReadonly(),
volume: volume.asReadonly(),
status: status.asReadonly(),
lastPlayed: lastPlayed.asReadonly(),
};
}interactive/menu/menu-radio-controller.ts
CngxMenuRadioGroupFactoryPure factory wiring a CngxMenuRadioGroup from already-created
signals. CngxMenuGroup implements the contract directly; this factory
exists for hand-built menu hosts that do not declare a directive but
still need to provide CNGX_MENU_RADIO_GROUP.
({ selectedValue, name }) => ({
selectedValue,
name,
select(value) {
selectedValue.set(value);
},
})paginator/segments/paginator-page-window.token.ts
CngxPaginatorPageWindowFactoryDefault page-window factory. Resolves to the built-in pageWindow math
(one sibling each side, one pinned boundary at each end at the 1 / 1
defaults). The segment builds its window via
inject(CNGX_PAGINATOR_PAGE_WINDOW_FACTORY)() rather than calling this
directly, mirroring createPaginatorAnnouncer.
() => pageWindowinput/mask-presets/date-formats.ts
Record{
'de-DE': '00.00.00',
'de-AT': '00.00.00',
'de-CH': '00.00.00',
'fr-FR': '00/00/00',
'fr-BE': '00/00/00',
'fr-CH': '00.00.00',
'fr-CA': '00-00-00',
'es-ES': '00/00/00',
'es-MX': '00/00/00',
'es-AR': '00/00/00',
'it-IT': '00/00/00',
'it-CH': '00.00.00',
'pt-PT': '00/00/00',
'pt-BR': '00/00/00',
'nl-NL': '00-00-00',
'nl-BE': '00/00/00',
'ru-RU': '00.00.00',
'ja-JP': '00/00/00',
'zh-CN': '00-00-00',
'zh-TW': '00/00/00',
'zh-HK': '00/00/00',
'ko-KR': '00.00.00',
'en-US': '00/00/00',
'en-GB': '00/00/00',
'en-AU': '00/00/00',
'en-CA': '00-00-00',
'en-IE': '00/00/00',
'pl-PL': '00.00.00',
'cs-CZ': '00.00.00',
'sk-SK': '00.00.00',
'hu-HU': '00.00.00',
'ro-RO': '00.00.00',
'bg-BG': '00.00.00',
'el-GR': '00/00/00',
'sv-SE': '00-00-00',
'nb-NO': '00.00.00',
'da-DK': '00-00-00',
'fi-FI': '00.00.00',
'hr-HR': '00.00.00',
'sr-RS': '00.00.00',
'sl-SI': '00.00.00',
'uk-UA': '00.00.00',
'lt-LT': '00-00-00',
'lv-LV': '00.00.00',
'et-EE': '00.00.00',
'is-IS': '00.00.00',
'mt-MT': '00/00/00',
'ga-IE': '00/00/00',
'lb-LU': '00.00.00',
'tr-TR': '00.00.00',
}interactive/breadcrumb/breadcrumb-responsive.ts
CngxBreadcrumbWidthTier[]Default width tiers, matching the headless responsive-collapse recipe: 640px
and up shows 6, 440px and up shows 4, anything narrower shows 2. The 0 floor
guarantees a match for every non-negative width.
[
{ minWidth: 640, maxVisible: 6 },
{ minWidth: 440, maxVisible: 4 },
{ minWidth: 0, maxVisible: 2 },
]forms/field/form-field.token.ts
ConstraintHintFormattersEnglish default formatters for constraint hints.
{
lengthRange: (min, max) => `${min}–${max} characters`,
minLength: (min) => `Min. ${min} characters`,
maxLength: (max) => `Max. ${max} characters`,
valueRange: (min, max) => `${min}–${max}`,
minValue: (min) => `Min. ${min}`,
maxValue: (max) => `Max. ${max}`,
extra: () => [],
}forms/input/input-config.ts
InputAriaLabelsEnglish default ARIA labels for @cngx/forms/input. Directives read a key
as config.ariaLabels?.<key> ?? DEFAULT_INPUT_ARIA_LABELS.<key>.
{
clear: 'Clear',
otpGroup: 'One-time code',
otpSlot: (index, length) => `Digit ${index + 1} of ${length}`,
otpComplete: 'Code complete',
copySuccess: 'Copied',
copyError: 'Copy failed',
fileDropZone: 'File drop zone',
capsLockOn: 'Caps Lock is on',
passwordStrength: (label) => `Password strength: ${label}`,
inputRejected: 'Character not allowed',
sensitiveReveal: 'Value revealed',
sensitiveHide: 'Value hidden',
ratingValue: (value, max) => `${value} of ${max}`,
ratingItem: (step, max) => `${step} of ${max}`,
phoneCountry: 'Country',
}interactive/menu/menu-config.ts
CngxMenuConfigLibrary-default menu configuration. English-only - German (or any
other locale) is consumer-supplied via withAriaLabels per the
feedback_en_default_locale rule.
{
ariaLabels: {
submenuOpened: 'Submenu opened',
submenuClosed: 'Submenu closed',
itemActivated: 'Item activated',
itemDisabled: 'Item disabled',
menuDismissed: 'Menu dismissed',
},
typeaheadDebounce: 300,
submenuOpenDelay: 0,
submenuCloseDelay: 150,
closeOnSelect: true,
dismissOnOutsideClick: true,
dismissOnScroll: false,
dismissOnBlur: true,
}forms/filter-builder/filter-builder.types.ts
unknownDefault operator lists per builtin editor type. Consumers extend via withDefaultOperators({...}).
{
string: ['contains', 'eq', 'neq', 'startsWith', 'endsWith', 'isEmpty', 'isNotEmpty'],
number: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'isEmpty', 'isNotEmpty'],
date: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'isEmpty', 'isNotEmpty'],
boolean: ['eq', 'neq'],
} as const satisfies Record<FilterEditorType, readonly string[]>tabs/scroll-sync/organism-scroll-sync.ts
ScrollIntoViewOptions{
behavior: 'smooth',
block: 'nearest',
inline: 'center',
}ui/mat-paginator/mat-paginator-bridge.directive.ts
unknownEnglish default announcement. Localise via [announceLabel].
(c: CngxMatPaginatorAnnounceContext): string =>
`Page ${c.page} of ${c.totalPages}, showing items ${c.start} to ${c.end} of ${c.total}`common/stepper/create-stepper-host-proxy.ts
CngxStepNode[]Object.freeze([])data/recycler/range-computer.ts
RangeResult{
start: 0,
end: 0,
offsetBefore: 0,
offsetAfter: 0,
totalSize: 0,
}display/password-strength-meter/password-strength-meter.component.ts
unknown(a: readonly boolean[], b: readonly boolean[]): boolean =>
a.length === b.length && a.every((v, i) => v === b[i])unknown['none', 'weak', 'fair', 'good', 'strong'] as constui/layout/stack.component.ts
audio/autoplay-gate/autoplay-gate.ts
unknownThe user-gesture events that satisfy the browser autoplay policy.
['pointerdown', 'keydown', 'touchstart'] as constpaginator/segments/paginator-dots.component.ts
input/mask-presets/iban-patterns.ts
RecordIBAN format masks per country (ISO 3166-1 alpha-2 keys).
A = letter, 0 = digit.
Source: official IBAN registry (SWIFT/ISO 13616). Unlike phone numbers, IBAN lengths are FIXED and officially standardised, so these masks are actually reliable, not approximations. IBAN is used in ~70 countries (mostly Europe + parts of the Middle East), hence no US/JP/CN/KR entries.
{
// DACH
CH: 'AA00 0000 0000 0000 0000 0', // 21
DE: 'AA00 0000 0000 0000 0000 00', // 22
AT: 'AA00 0000 0000 0000 0000', // 20
// Western Europe
FR: 'AA00 0000 0000 0000 0000 0000 000', // 27
BE: 'AA00 0000 0000 0000', // 16, BBAN fully numeric
NL: 'AA00 AAAA 0000 0000 00', // 18
LU: 'AA00 0000 0000 0000 0000', // 20
IE: 'AA00 AAAA 0000 0000 0000 00', // 22, 4-letter bank code + 14 digits
GB: 'AA00 AAAA 0000 0000 0000 00', // 22
MT: 'AA00 AAAA 0000 0000 0000 0000 000', // 31, longest IBAN in Europe
// Southern Europe
ES: 'AA00 0000 0000 0000 0000 0000', // 24
IT: 'AA00 A000 0000 0000 0000 0000 000', // 27, 1 letter (CIN check char) at BBAN start
PT: 'AA00 0000 0000 0000 0000 0000 0', // 25
GR: 'AA00 0000 0000 0000 0000 0000 000', // 27
// Northern Europe
SE: 'AA00 0000 0000 0000 0000 0000', // 24
NO: 'AA00 0000 0000 000', // 15, shortest in Europe
DK: 'AA00 0000 0000 0000 00', // 18
FI: 'AA00 0000 0000 0000 00', // 18
IS: 'AA00 0000 0000 0000 0000 0000 00', // 26
// Central Europe
PL: 'AA00 0000 0000 0000 0000 0000 0000', // 28, fully numeric BBAN
CZ: 'AA00 0000 0000 0000 0000 0000', // 24
SK: 'AA00 0000 0000 0000 0000 0000', // 24
HU: 'AA00 0000 0000 0000 0000 0000 0000', // 28
// Eastern Europe / Balkans
RO: 'AA00 AAAA 0000 0000 0000 0000', // 24, account part is alphanumeric
BG: 'AA00 AAAA 0000 0000 0000 00', // 22
HR: 'AA00 0000 0000 0000 0000 0', // 21
RS: 'AA00 0000 0000 0000 0000 00', // 22
SI: 'AA00 0000 0000 0000 000', // 19
UA: 'AA00 0000 0000 0000 0000 0000 0000 0', // 29
LT: 'AA00 0000 0000 0000 0000', // 20
LV: 'AA00 AAAA 0000 0000 0000 0', // 21
EE: 'AA00 0000 0000 0000 0000', // 20
RU: 'AA00 0000 0000 0000 0000 0000 00', // 33, in the IBAN registry since 2021
TR: 'AA00 0000 0000 0000 0000 0000 00', // 26, 1 reserved digit after the bank code
}display/stat/stat.component.ts
unknownStructural equality so an unchanged id set never cascades labelledBy.
(a: readonly string[], b: readonly string[]): boolean =>
a.length === b.length && a.every((id, i) => id === b[i])CngxStatSlotKind[]Fixed reading order for the coordinated slots.
['label', 'value', 'delta', 'caption']mat-tabs/material-bridge/private-surfaces.ts
.mat-mdc-tab-header — the strip element rendered by
MatTabHeader. [cngxMatTabs] walks
host.querySelector('.mat-mdc-tab-header') to locate the mount
anchor for the cngx overflow molecule (so the More button pins
inside Material's strip rather than as a sibling of
<mat-tab-group>).
'.mat-mdc-tab-header'.mat-mdc-tab-label-container — Material's IO-friendly scroll
viewport ancestor of the rendered tab buttons. The Material-
twin overflow adapter walks host → header → any tab → closest label-container to attach the molecule's
IntersectionObserver against the same element Material
itself scrolls.
'.mat-mdc-tab-label-container'.mat-mdc-tab — the class Material applies to the rendered
tab <button> elements inside MatTabHeader. cngx
index-correlates presenter.tabs() registration order against
host.querySelectorAll('.mat-mdc-tab') because MatTab is a
portal — the rendered buttons live inside MatTabHeader's
template, not on the <mat-tab> declaration site, and Material
exposes no public per-tab element accessor.
'.mat-mdc-tab'paginator/segments/paginator-nav.component.ts
unknown`
<button
type="button"
class="cngx-paginator__button cngx-paginator__nav"
cngxRipple
[attr.aria-disabled]="core.disabled()"
(click)="core.activate()"
>
<cngx-icon aria-hidden="true">{{ glyph }}</cngx-icon>
<span class="cngx-paginator__nav-label">{{ core.ariaLabel() }}</span>
</button>
`select/shared/template-registry.ts
Resolves the shared template slots through the 3-stage cascade.
Injection context required (injectResolvedTemplate reads
CNGX_SELECT_CONFIG).
signal(undefined)interactive/breadcrumb/breadcrumb-collapse.ts
ReadonlySetnew Set()ui/data-grid-accordion/data-grid-sort-header.directive.ts
audio/pitch-mode/audio-pitch.directive.ts
display/time/time.component.ts
literal type[]Unit ladder for the relative formatter, smallest first. Each amount is the
count of that unit in the next-larger one; anything past months falls
through to the years return.
[
{ amount: 60, unit: 'seconds' },
{ amount: 60, unit: 'minutes' },
{ amount: 24, unit: 'hours' },
{ amount: 7, unit: 'days' },
{ amount: 4.34524, unit: 'weeks' },
{ amount: 12, unit: 'months' },
]forms/input/password-strength.factory.ts
display/segmented-progress/segmented-progress.component.ts
unknown(a: readonly SegmentState[], b: readonly SegmentState[]): boolean =>
a.length === b.length && a.every((s, i) => s === b[i])display/delta/delta.component.ts
forms/input/input-mask.directive.ts
Record{
'0': { test: /[0-9]/, optional: false },
'9': { test: /[0-9]/, optional: true },
A: { test: /[a-zA-Z]/, optional: false },
a: { test: /[a-zA-Z]/, optional: true },
'*': { test: /[a-zA-Z0-9]/, optional: false },
}common/stepper/stepper-config.ts
STEPPER_DEFAULT_DENSITY_BREAKPOINTSOpen detail pageSTEPPER_DEFAULT_MOBILE_BREAKPOINTOpen detail page
CngxStepperDensityBreakpointsDefault per-step px thresholds for the density: 'auto' ladder. The
literal lives on a single exported const so the runtime resolver, the
config default, and any JSDoc cross-references stay in lockstep.
Consumers override via withStepperDensity's second argument.
{
compact: 120,
minimal: 64,
}Default media query the mobile auto-collapse policy reacts to. The literal lives on a single exported const so the runtime, the config default, and any JSDoc cross-references stay in lockstep. Consumers override the query via withStepperMobileBreakpoint.
'(max-width: 480px)'common/tabs/tabs-config.ts
unknown{
defaultOrientation: 'horizontal',
defaultLoop: true,
defaultCommitMode: 'optimistic',
fragmentSyncMode: 'fragment',
fragmentSyncParam: 'tab',
ariaLabels: {
tabsRegion: 'Tabs',
},
fallbackLabels: {
// W3C ARIA tablist convention - kept distinct from
// `i18n.tabsLabel` so AT doesn't read the same string twice
// back-to-back across `aria-roledescription` and `aria-label`.
tabRoleDescription: 'tab list',
tabPanelRoleDescription: 'tab panel',
},
overflowStabilizeMs: 100,
overflowMaxDeferMs: 250,
templates: {},
}unknownwithTabsDefaultOrientationunknownwithTabsFragmentSynctabs/i18n/tabs-i18n.ts
CngxTabsI18n{
tabsLabel: 'Tabs',
selectedTab: (label, position, count) => `Tab ${position} of ${count}: ${label}`,
tabLabelWithDetail: (label, detail) => `${label}, ${detail}`,
tabHasErrors: (count) => `${count} error${count === 1 ? '' : 's'}`,
moreTabsLabel: (count) => `${count} more`,
previousTab: 'Previous tab',
nextTab: 'Next tab',
closeTab: (label) => `Close "${label}"`,
addTab: 'Add tab',
commitFailedRetry: 'Tab change refused — retry?',
commitInFlight: 'Switching tab…',
commitRolledBackTo: (originLabel) => `Could not save changes — reverted to "${originLabel}".`,
}display/status/status.component.ts
interactive/menu/menu.directive.ts
unknownnew WeakSet<HTMLElement>()input/mask-presets/zip-patterns.ts
RecordPostal-code format masks per country (ISO 3166-1 alpha-2 keys).
A = letter, 0 = digit; a | declares length alternates picked by input
length.
Note (like phone numbers): not every country has a strictly fixed format
(Ireland's Eircode is highly irregular), so treat these as UI hints, not hard
validators. Countries with no postal-code system (HK, AE, ...) are
intentionally omitted - do not apply a zip: mask there.
{
// DACH
DE: '00000',
AT: '0000',
CH: '0000',
// Western Europe
FR: '00000',
BE: '0000',
NL: '0000 AA', // e.g. 1234 AB
LU: '0000',
IE: 'A00 0000', // Eircode since 2015; highly irregular
GB: 'A0A 0AA|AA0 0AA|AA00 0AA|A0 0AA|A00 0AA|AA0A 0AA', // incl. AA0A 0AA (e.g. EC1A 1BB)
UK: 'A0A 0AA|AA0 0AA|AA00 0AA|A0 0AA|A00 0AA|AA0A 0AA', // back-compat alias of GB
MT: 'AAA 0000', // e.g. VLT 1117
// Southern Europe
ES: '00000',
IT: '00000',
PT: '0000-000',
GR: '000 00',
// Northern Europe
SE: '000 00',
NO: '0000',
DK: '0000',
FI: '00000',
IS: '000', // 3-digit, very small system
// Central Europe
PL: '00-000',
CZ: '000 00',
SK: '000 00',
HU: '0000',
// Eastern Europe / Balkans
RO: '000000',
BG: '0000',
HR: '00000',
RS: '00000',
SI: '0000',
UA: '00000',
LT: '00000', // often written with an "LT-" prefix
LV: '0000', // often written with an "LV-" prefix
EE: '00000',
RU: '000000',
TR: '00000',
// North America
US: '00000', // ZIP+4 (00000-0000) not modelled here
CA: 'A0A 0A0', // alternating letter/digit
MX: '00000',
// East Asia
JP: '000-0000',
CN: '000000',
KR: '00000', // 5-digit since 2015
TW: '00000', // modern 5-digit (3+2)
// Southeast Asia
SG: '000000',
MY: '00000',
TH: '00000',
VN: '000000',
PH: '0000',
ID: '00000',
// South Asia
IN: '000 000', // PIN code, also written without the space
// Middle East
IL: '0000000',
SA: '00000-0000', // base 5-digit, "-0000" extension often omitted
// Africa
ZA: '0000',
EG: '00000',
NG: '000000',
// Latin America
AR: 'A0000AAA', // format since 1999 (e.g. C1425CKC); older 4-digit still seen
CL: '0000000',
CO: '000000',
PE: '00000',
BR: '00000-000',
// Oceania
AU: '0000',
NZ: '0000',
}