Skip to main content
cngx-src documentation
Miscellaneous > Variables

Variables

Variable115

Index

ALERT_FALLBACK_DURATIONALL_PRESET_KEYSALPHAALPHANUMERICALREADY_COVEREDANNOUNCE_DEBOUNCE_MSATTACK_SECCARD_I18N_DEFAULTSCHART_SMALL_BREAKPOINT_PXCLEAR_DELAY_MSCNGX_A11Y_PANEL_DEFAULTSCNGX_AUDIO_DEFAULTSCNGX_AUDIO_DOM_EVENTSCNGX_AUDIO_STATUS_DOM_EVENTSCNGX_FILTER_BUILTIN_OPERATOR_DEFSCNGX_FOCUSABLE_SELECTORCNGX_INCREMENTAL_LIST_DEFAULTSCNGX_LOADING_DEFAULTSCNGX_NAV_DEFAULTSCNGX_PAGINATOR_DEFAULTSCNGX_PAGINATOR_GLYPHSCNGX_SUBMENU_TRY_FALLBACKSCONTRAST_VALUEScontrastAxiscreateAudioEnginecreateDefaultChartRenderercreateMenuRadioControllercreatePaginatorPageWindowcreateToneGeneratorDATE_SHORT_FORMATSDATE_TIME_FORMATTER_CACHE_LIMITDEFAULT_COMMAND_PALETTE_CONFIGDEFAULT_HINT_FORMATTERSDEFAULT_INPUT_ARIA_LABELSDEFAULT_MENU_CONFIGDEFAULT_OPERATORSDEFAULT_RESOLVE_KEYBOARD_OPENDEFAULT_RESOLVE_OPENDEFAULT_SCROLL_OPTIONSDEFAULT_SKELETON_SLOTSDEFAULT_TONE_GAINDEFAULT_WINDOW_MSDENSITY_VALUESdensityAxisDIALOG_LABEL_DEFAULTSDOM_EVENT_SETDOM_EVENT_SETDOWNLOAD_PROGRESSEMPTY_IDSEMPTY_ITEMSEMPTY_NODESEMPTY_RANGEEXCLUDEDFEEDBACK_I18N_DEFAULTSfilledEqualformatterForKeyGAP_DEFAULTSGESTURE_EVENTSHALFIBAN_PATTERNSidsEqualinflightkindsEqualLIFECYCLE_KEYSLIFECYCLE_SETMAT_MDC_TAB_HEADER_SELECTORMAT_MDC_TAB_LABEL_CONTAINER_SELECTORMAT_MDC_TAB_SELECTORMIN_DURATION_SECMOTION_VALUESmotionAxisNAV_TEMPLATEngDevModengDevModeNO_ACTION_DIRECTIVENO_COLLAPSENO_LOADING_GLYPH_DIRECTIVENOT_SORTEDoptimisticPITCH_VOICEPREDEFINED_TAG_COLORSRATCHETreferenceEqualRELATIVE_DIVISIONSrelativeFormatterForRELEASE_SECRESIZE_KEY_STEPRESPONSESCORE_LABELSSEGMENT_COUNTsegmentsEqualSENTIMENT_WORDSLOT_ORDERSLOT_TOKENSSORTED_ASCENDINGSORTED_DESCENDINGSTATUS_ALIASESSTEPPER_DEFAULT_DENSITY_BREAKPOINTSSTRENGTH_LEVELSstrengthEqualtablesTABS_CONFIG_DEFAULTSTABS_I18N_DEFAULTSTEXT_SCALE_VALUEStextScaleAxisTIMELINE_CONFIG_DEFAULTSTONE_GLYPHtouchTargetAxisUPLOAD_PROGRESSVISIBLEwarnedFocusHostswithDefaultOrientationwithRetrywithTabsRouterSyncZIP_PATTERNS
No matching entities

feedback/alert/alerter.service.ts

ALERT_FALLBACK_DURATION#number

Auto-dismiss fallback when neither duration nor withAlerts({defaultDuration}) is set.

5000

input/mask-presets/registry.ts

ALL_PRESET_KEYS#MaskPresetKey[]
['phone', 'date', 'iban', 'zip']
inflight#unknown
new Map<MaskPresetKey, Promise<void>>()
tables#unknown
signal<MaskPresetTables>({})

