Skip to content

Fix bugs and harden the type surface from the TypeScript migration - #42759

Merged
mdo merged 10 commits into
v6-devfrom
post-ts-followups
Jul 28, 2026
Merged

Fix bugs and harden the type surface from the TypeScript migration#42759
mdo merged 10 commits into
v6-devfrom
post-ts-followups

Conversation

@mdo

@mdo mdo commented Jul 28, 2026

Copy link
Copy Markdown
Member

Supersedes #42732 (auto-closed when its base branch issue-42620 was deleted during the #42724 merge; its work never landed). Rebased onto current v6-dev.

Fixes bugs found while migrating the JS to TypeScript, plus a follow-up review pass that hardens the published type surface.

Component bug fixes

  • tooltip: compare Floating UI arrow coords against undefined (kills left: "undefinedpx"); adapt array-returning offset callbacks to { mainAxis, crossAxis } via a shared toFloatingOffset(); menu got the same offset fix (it silently ignored an array-returning callback).
  • dialog + nav-overflow: dispose() removes only the instance's own listener (was removing other instances' and consumers' listeners).
  • chips: remove() keeps values in rendered order under allowDuplicates; removes one entry per duplicate.
  • strength: drop the unused _updateUI arg. toggler: document the required value.

Type surface hardening

  • Every _-prefixed instance member is protected (409 members) so consumers can't reach internals; getOrCreateInstance config typed via NonNullable<ConstructorParameters<T>[1]>.
  • Config types match their runtime validators (container, dateMin/dateMax, toggler value).
  • Fixed three assert-then-test contradictions (menu keydown, chips/otp constructors) — no ! remains.
  • Removed the dead jQuery jQueryInterface from combobox.

Packaging & tooling

  • Package entry moved to js/src/index.ts (tsc emits its .d.ts); bootstrap/js/src/*.js exports map to the compiled js/dist; migration guide notes the source is TypeScript.
  • Declaration emit runs parallel to compile/minify; moduleResolution: nodenext enforces .js import extensions.
  • Added js/tests/types/consumer.ts (checks the shipped .d.ts through the exports map) and api.ts assertions locking the config path and protected internals.

All gates green: 1192 tests, lint, tsc, consumer d.ts, cSpell, bundlewatch, docs.

mdo added 10 commits July 27, 2026 22:57
- tooltip: compare Floating UI arrow coords against undefined (not null),
  the value the arrow middleware actually reports, so an absent cross-axis
  coordinate no longer writes "undefinedpx"; adapt array results from an
  offset callback to Floating UI's { mainAxis, crossAxis } shape
- dialog: remove the unnamespaced native cancel listener on dispose so it
  no longer leaks past teardown
- nav-overflow: namespace the fallback window resize listener and remove it
  on dispose; drop the write-only _isInitialized flag
- chips: remove only a single value entry so duplicate chips stay in sync
  with the DOM under allowDuplicates
- strength: drop the unused score argument passed to _updateUI
- toggler: document that value is required (the null default is a
  must-override placeholder)

Adds unit coverage for each fix, including a new chips spec.
Fixes issues a code review found on the migration branch, plus cleanups.

Correctness:
- Menu offset callbacks now work. A callback that returns a `[skidding,
  distance]` array is converted to Floating UI's `{ mainAxis, crossAxis }`
  shape through a shared `toFloatingOffset()` helper. Tooltip and Menu both
  use it, so all three offset forms share one mapping. Menu previously
  ignored an array-returning callback.
- `dispose()` on dialog-base and nav-overflow now removes only the instance's
  own listener. Calling `EventHandler.off()` with no handler removed other
  instances' and consumers' listeners too. Each instance now stores its
  handler and passes it to `off()`.
- chips `remove()` rebuilds the value array from the remaining chips. Values
  now stay in the rendered order when a duplicate is removed.

Packaging:
- The `bootstrap/js/src/*.js` export now maps to the compiled `js/dist` build,
  not the raw `.ts` source, so non-TypeScript consumers can load deep imports.
  The migration guide documents the change.
- The package entry moved to `js/src/index.ts`, so tsc emits its declarations.
  This removes the string-replace hack in `build-plugins.mjs`.

Tooling:
- `js` emits declarations in parallel with the compile and minify chain, so
  minification no longer waits for tsc and the watch loop skips it.
- `moduleResolution: nodenext` makes tsc enforce the `.js` import extensions.
- A `declare ['constructor']` field on Config, BaseComponent, and Tooltip
  replaces about 29 `(this.constructor as typeof X)` casts.
- The offset and floatingConfig option types are shared from util/floating-ui.
`js/tests/types/consumer.ts` imports through the package name and the deep
subpaths, so Node resolves it through the `exports` map to the emitted
`js/dist/*.d.ts`. This proves downstream projects get working types with no
`@types/bootstrap`, and it fails when a shipped declaration is malformed.
`npm test` runs it as `js-typecheck-dist` after the build.
An adversarial review found the migration's types assert away things the
runtime rejects. Fix the type layer without changing behavior.

- Delete the dead `jQueryInterface` from combobox. v6 removed jQuery, so
  `this.each` does not exist and the method throws; it was unreferenced and
  laundered three `any`s through the compiler.
- Narrow config types to match their runtime validators: tooltip `container`
  drops `null`, datepicker `dateMin`/`dateMax` drop `Date`, and toggler
  `value` drops `null`. Each value passed the type but threw in
  `_typeCheckConfig`.
- Type the static path. `getOrCreateInstance` now types its config per
  component via `Partial<InstanceType<T>['_config']>`, so it enforces the same
  keys as the constructor. This is the path the docs recommend.
- Force Babel to use define semantics for class fields, so it agrees with
  tsconfig `useDefineForClassFields` and Rolldown. No field is emitted today
  (all instance fields use `declare`), so dist is unchanged.

The type tests (`api.ts`, `consumer.ts`) now assert the static config path.
The migration exposed every component internal as public. Consumers could
reach `_config`, `_element`, and helper methods, which would make them
breaking changes once shipped. Restrict the surface and stop a few types from
lying about nullability.

- Mark every `_`-prefixed instance field and method `protected` across all
  components (409 members). `protected`, not `private`, so subclasses keep
  access. Public API methods and statics stay public. Two carousel data-api
  hooks stay public because the module-level handlers call them.
- Retype `getOrCreateInstance`'s config to `NonNullable<ConstructorParameters<T>[1]>`
  so it still enforces each component's config now that `_config` is protected
  (indexing a protected member on a type parameter is illegal).
- Fix three assert-then-test contradictions found by the review: the `menu`
  submenu-keydown guard, and the `chips`/`otp-input` constructors that asserted
  `findOne(...)!` then tested the result for null. Each now handles the null in
  a local, so the guard is honest and no `!` remains.

The type tests (`api.ts`, `consumer.ts`) now assert consumers cannot reach the
protected internals.
v6-dev now spell-checks .ts files. Add the technical terms the migration
introduced: desync, disableable, extensionless, unnamespaced.
@mdo
mdo requested a review from a team as a code owner July 28, 2026 06:02
@mdo
mdo merged commit 9377fc7 into v6-dev Jul 28, 2026
12 checks passed
@mdo
mdo deleted the post-ts-followups branch July 28, 2026 06:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant