@lumx/react 4.19.1-alpha.8 → 4.19.1-alpha.9

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/index.d.ts CHANGED
@@ -2168,12 +2168,14 @@ declare namespace ComboboxProvider {
2168
2168
  * Hook to subscribe to a combobox event via the handle's subscriber system.
2169
2169
  *
2170
2170
  * Uses `useSyncExternalStore` (shim, for React 17 support) reading the handle's synchronous
2171
- * `getSnapshot`. This lets React pick up changes during its own commit notably the
2172
- * `optionsChange` snapshot, which the handle refreshes synchronously while coalescing the actual
2173
- * subscriber push into a microtask. Reading the snapshot during commit avoids a state update
2174
- * landing in the microtask outside `act` (which unit tests report as an un-acted update).
2171
+ * `getSnapshot` the correct pattern for an external (non-React) store. It reads the current
2172
+ * value during commit, so a consumer doesn't miss changes that happened between its render and its
2173
+ * subscription (e.g. options registered by child effects before this consumer subscribed, since
2174
+ * `optionsChange` is not replayed on subscribe), and it is tearing-safe under concurrent rendering.
2175
2175
  *
2176
- * Re-subscribes when the handle changes (e.g. trigger mount/unmount).
2176
+ * The handle dispatches synchronously, so the store update fires within the triggering render/event
2177
+ * (inside React's `act` in tests) rather than a deferred microtask. Re-subscribes when the handle
2178
+ * changes (e.g. trigger mount/unmount).
2177
2179
  */
2178
2180
  declare function useComboboxEvent<K extends keyof ComboboxEventMap>(event: K, initialValue: ComboboxEventMap[K]): ComboboxEventMap[K];
2179
2181
 
package/index.js CHANGED
@@ -6415,29 +6415,6 @@ function setupListbox(handle, signal, notify, options) {
6415
6415
  return focusNav;
6416
6416
  }
6417
6417
 
6418
- /**
6419
- * Wrap `fn` so that, no matter how many times the returned function is called within the same
6420
- * microtask cycle, `fn` only runs once at the end of that cycle.
6421
- */
6422
- function debounceMicrotask(fn) {
6423
- let running = false;
6424
- const debounced = () => {
6425
- if (!running) {
6426
- running = true;
6427
- queueMicrotask(() => {
6428
- if (running) {
6429
- running = false;
6430
- fn();
6431
- }
6432
- });
6433
- }
6434
- };
6435
- debounced.cancel = () => {
6436
- running = false;
6437
- };
6438
- return debounced;
6439
- }
6440
-
6441
6418
  /** Options for configuring the shared combobox behavior. */
6442
6419
 
6443
6420
  /**
@@ -6499,15 +6476,11 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
6499
6476
  };
6500
6477
 
6501
6478
  /**
6502
- * Last value seen for each event, kept in sync so pull-based subscribers (React's
6503
- * `useSyncExternalStore` via `handle.getSnapshot`) can read the current value during their
6504
- * own render without relying on a push landing inside a specific async boundary.
6505
- *
6506
- * `optionsChange` is special: its push is coalesced into a microtask (see
6507
- * `notifyVisibilityChange`), but its snapshot here is refreshed synchronously so React can
6508
- * pick up the change during the same commit that mounted the options, instead of via the
6509
- * later microtask (which would fire outside `act` in tests). All other events push
6510
- * synchronously, so caching in `notify` keeps their snapshot current.
6479
+ * Last value dispatched for each event. Kept in sync so pull-based subscribers React's
6480
+ * `useSyncExternalStore` via {@link ComboboxHandle.getSnapshot} can read the current value
6481
+ * during their own render/commit. This lets React catch changes that happened between a
6482
+ * consumer's render and its subscription (e.g. options registered by child effects before the
6483
+ * consumer subscribed, since `optionsChange` is not replayed on subscribe).
6511
6484
  */
6512
6485
  const latestValues = {};
6513
6486
 
@@ -6532,48 +6505,25 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
6532
6505
  }
6533
6506
 
6534
6507
  /**
6535
- * The current `optionsChange` snapshot. Refreshed synchronously by `refreshOptionsSnapshot`
6536
- * (so React can read it via `handle.getSnapshot` during the commit that mounts the options)
6537
- * and pushed to subscribers on a coalesced microtask by `flushVisibilityChange`
6538
- * (so Vue reacts once to the final state instead of hitting its recursive-update guard).
6508
+ * Notify all registered sections and fire `optionsChange` if the visible option count changed
6509
+ * or if the input value changed while the list is empty (so `emptyMessage` callbacks get
6510
+ * the updated query string).
6511
+ * Called whenever the set of visible options may have changed (option register/unregister, filter change).
6539
6512
  */
6540
- let optionsSnapshot;
6541
- /** The last snapshot actually pushed to subscribers, to avoid redundant notifications. */
6542
- let pushedOptionsSnapshot;
6543
-
6544
- /**
6545
- * Recompute the `optionsChange` snapshot when the visible option count changed, or when the
6546
- * input value changed while the list is empty (so `emptyMessage` callbacks get the updated
6547
- * query string). Runs synchronously; a new object is created only when the value actually
6548
- * changes, keeping the reference stable for pull-based subscribers.
6549
- */
6550
- function refreshOptionsSnapshot() {
6513
+ function notifyVisibilityChange() {
6514
+ for (const [sectionElement] of sectionRegistrations) {
6515
+ notifySection(sectionElement, sectionRegistrations, optionRegistrations);
6516
+ }
6551
6517
  const visibleCount = getVisibleOptionCount();
6552
6518
  const inputValue = trigger?.value ?? '';
6553
6519
  const isEmpty = visibleCount === 0;
6554
6520
  if (visibleCount !== lastOptionsLength || isEmpty && inputValue !== lastInputValue) {
6555
6521
  lastOptionsLength = visibleCount;
6556
6522
  lastInputValue = inputValue;
6557
- optionsSnapshot = {
6523
+ notify('optionsChange', {
6558
6524
  optionsLength: visibleCount,
6559
6525
  inputValue
6560
- };
6561
- latestValues.optionsChange = optionsSnapshot;
6562
- }
6563
- }
6564
-
6565
- /**
6566
- * Notify all registered sections, push `optionsChange` when the snapshot changed since the last
6567
- * push, and re-evaluate `aria-expanded`. Coalesced into a microtask so many option
6568
- * registrations in one tick collapse into a single subscriber notification.
6569
- */
6570
- const flushVisibilityChange = debounceMicrotask(() => {
6571
- for (const [sectionElement] of sectionRegistrations) {
6572
- notifySection(sectionElement, sectionRegistrations, optionRegistrations);
6573
- }
6574
- if (optionsSnapshot !== pushedOptionsSnapshot) {
6575
- pushedOptionsSnapshot = optionsSnapshot;
6576
- notify('optionsChange', optionsSnapshot);
6526
+ });
6577
6527
  }
6578
6528
 
6579
6529
  // Re-evaluate aria-expanded when the combobox is open — visible content may have
@@ -6581,16 +6531,6 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
6581
6531
  if (isOpenState) {
6582
6532
  trigger?.setAttribute('aria-expanded', String(hasVisibleContent()));
6583
6533
  }
6584
- });
6585
-
6586
- /**
6587
- * Called whenever the set of visible options may have changed (option register/unregister,
6588
- * filter change). Refreshes the snapshot synchronously (for pull-based/React subscribers) then
6589
- * schedules the coalesced push (for push-based/Vue subscribers).
6590
- */
6591
- function notifyVisibilityChange() {
6592
- refreshOptionsSnapshot();
6593
- flushVisibilityChange();
6594
6534
  }
6595
6535
 
6596
6536
  // ── Skeleton loading tracking ──────────────────────────────
@@ -6988,8 +6928,6 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
6988
6928
  lastOptionsLength = 0;
6989
6929
  lastInputValue = '';
6990
6930
  lastLoadingState = false;
6991
- optionsSnapshot = undefined;
6992
- pushedOptionsSnapshot = undefined;
6993
6931
  // Drop cached snapshots so `getSnapshot` doesn't return stale values after destroy.
6994
6932
  for (const key of Object.keys(latestValues)) {
6995
6933
  delete latestValues[key];
@@ -6997,7 +6935,6 @@ function setupCombobox(callbacks, options, onTriggerAttach) {
6997
6935
  optionRegistrations.clear();
6998
6936
  sectionRegistrations.clear();
6999
6937
  skeletonCount = 0;
7000
- flushVisibilityChange.cancel();
7001
6938
  clearTimeout(loadingTimer);
7002
6939
  announcementSent = false;
7003
6940
  // Clear all subscribers
@@ -7540,12 +7477,14 @@ function useComboboxContext() {
7540
7477
  * Hook to subscribe to a combobox event via the handle's subscriber system.
7541
7478
  *
7542
7479
  * Uses `useSyncExternalStore` (shim, for React 17 support) reading the handle's synchronous
7543
- * `getSnapshot`. This lets React pick up changes during its own commit notably the
7544
- * `optionsChange` snapshot, which the handle refreshes synchronously while coalescing the actual
7545
- * subscriber push into a microtask. Reading the snapshot during commit avoids a state update
7546
- * landing in the microtask outside `act` (which unit tests report as an un-acted update).
7547
- *
7548
- * Re-subscribes when the handle changes (e.g. trigger mount/unmount).
7480
+ * `getSnapshot` the correct pattern for an external (non-React) store. It reads the current
7481
+ * value during commit, so a consumer doesn't miss changes that happened between its render and its
7482
+ * subscription (e.g. options registered by child effects before this consumer subscribed, since
7483
+ * `optionsChange` is not replayed on subscribe), and it is tearing-safe under concurrent rendering.
7484
+ *
7485
+ * The handle dispatches synchronously, so the store update fires within the triggering render/event
7486
+ * (inside React's `act` in tests) rather than a deferred microtask. Re-subscribes when the handle
7487
+ * changes (e.g. trigger mount/unmount).
7549
7488
  */
7550
7489
  function useComboboxEvent(event, initialValue) {
7551
7490
  const {