forms/input/input-filter.directive.ts

ALPHA#unknown
/[a-z]/i
ALPHANUMERIC#unknown
/[a-z0-9]/i

core/theming/user-facing-string-coverage.fixtures.ts

ALREADY_COVERED#StringManifestEntry[]

Strings the scanner cannot prove are covered, but which are - the override read happens through an indirection the regex does not follow.

Not a parking lot: every row names the token that overrides it.

[
  {
    file: 'projects/forms/input/phone-input/countries.ts',
    value: 'United States',
    note: 'default row of CNGX_PHONE_COUNTRIES, replaced wholesale via CngxPhoneInput [countries]',
  },
  {
    file: 'projects/forms/input/phone-input/countries.ts',
    value: 'United Kingdom',
    note: 'default row of CNGX_PHONE_COUNTRIES, replaced wholesale via CngxPhoneInput [countries]',
  },
  {
    file: 'projects/forms/input/phone-input/countries.ts',
    value: 'Germany',
    note: 'default row of CNGX_PHONE_COUNTRIES, replaced wholesale via CngxPhoneInput [countries]',
  },
  {
    file: 'projects/forms/input/phone-input/countries.ts',
    value: 'Austria',
    note: 'default row of CNGX_PHONE_COUNTRIES, replaced wholesale via CngxPhoneInput [countries]',
  },
  {
    file: 'projects/forms/input/phone-input/countries.ts',
    value: 'Switzerland',
    note: 'default row of CNGX_PHONE_COUNTRIES, replaced wholesale via CngxPhoneInput [countries]',
  },
  {
    file: 'projects/forms/input/phone-input/countries.ts',
    value: 'France',
    note: 'default row of CNGX_PHONE_COUNTRIES, replaced wholesale via CngxPhoneInput [countries]',
  },
  {
    file: 'projects/forms/input/phone-input/countries.ts',
    value: 'Italy',
    note: 'default row of CNGX_PHONE_COUNTRIES, replaced wholesale via CngxPhoneInput [countries]',
  },
  {
    file: 'projects/forms/input/phone-input/countries.ts',
    value: 'Spain',
    note: 'default row of CNGX_PHONE_COUNTRIES, replaced wholesale via CngxPhoneInput [countries]',
  },
  {
    file: 'projects/forms/input/phone-input/countries.ts',
    value: 'Slovenia',
    note: 'default row of CNGX_PHONE_COUNTRIES, replaced wholesale via CngxPhoneInput [countries]',
  },
  {
    file: 'projects/forms/input/phone-input/countries.ts',
    value: 'Croatia',
    note: 'default row of CNGX_PHONE_COUNTRIES, replaced wholesale via CngxPhoneInput [countries]',
  },
  {
    file: 'projects/forms/input/phone-input/countries.ts',
    value: 'Poland',
    note: 'default row of CNGX_PHONE_COUNTRIES, replaced wholesale via CngxPhoneInput [countries]',
  },
  {
    file: 'projects/forms/input/phone-input/countries.ts',
    value: 'Japan',
    note: 'default row of CNGX_PHONE_COUNTRIES, replaced wholesale via CngxPhoneInput [countries]',
  },
  {
    file: 'projects/forms/input/phone-input/countries.ts',
    value: 'Brazil',
    note: 'default row of CNGX_PHONE_COUNTRIES, replaced wholesale via CngxPhoneInput [countries]',
  },
  {
    file: 'projects/ui/action-button/action-button.ts',
    value: 'Action succeeded',
    note: 'overridden per instance by [succeededAnnouncement] then [succeededLabel]',
  },
  {
    file: 'projects/ui/action-button/action-button.ts',
    value: 'Action failed',
    note: 'overridden per instance by [failedAnnouncement] then [failedLabel]',
  },
]

Deliberate non-hooks. Each row cites the accepted-debt entry that settled it, so the guard does not re-raise a closed decision on every run.

[
  {
    file: 'projects/common/interactive/guard/can-deactivate.ts',
    value: 'You have unsaved changes. Leave anyway?',
    note: 'default parameter of canDeactivateWhenClean - the caller passes its own message',
  },
]

The gap list: user-facing strings that ship with no override path.

Empty since 2026-09-22 - the A2 i18n audit drove it to zero. The suite asserts the emptiness actively, so re-adding a row is a conscious edit with a one-clause reason, not a quiet regression.

