@jsenv/navi 0.26.19 → 0.26.20

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.
@@ -1,5 +1,5 @@
1
1
  import { installImportMetaCssBuild } from "./jsenv_navi_side_effects.js";
2
- import { isValidElement, createContext, h, options, toChildArray, render, cloneElement } from "preact";
2
+ import { isValidElement, createContext, h, toChildArray, render, cloneElement } from "preact";
3
3
  import { useErrorBoundary, useLayoutEffect, useEffect, useContext, useMemo, useRef, useState, useCallback, useImperativeHandle, useId } from "preact/hooks";
4
4
  import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
5
5
  import { signal, effect, computed, batch, useSignal } from "@preact/signals";
@@ -8041,72 +8041,42 @@ const updateStyle = (element, style, preventInitialTransition) => {
8041
8041
  styleKeySetWeakMap.set(element, styleKeySet);
8042
8042
  };
8043
8043
 
8044
- // Implementation notes:
8045
- //
8046
- // options.__r fires before each component render — we capture the current
8047
- // component instance (vnode.__c) so useEarlyDOMEffect can register itself.
8048
- //
8049
- // options.__c (commitRoot) fires after refs are assigned and before any
8050
- // useLayoutEffect runs. We flush all pending effects there.
8051
- // The DOM node is read from component.__v.__e (vnode → root DOM node),
8052
- // which Preact sets during diffing, before options.__c fires.
8053
- //
8054
- // stateMap (WeakMap) stores { cleanup, deps } per component instance.
8055
- // It's auto-GC'd when a component is destroyed; options.unmount also
8056
- // deletes entries eagerly to release cleanup functions sooner.
8057
- //
8058
- // pendingMap (Map) holds effects registered during the current render pass.
8059
- // It is always fully cleared in options.__c — bounded to one commit, no leak.
8060
-
8061
8044
  /**
8062
- * Like useLayoutEffect, but runs before any layout effect in the commit —
8063
- * including those of descendant components.
8064
- *
8065
- * Use this when a parent needs to mutate the DOM (e.g. apply styles) so that
8066
- * children can read those mutations in their own useLayoutEffect.
8067
- *
8068
- * The DOM node of the component is passed as the first argument to fn.
8069
- * The effect is skipped if no DOM node is found (e.g. on a fragment root).
8070
- *
8071
- * Supports deps and cleanup return, same as useLayoutEffect.
8045
+ * Keeps a DOM element in sync with `syncElement(el)` whenever deps change.
8046
+ * - If element is already mounted: runs syncElement immediately during render.
8047
+ * - If not yet mounted: runs syncElement in the ref callback when element arrives.
8048
+ * - Calls cleanup (if returned by syncElement) before each re-run and on unmount.
8049
+ *
8050
+ * @param {function|object|null} externalRef - Optional ref to forward to
8051
+ * @param {function} syncElement - Called with the DOM element when deps change
8052
+ * @param {Array} deps - syncElement is re-called only when deps change
8072
8053
  */
8073
- const useEarlyDOMEffect = (fn, deps, { needDOMNode = true } = {}) => {
8074
- const component = _currentComponent;
8075
- if (component) {
8076
- pendingMap.set(component, { fn, deps, needDOMNode });
8077
- }
8078
- };
8079
-
8080
- // Populated during render, consumed + cleared in options.__c each commit.
8081
- const pendingMap = new Map(); // component → { fn, deps, ref }
8082
-
8083
- // Persists across commits. WeakMap → no leak when component is destroyed.
8084
- const stateMap = new WeakMap(); // component → { cleanup, deps }
8054
+ const useElementRefEffect = (externalRef, syncElement, deps) => {
8055
+ const cleanupRef = useRef(null);
8056
+ const elRef = useRef(null);
8057
+ const prevDepsRef = useRef(undefined);
8058
+ const refCallbackRef = useRef(null);
8085
8059
 
8086
- let _currentComponent = null;
8087
- const _prevBeforeRender = options.__r;
8088
- options.__r = (vnode) => {
8089
- _currentComponent = vnode.__c;
8090
- if (_prevBeforeRender) {
8091
- _prevBeforeRender(vnode);
8092
- }
8093
- };
8094
-
8095
- const _prevCommit = options.__c;
8096
- options.__c = (root, commitQueue) => {
8097
- for (const [component, { fn, deps, needDOMNode }] of pendingMap) {
8098
- // component.__v is the component's vnode; __e is its root DOM node.
8099
- // Both are set during diff, before options.__c fires.
8100
- const element = component.__v && component.__v.__e;
8101
- if (needDOMNode && !element) {
8102
- continue;
8060
+ const runSync = (el) => {
8061
+ if (cleanupRef.current) {
8062
+ cleanupRef.current();
8063
+ cleanupRef.current = null;
8103
8064
  }
8104
- const prev = stateMap.get(component);
8105
- const prevDeps = prev ? prev.deps : undefined;
8065
+ prevDepsRef.current = deps;
8066
+ const cleanup = syncElement(el);
8067
+ if (typeof cleanup === "function") {
8068
+ cleanupRef.current = cleanup;
8069
+ }
8070
+ };
8071
+
8072
+ // If element already mounted, check deps and sync during render.
8073
+ if (elRef.current) {
8074
+ const prevDeps = prevDepsRef.current;
8106
8075
  let depsChanged;
8107
- if (!prevDeps || !deps || prevDeps.length !== deps.length) {
8076
+ if (!prevDeps || prevDeps.length !== deps.length) {
8108
8077
  depsChanged = true;
8109
8078
  } else {
8079
+ depsChanged = false;
8110
8080
  for (let i = 0; i < deps.length; i++) {
8111
8081
  if (!Object.is(deps[i], prevDeps[i])) {
8112
8082
  depsChanged = true;
@@ -8115,35 +8085,35 @@ options.__c = (root, commitQueue) => {
8115
8085
  }
8116
8086
  }
8117
8087
  if (depsChanged) {
8118
- if (prev && prev.cleanup) {
8119
- prev.cleanup();
8120
- }
8121
- const result = fn(element);
8122
- const cleanup = typeof result === "function" ? result : undefined;
8123
- stateMap.set(component, { cleanup, deps });
8088
+ runSync(elRef.current);
8124
8089
  }
8125
8090
  }
8126
- pendingMap.clear();
8127
- if (_prevCommit) {
8128
- _prevCommit(root, commitQueue);
8129
- }
8130
- };
8131
8091
 
8132
- const _prevUnmount = options.unmount;
8133
- options.unmount = (vnode) => {
8134
- const component = vnode.__c;
8135
- if (component) {
8136
- const state = stateMap.get(component);
8137
- if (state && state.cleanup) {
8138
- state.cleanup();
8139
- }
8140
- // stateMap is a WeakMap so the entry is GC'd automatically,
8141
- // but deleting explicitly releases the cleanup fn sooner.
8142
- stateMap.delete(component);
8143
- }
8144
- if (_prevUnmount) {
8145
- _prevUnmount(vnode);
8092
+ if (!refCallbackRef.current) {
8093
+ refCallbackRef.current = (el) => {
8094
+ elRef.current = el;
8095
+ if (externalRef) {
8096
+ if (typeof externalRef === "function") {
8097
+ externalRef(el);
8098
+ } else {
8099
+ externalRef.current = el;
8100
+ }
8101
+ }
8102
+ if (el) {
8103
+ runSync(el);
8104
+ } else {
8105
+ if (cleanupRef.current) {
8106
+ cleanupRef.current();
8107
+ cleanupRef.current = null;
8108
+ }
8109
+ prevDepsRef.current = undefined;
8110
+ }
8111
+ };
8146
8112
  }
8113
+
8114
+ const refCallback = refCallbackRef.current;
8115
+ refCallback.current = elRef.current;
8116
+ return refCallback;
8147
8117
  };
8148
8118
 
8149
8119
  installImportMetaCssBuild(import.meta);/**
@@ -8323,8 +8293,7 @@ const Box = props => {
8323
8293
  separator,
8324
8294
  ...rest
8325
8295
  } = props;
8326
- const defaultRef = useRef();
8327
- const ref = props.ref || defaultRef;
8296
+ let ref;
8328
8297
  const TagName = as;
8329
8298
  const defaultDisplay = getDefaultDisplay(TagName);
8330
8299
  // Read the parent flow early so we can use it when display="inherit" is requested.
@@ -8677,9 +8646,7 @@ const Box = props => {
8677
8646
  styleDeps.push(...pseudoClasses);
8678
8647
  }
8679
8648
  }
8680
- // TODO: just use ref function, it will be called same time as early dom effect + give the dom node + be standard
8681
- // we need to implent our styleDeps tracking but that's likely very easy
8682
- useEarlyDOMEffect(boxEl => {
8649
+ ref = useElementRefEffect(props.ref, boxEl => {
8683
8650
  const pseudoStateEl = pseudoStateSelector ? boxEl.querySelector(pseudoStateSelector) : boxEl;
8684
8651
  const visualEl = visualSelector ? boxEl.querySelector(visualSelector) : null;
8685
8652
  return initPseudoStyles(pseudoStateEl, {