[]

forms/input/password-strength.directive.ts

ANNOUNCE_DEBOUNCE_MS#number

Default debounce before the strength label is announced, in ms.

400
strengthEqual#unknown
(a: PasswordStrengthResult, b: PasswordStrengthResult): boolean =>
a.score === b.score && a.label === b.label

audio/tone-generator/tone-generator.ts

ATTACK_SEC#number

Attack ramp, seconds - lifts gain from 0 so the oscillator start is click-free.

0.005

Pure 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_TONE_GAIN#number

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.2
MIN_DURATION_SEC#unknown

A tone shorter than attack+release cannot host both ramps; clamp up to this floor.

ATTACK_SEC + RELEASE_SEC
RELEASE_SEC#number

Release ramp, seconds - drops gain to 0 before stop so the end is click-free.

0.03

card/i18n/card-i18n.ts

CARD_I18N_DEFAULTS#CngxCardI18n
{
  selected: 'Selected',
  deselected: 'Deselected',
  loading: 'Loading',
}

chart/chart/template-slots.ts

CHART_SMALL_BREAKPOINT_PX#number

Threshold (px) below which CngxChartSlotContext.small is true. Hardcoded for v1; later releases may make this a config token.

400

a11y/aria/live-announcer.ts

CLEAR_DELAY_MS#number
16

ui/a11y/a11y-panel.config.ts

CNGX_A11Y_PANEL_DEFAULTS#CngxA11yPanelConfig

Library defaults - English. Override via provideA11yPanelConfig.

{
  labels: {
    axes: {
      density: 'Spacing',
      textScale: 'Text size',
      motion: 'Motion',
      contrast: 'Contrast',
    },
    reset: 'Reset to defaults',
    heading: 'Accessibility',
    resetMessage: 'Preferences reset to defaults',
  },
  axes: [
    {
      axis: 'density',
      reset: 'comfortable',
      options: [
        { value: 'compact', label: 'Compact' },
        { value: 'comfortable', label: 'Comfortable' },
        { value: 'spacious', label: 'Spacious' },
      ],
    },
    {
      axis: 'textScale',
      reset: 'md',
      options: [
        { value: 'sm', label: 'Small' },
        { value: 'md', label: 'Default' },
        { value: 'lg', label: 'Large' },
      ],
    },
    {
      axis: 'motion',
      reset: 'auto',
      options: [
        { value: 'full', label: 'Full' },
        { value: 'reduced', label: 'Reduced' },
        { value: 'auto', label: 'System' },
      ],
    },
    {
      axis: 'contrast',
      reset: 'auto',
      options: [
        { value: 'normal', label: 'Normal' },
        { value: 'more', label: 'More' },
        { value: 'auto', label: 'System' },
      ],
    },
  ],
}

audio/config/audio-config.ts

CNGX_AUDIO_DEFAULTS#CngxAudioConfig

Library defaults - English, browser-native.

{
  muted: false,
  volume: 1,
  respectReducedMotion: true,
  debounceMs: 100,
  earcons: {},
}

audio/event-mode/parse-bindings.ts

CNGX_AUDIO_DOM_EVENTS#unknown

DOM events [cngxAudio] may bind an earcon to.

[
  'click',
  'focus',
  'blur',
  'pointerenter',
  'pointerleave',
  'submit',
  'change',
  'input',
] as const
DOM_EVENT_SET#unknown
new Set<string>(CNGX_AUDIO_DOM_EVENTS)
LIFECYCLE_KEYS#unknown

Lifecycle keys that belong to [cngxAudioStatus], rejected here.

[
  'pending',
  'succeeded',
  'failed',
  'loading',
  'refreshing',
  'success',
  'error',
  'idle',
] as const
LIFECYCLE_SET#unknown
new Set<string>(LIFECYCLE_KEYS)

audio/status-mode/parse-status-bindings.ts

CNGX_AUDIO_STATUS_DOM_EVENTS#unknown

DOM-event keys that belong to [cngxAudio], rejected here.

[
  'click',
  'focus',
  'blur',
  'pointerenter',
  'pointerleave',
  'submit',
  'change',
  'input',
] as const
DOM_EVENT_SET#unknown
new Set<string>(CNGX_AUDIO_STATUS_DOM_EVENTS)
STATUS_ALIASES#Readonly>

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',
}

forms/filter-builder/filter-builder-operators.ts

CNGX_FILTER_BUILTIN_OPERATOR_DEFS#ReadonlyMap

The 14 builtin operator definitions: the 11 historical switch arms re-expressed as data, plus the opt-in between / in / notIn family. Default value of the CNGX_FILTER_BUILDER_CONFIG.operators slice and the fallback registry evaluateExpression resolves against when no options are passed - keeping the no-options evaluation path bit-identical to the historical closed switch.

The opt-in trio is deliberately absent from DEFAULT_OPERATORS: no field grows a picker entry its native editor cannot serve. Expose the keys per field via FilterFieldDef.operators (or per editor type via withDefaultOperators) together with a value editor that produces the matching array shape - [min, max] for between, a value list for in / notIn.

new Map<string, CngxFilterOperatorDef>([
  // Labels for the builtins live in the default i18n bundle
  // (DEFAULT_I18N.operators) - def.label is the tier for
  // consumer-registered keys only, so the string exists once.
  ['eq', { evaluate: (itemValue, exprValue) => Object.is(itemValue, exprValue) }],
  ['neq', { evaluate: (itemValue, exprValue) => !Object.is(itemValue, exprValue) }],
  ['isEmpty', { valueless: true, evaluate: (itemValue) => itemValue == null || itemValue === '' }],
  [
    'isNotEmpty',
    { valueless: true, evaluate: (itemValue) => itemValue != null && itemValue !== '' },
  ],
  ['contains', substringDef((a, b) => a.includes(b))],
  ['startsWith', substringDef((a, b) => a.startsWith(b))],
  ['endsWith', substringDef((a, b) => a.endsWith(b))],
  ['gt', { evaluate: (a, b) => compare(a, b) > 0 }],
  ['gte', { evaluate: (a, b) => compare(a, b) >= 0 }],
  ['lt', { evaluate: (a, b) => compare(a, b) < 0 }],
  ['lte', { evaluate: (a, b) => compare(a, b) <= 0 }],
  [
    'between',
    {
      // Value shape [min, max]. A non-array (or wrong-arity) value is a
      // wiring bug -> conservative false; a nullish bound means the range
      // editor is half-filled -> no-op true, mirroring the empty-value
      // guard that cannot see inside arrays.
      evaluate: (itemValue, exprValue) => {
        if (!Array.isArray(exprValue) || exprValue.length !== 2) {
          return false;
        }
        const [min, max] = exprValue as [unknown, unknown];
        if (min == null || max == null) {
          return true;
        }
        return compare(itemValue, min) >= 0 && compare(itemValue, max) <= 0;
      },
    },
  ],
  [
    'in',
    {
      // An empty list is an unfilled editor, not "match nothing" - the
      // no-op guard cannot see inside arrays, so the def honours the
      // unfilled-row contract itself (mirrors the between bound guard).
      evaluate: (itemValue, exprValue) => {
        if (!Array.isArray(exprValue)) {
          return false;
        }
        if (exprValue.length === 0) {
          return true;
        }
        return exprValue.some((v) => Object.is(v, itemValue));
      },
    },
  ],
  [
    'notIn',
    {
      evaluate: (itemValue, exprValue) => {
        if (!Array.isArray(exprValue)) {
          return false;
        }
        if (exprValue.length === 0) {
          return true;
        }
        return !exprValue.some((v) => Object.is(v, itemValue));
      },
    },
  ],
])
ngDevMode#boolean | undefined

Pure operator definitions - zero Angular dependency, zero inject(). The single source of truth for what an operator key means at evaluation time. evaluateExpression resolves every operator through this registry (or a consumer-extended map from CNGX_FILTER_BUILDER_CONFIG.operators), so an operator a consumer can register is an operator that also evaluates - no silent-false switch arms.

a11y/focus/focus-restore.directive.ts

CNGX_FOCUSABLE_SELECTOR#string

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

CNGX_INCREMENTAL_LIST_DEFAULTS#CngxIncrementalListConfig

Library 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.`,
  },
}

core/utils/loading-config.ts

CNGX_LOADING_DEFAULTS#unknown

Library defaults for CngxLoadingConfig. English/neutral values; a 120ms show delay suppresses sub-perceptual blips, a 400ms min-dwell keeps a shown indicator on screen long enough to read, an 800ms cutoff splits a spinner-worthy wait from a skeleton-worthy one.

{
  showDelay: 120,
  minDwell: 400,
  spinnerVsSkeletonCutoff: 800,
} as const satisfies CngxLoadingConfig

interactive/nav/nav-config.ts

CNGX_NAV_DEFAULTS#Readonly>

Default nav configuration values.

{
  indent: 12,
  singleAccordion: false,
  animationDuration: 150,
}

ui/paginator/paginator-config.ts

CNGX_PAGINATOR_DEFAULTS#CngxPaginatorConfig

Library 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

CNGX_PAGINATOR_GLYPHS#unknown

Default 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 const

interactive/menu/submenu-defaults.ts

CNGX_SUBMENU_TRY_FALLBACKS#unknown

Recommended 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)

core/theming/a11y-preferences.ts

['normal', 'more', 'auto']
DENSITY_VALUES#CngxDensityValue[]
['comfortable', 'compact', 'spacious']
['full', 'reduced', 'auto']
TEXT_SCALE_VALUES#CngxTextScaleValue[]
['sm', 'md', 'lg']

core/theming/contrast.ts

contrastAxis#unknown
createPreferenceAxis({
  token: CNGX_CONTRAST,
  attribute: 'data-contrast',
  removeValue: 'auto',
})

audio/engine/audio-engine.ts

createAudioEngine#CngxAudioEngineFactory

Create 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 }),
  );
  // Dev-only: an unknown earcon name is a fixed authoring mistake, so warn once
  // per name rather than on every (debounced) play attempt.
  const warnedUnknownEarcons = new Set<string>();

  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!,
  });

  // Single source of truth for `status`: the context's own statechange event.
  // Browser-initiated transitions the page never requested (Safari interrupt,
  // OS suspend) flow through here too, and because resume()/suspend() land
  // their result via this listener there is no manual status write left to
  // race - a stale resume resolution cannot clobber a later suspend.
  const onStateChange = (): void => {
    if (context) {
      status.set(mapAudioStatus(context.state));
    }
  };

  function resumeIfArmed(): void {
    if (context && gate.armed() && isSuspendedLike(context.state)) {
      void (context as AudioContext).resume().catch(() => 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);
    // Born in its current state - no statechange fires for the initial value,
    // so seed it directly; every later transition arrives via onStateChange.
    status.set(mapAudioStatus(created.state));
    created.addEventListener('statechange', onStateChange);
    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().catch(() => undefined);
      }
    } else {
      resumeIfArmed();
    }
  };
  doc.addEventListener('visibilitychange', onVisibility);
  destroyRef.onDestroy(() => {
    doc.removeEventListener('visibilitychange', onVisibility);
    context?.removeEventListener('statechange', onStateChange);
    // 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() && !warnedUnknownEarcons.has(name)) {
          warnedUnknownEarcons.add(name);
          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(),
  };
}

chart/renderer/renderer-factory.ts

createDefaultChartRenderer#CngxChartRendererFactory

The default renderer factory: dispatches 'svg' to createSvgRenderer and 'canvas' to createCanvasRenderer. Inlines the two-backend switch; a consumer wanting a third backend (WebGL, ...) supplies a whole replacement via withChartRendererFactory.

(mode, deps) =>
mode === 'svg' ? createSvgRenderer(deps) : createCanvasRenderer(deps)

interactive/menu/menu-radio-controller.ts

createMenuRadioController#CngxMenuRadioGroupFactory

Pure 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

createPaginatorPageWindow#CngxPaginatorPageWindowFactory

Default 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.

() => pageWindow

input/mask-presets/date-formats.ts

DATE_SHORT_FORMATS#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',
}

core/utils/intl-format.util.ts

DATE_TIME_FORMATTER_CACHE_LIMIT#number
32
formatterForKey#unknown
memoize(
  (key: string): Intl.DateTimeFormat => {
    const sep = key.indexOf('|');
    return new Intl.DateTimeFormat(
      key.slice(0, sep),
      JSON.parse(key.slice(sep + 1)) as Intl.DateTimeFormatOptions,
    );
  },
  { cacheLimit: DATE_TIME_FORMATTER_CACHE_LIMIT },
)

command-palette/config/command-palette-config.ts

DEFAULT_COMMAND_PALETTE_CONFIG#CngxCommandPaletteConfig

Library-default palette configuration, built from the internal defaults const. English-only.

{
  openShortcut: CNGX_COMMAND_PALETTE_DEFAULTS.openShortcut,
  searchPlaceholder: CNGX_COMMAND_PALETTE_DEFAULTS.searchPlaceholder,
  listboxLabel: CNGX_COMMAND_PALETTE_DEFAULTS.listboxLabel,
  emptyLabel: CNGX_COMMAND_PALETTE_DEFAULTS.emptyLabel,
  loadingLabel: CNGX_COMMAND_PALETTE_DEFAULTS.loadingLabel,
  errorLabel: CNGX_COMMAND_PALETTE_DEFAULTS.errorLabel,
  retryLabel: CNGX_COMMAND_PALETTE_DEFAULTS.retryLabel,
  resultCount: CNGX_COMMAND_PALETTE_DEFAULTS.resultCount,
  footerLegend: CNGX_COMMAND_PALETTE_DEFAULTS.footerLegend,
}

forms/field/form-field.token.ts

DEFAULT_HINT_FORMATTERS#ConstraintHintFormatters

English 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

DEFAULT_INPUT_ARIA_LABELS#InputAriaLabels

English 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

DEFAULT_MENU_CONFIG#CngxMenuConfig

Library-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

DEFAULT_OPERATORS#unknown

Default 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[]>

interactive/menu/context-menu-trigger-core.ts

DEFAULT_RESOLVE_KEYBOARD_OPEN#unknown
DEFAULT_RESOLVE_OPEN
DEFAULT_RESOLVE_OPEN#unknown
(): CngxContextMenuOpenDecision => ({
  open: true,
  context: undefined,
})

tabs/scroll-sync/organism-scroll-sync.ts

DEFAULT_SCROLL_OPTIONS#ScrollIntoViewOptions
{
  behavior: 'smooth',
  block: 'nearest',
  inline: 'center',
}

ui/stat-card/stat-card.component.ts

DEFAULT_SKELETON_SLOTS#CngxStatSlotKind[]

Placeholder shape for a card whose consumer projected no stat slots at all.

['label', 'value', 'caption']

audio/debouncer/debouncer.ts

DEFAULT_WINDOW_MS#number

Default suppression window in milliseconds.

100

core/theming/density.ts

densityAxis#unknown
createPreferenceAxis({
  token: CNGX_DENSITY,
  attribute: 'data-density',
})

dialog/config/dialog-config.ts

DIALOG_LABEL_DEFAULTS#CngxDialogLabels
{
  close: 'Close dialog',
  errorFallback: 'An error occurred',
  dragHandle: 'Move dialog',
  dragHandleRoleDescription: 'draggable',
  dragInstructions: 'Use arrow keys to move the dialog; Shift for larger steps',
}

data/async-state/operators.ts

DOWNLOAD_PROGRESS#number
3
RESPONSE#number
4
UPLOAD_PROGRESS#number
1

ui/toc/toc.component.ts

EMPTY_IDS#string[]

Shared empty result so the no-active-section trail is reference-stable.

[]
EMPTY_ITEMS#CngxTocItem[]

Shared empty outline so the pre-scan / no-headings state is reference-stable.

[]

common/stepper/create-stepper-host-proxy.ts

EMPTY_NODES#CngxStepNode[]
Object.freeze([])

data/recycler/range-computer.ts

EMPTY_RANGE#RangeResult
{
  start: 0,
  end: 0,
  offsetBefore: 0,
  offsetAfter: 0,
  totalSize: 0,
}

feedback/config/feedback-i18n.ts

FEEDBACK_I18N_DEFAULTS#CngxFeedbackI18n
{
  alertsRegionLabel: 'Alerts',
  notificationsRegionLabel: 'Notifications',
  announcements: {
    alertDismissed: 'Alert dismissed',
    alertOverflow: (count) => `Show ${count} more alerts`,
    asyncLoading: 'Loading content',
    asyncLoaded: 'Content loaded',
    asyncError: 'Error loading content',
    asyncRefreshing: 'Refreshing content',
    asyncRefreshed: 'Content refreshed',
    asyncRefreshFailed: 'Refresh failed',
  },
}

display/password-strength-meter/password-strength-meter.component.ts

filledEqual#unknown
(a: readonly boolean[], b: readonly boolean[]): boolean =>
a.length === b.length && a.every((v, i) => v === b[i])
SEGMENT_COUNT#number
4
STRENGTH_LEVELS#unknown
['none', 'weak', 'fair', 'good', 'strong'] as const

ui/layout/stack.component.ts

GAP_DEFAULTS#Record
{
  none: '0',
  xs: 'var(--cngx-gap-xs, var(--cngx-space-xs, 4px))',
  sm: 'var(--cngx-gap-sm, var(--cngx-space-sm, 8px))',
  md: 'var(--cngx-gap-md, var(--cngx-space-md, 16px))',
  lg: 'var(--cngx-gap-lg, var(--cngx-space-lg, 24px))',
  xl: 'var(--cngx-gap-xl, var(--cngx-space-xl, 32px))',
}

audio/autoplay-gate/autoplay-gate.ts

GESTURE_EVENTS#unknown

The user-gesture events that satisfy the browser autoplay policy.

['pointerdown', 'keydown', 'touchstart'] as const

paginator/segments/paginator-dots.component.ts

HALF#unknown

Pages flanking the centred current page inside the viewport.

Math.floor(VISIBLE / 2)
VISIBLE#number

Visible dot count in the viewport before the track scrolls.

7

input/mask-presets/iban-patterns.ts

IBAN_PATTERNS#Record

IBAN 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-coordinator.directive.ts

idsEqual#unknown

Structural 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])
kindsEqual#unknown

Structural equality so an unchanged slot set never cascades to a placeholder.

(a: readonly CngxStatSlotKind[], b: readonly CngxStatSlotKind[]): boolean =>
a.length === b.length && a.every((kind, i) => kind === b[i])

Fixed reading order for the coordinated slots.

['label', 'value', 'delta', 'caption']

mat-tabs/material-bridge/private-surfaces.ts

MAT_MDC_TAB_HEADER_SELECTOR#string

.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_SELECTOR#string

.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_SELECTOR#string

.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'

core/theming/motion.ts

motionAxis#unknown
createPreferenceAxis({
  token: CNGX_MOTION,
  attribute: 'data-motion',
  removeValue: 'auto',
})

paginator/segments/paginator-nav.component.ts

projects/utils/tree.ts

ngDevMode#boolean | undefined

select/shared/template-registry.ts

NO_ACTION_DIRECTIVE#Signal

Resolves the shared template slots through the 3-stage cascade. Injection context required (injectResolvedTemplate reads CNGX_SELECT_CONFIG).

signal(undefined)
NO_LOADING_GLYPH_DIRECTIVE#Signal
signal(undefined)

interactive/breadcrumb/breadcrumb-collapse.ts

NO_COLLAPSE#ReadonlySet
new Set()

ui/data-grid-accordion/data-grid-sort-header.directive.ts

NOT_SORTED#string
'not sorted, activate to sort ascending'
SORTED_ASCENDING#string
'sorted ascending, activate to sort descending'
SORTED_DESCENDING#string
'sorted descending, activate to sort ascending'

interactive/optimistic/optimistic.ts

optimistic#unknown
Use `createOptimistic` - this name is removed at the v1.0 cut (exported factories carry the `create` prefix).

Alias of createOptimistic under the pre-convention name.

createOptimistic

audio/pitch-mode/audio-pitch.directive.ts

PITCH_VOICE#string

Throttle key - the pitch directive plays a single ad-hoc voice.

'pitch'

display/tag/tag.directive.ts

PREDEFINED_TAG_COLORS#ReadonlySet

Colour keys whose [data-color] cascade ships in tag.css. A withTagColors() entry under one of these keys stays a no-op - the stylesheet cascade owns them.

new Set([
  'neutral',
  'success',
  'warning',
  'error',
  'info',
])

forms/filter-builder/filter-builder-internal.ts

referenceEqual#unknown

Reference-identity equality predicate. Used as the equal fn on object and array computed signals whose producers already preserve identity across re-evaluations - see getNodeAtPath / appendAtPath / updateAtPath / removeAtPath in filter-builder.utils.ts, which return the same reference when no descendant changed.

Explicit equal fn on every object/array computed prevents downstream cascades.

<T>(a: T, b: T): boolean => a === b

display/time/time.component.ts

RELATIVE_DIVISIONS#literal type[]
[
  { 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' },
]
relativeFormatterFor#unknown

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.

memoize(
  (locale: string) => new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }),
)

ui/sidenav/sidenav.ts

RESIZE_KEY_STEP#number

Per-keypress width change of the arrow-key resize, in px.

16

forms/input/password-strength.factory.ts

SCORE_LABELS#Record
{
  0: 'weak',
  1: 'weak',
  2: 'fair',
  3: 'good',
  4: 'strong',
}

display/segmented-progress/segmented-progress.component.ts

segmentsEqual#unknown
(a: readonly SegmentState[], b: readonly SegmentState[]): boolean =>
a.length === b.length && a.every((s, i) => s === b[i])

display/delta/delta.component.ts

SENTIMENT_WORD#Record
{
  positive: 'improved',
  negative: 'declined',
  neutral: 'unchanged',
}

forms/input/input-mask.directive.ts

SLOT_TOKENS#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_BREAKPOINTS#CngxStepperDensityBreakpoints

Default 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,
}

common/tabs/tabs-config.ts

TABS_CONFIG_DEFAULTS#unknown
{
  defaultOrientation: 'horizontal',
  defaultLoop: true,
  defaultCommitMode: 'optimistic',
  fragmentSyncMode: 'fragment',
  fragmentSyncParam: 'tab',
  // routeMatch stays unset: the host-flavour default is the right answer
  // for both organisms, and pinning one here would silently flip the
  // other. linkAriaCurrent defaults at the link, not here.
  linkAriaCurrent: 'page',
  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: {},
}
withDefaultOrientation#unknown
Use {@link withTabsDefaultOrientation}. The unprefixed name collides with the stepper family's identical symbol when both libs are auto-imported. The alias stays for one minor release before removal.
withTabsDefaultOrientation
withTabsRouterSync#unknown
Use {@link withTabsFragmentSync}. "Router sync" collided with the new `[cngxTabsRouteSync]` router-outlet directive; this feature only ever configured fragment / query-param deep-linking. The alias stays for one minor release before removal.
withTabsFragmentSync

tabs/i18n/tabs-i18n.ts

TABS_I18N_DEFAULTS#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',
  closedTab: (label) => (label ? `Closed "${label}"` : 'Tab closed'),
  commitFailedRetry: 'Tab change refused - retry?',
  commitInFlight: 'Switching tab…',
  commitRolledBackTo: (originLabel) => `Could not save changes - reverted to "${originLabel}".`,
}

core/theming/text-scale.ts

textScaleAxis#unknown
createPreferenceAxis({
  token: CNGX_TEXT_SCALE,
  attribute: 'data-text-size',
})

common/timeline/timeline-config.ts

TIMELINE_CONFIG_DEFAULTS#Required
{
  labels: {
    timelineRegion: 'Timeline',
    retry: 'Retry',
    errorFallback: 'Could not load the timeline.',
    emptyFallback: 'No events yet.',
    loading: 'Loading timeline',
    refreshing: 'Updating…',
    itemBusy: 'Updating',
    itemErrorFallback: 'Could not load this event.',
    status: {
      done: 'Completed',
      active: 'In progress',
      upcoming: 'Upcoming',
      rejected: 'Rejected',
    },
    groupLabel: (group) => group.start.toLocaleDateString(),
  },
  templates: {},
}

display/status/status.component.ts

TONE_GLYPH#Record

Tone glyph rendered inside the decorative dot - the non-colour signal.

{
  success: '✓',
  warning: '!',
  danger: '✕',
  info: 'i',
  neutral: '•',
}

core/theming/touch-target.ts

touchTargetAxis#unknown
createPreferenceAxis({
  token: CNGX_TOUCH_TARGET,
  attribute: 'data-touch',
  removeValue: 'auto',
})

interactive/menu/menu.directive.ts

warnedFocusHosts#unknown
new WeakSet<HTMLElement>()

interactive/retry/with-retry.ts

withRetry#unknown
Use `createRetry` - this name is removed at the v1.0 cut (`with*` is reserved for provider feature functions).

Alias of createRetry under the pre-convention name.

createRetry

input/mask-presets/zip-patterns.ts

ZIP_PATTERNS#Record

Postal-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',
}