@praxisjs/core 2.1.0 → 2.3.0

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.
Files changed (126) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/component/internals.d.ts +12 -2
  3. package/dist/component/internals.d.ts.map +1 -1
  4. package/dist/component/internals.js +25 -7
  5. package/dist/component/internals.js.map +1 -1
  6. package/dist/internal.d.ts +3 -2
  7. package/dist/internal.d.ts.map +1 -1
  8. package/dist/internal.js +3 -2
  9. package/dist/internal.js.map +1 -1
  10. package/dist/reactive/history.d.ts +2 -0
  11. package/dist/reactive/history.d.ts.map +1 -1
  12. package/dist/reactive/history.js +2 -1
  13. package/dist/reactive/history.js.map +1 -1
  14. package/dist/signal/batch.d.ts +1 -3
  15. package/dist/signal/batch.d.ts.map +1 -1
  16. package/dist/signal/batch.js +4 -16
  17. package/dist/signal/batch.js.map +1 -1
  18. package/dist/signal/computed.d.ts.map +1 -1
  19. package/dist/signal/computed.js +19 -61
  20. package/dist/signal/computed.js.map +1 -1
  21. package/dist/signal/effect.d.ts +6 -12
  22. package/dist/signal/effect.d.ts.map +1 -1
  23. package/dist/signal/effect.js +11 -74
  24. package/dist/signal/effect.js.map +1 -1
  25. package/dist/signal/graph.d.ts +111 -0
  26. package/dist/signal/graph.d.ts.map +1 -0
  27. package/dist/signal/graph.js +513 -0
  28. package/dist/signal/graph.js.map +1 -0
  29. package/dist/signal/index.d.ts +2 -1
  30. package/dist/signal/index.d.ts.map +1 -1
  31. package/dist/signal/index.js +1 -0
  32. package/dist/signal/index.js.map +1 -1
  33. package/dist/signal/peek.d.ts.map +1 -1
  34. package/dist/signal/peek.js +2 -9
  35. package/dist/signal/peek.js.map +1 -1
  36. package/dist/signal/persisted.d.ts +11 -1
  37. package/dist/signal/persisted.d.ts.map +1 -1
  38. package/dist/signal/persisted.js +39 -5
  39. package/dist/signal/persisted.js.map +1 -1
  40. package/dist/signal/selector.d.ts +5 -0
  41. package/dist/signal/selector.d.ts.map +1 -0
  42. package/dist/signal/selector.js +128 -0
  43. package/dist/signal/selector.js.map +1 -0
  44. package/dist/signal/signal.d.ts +0 -5
  45. package/dist/signal/signal.d.ts.map +1 -1
  46. package/dist/signal/signal.js +11 -81
  47. package/dist/signal/signal.js.map +1 -1
  48. package/package.json +13 -4
  49. package/src/component/internals.ts +35 -10
  50. package/src/internal.ts +27 -0
  51. package/src/reactive/history.ts +4 -1
  52. package/src/signal/batch.ts +5 -19
  53. package/src/signal/computed.ts +20 -63
  54. package/src/signal/effect.ts +12 -83
  55. package/src/signal/graph.ts +562 -0
  56. package/src/signal/index.ts +2 -1
  57. package/src/signal/peek.ts +2 -8
  58. package/src/signal/persisted.ts +54 -5
  59. package/src/signal/selector.ts +140 -0
  60. package/src/signal/signal.ts +11 -79
  61. package/dist/__tests__/batch.test.d.ts +0 -2
  62. package/dist/__tests__/batch.test.d.ts.map +0 -1
  63. package/dist/__tests__/batch.test.js +0 -73
  64. package/dist/__tests__/batch.test.js.map +0 -1
  65. package/dist/__tests__/component.test.d.ts +0 -2
  66. package/dist/__tests__/component.test.d.ts.map +0 -1
  67. package/dist/__tests__/component.test.js +0 -124
  68. package/dist/__tests__/component.test.js.map +0 -1
  69. package/dist/__tests__/computed.test.d.ts +0 -2
  70. package/dist/__tests__/computed.test.d.ts.map +0 -1
  71. package/dist/__tests__/computed.test.js +0 -252
  72. package/dist/__tests__/computed.test.js.map +0 -1
  73. package/dist/__tests__/effect.test.d.ts +0 -2
  74. package/dist/__tests__/effect.test.d.ts.map +0 -1
  75. package/dist/__tests__/effect.test.js +0 -189
  76. package/dist/__tests__/effect.test.js.map +0 -1
  77. package/dist/__tests__/peek.test.d.ts +0 -2
  78. package/dist/__tests__/peek.test.d.ts.map +0 -1
  79. package/dist/__tests__/peek.test.js +0 -26
  80. package/dist/__tests__/peek.test.js.map +0 -1
  81. package/dist/__tests__/persisted-signal.test.d.ts +0 -2
  82. package/dist/__tests__/persisted-signal.test.d.ts.map +0 -1
  83. package/dist/__tests__/persisted-signal.test.js +0 -156
  84. package/dist/__tests__/persisted-signal.test.js.map +0 -1
  85. package/dist/__tests__/reactive.test.d.ts +0 -2
  86. package/dist/__tests__/reactive.test.d.ts.map +0 -1
  87. package/dist/__tests__/reactive.test.js +0 -264
  88. package/dist/__tests__/reactive.test.js.map +0 -1
  89. package/dist/__tests__/resource-focus.test.d.ts +0 -2
  90. package/dist/__tests__/resource-focus.test.d.ts.map +0 -1
  91. package/dist/__tests__/resource-focus.test.js +0 -50
  92. package/dist/__tests__/resource-focus.test.js.map +0 -1
  93. package/dist/__tests__/resource-server-pass.test.d.ts +0 -2
  94. package/dist/__tests__/resource-server-pass.test.d.ts.map +0 -1
  95. package/dist/__tests__/resource-server-pass.test.js +0 -78
  96. package/dist/__tests__/resource-server-pass.test.js.map +0 -1
  97. package/dist/__tests__/resource.test.d.ts +0 -2
  98. package/dist/__tests__/resource.test.d.ts.map +0 -1
  99. package/dist/__tests__/resource.test.js +0 -336
  100. package/dist/__tests__/resource.test.js.map +0 -1
  101. package/dist/__tests__/signal.test.d.ts +0 -2
  102. package/dist/__tests__/signal.test.d.ts.map +0 -1
  103. package/dist/__tests__/signal.test.js +0 -186
  104. package/dist/__tests__/signal.test.js.map +0 -1
  105. package/dist/__tests__/synced.test.d.ts +0 -2
  106. package/dist/__tests__/synced.test.d.ts.map +0 -1
  107. package/dist/__tests__/synced.test.js +0 -142
  108. package/dist/__tests__/synced.test.js.map +0 -1
  109. package/dist/__tests__/track.test.d.ts +0 -2
  110. package/dist/__tests__/track.test.d.ts.map +0 -1
  111. package/dist/__tests__/track.test.js +0 -54
  112. package/dist/__tests__/track.test.js.map +0 -1
  113. package/src/__tests__/batch.test.ts +0 -87
  114. package/src/__tests__/component.test.ts +0 -165
  115. package/src/__tests__/computed.test.ts +0 -290
  116. package/src/__tests__/effect.test.ts +0 -226
  117. package/src/__tests__/peek.test.ts +0 -31
  118. package/src/__tests__/persisted-signal.test.ts +0 -194
  119. package/src/__tests__/reactive.test.ts +0 -305
  120. package/src/__tests__/resource-focus.test.ts +0 -61
  121. package/src/__tests__/resource-server-pass.test.ts +0 -90
  122. package/src/__tests__/resource.test.ts +0 -421
  123. package/src/__tests__/signal.test.ts +0 -213
  124. package/src/__tests__/synced.test.ts +0 -173
  125. package/src/__tests__/track.test.ts +0 -61
  126. package/tsconfig.json +0 -8
@@ -5,4 +5,5 @@ export { computed, writableComputed } from "./computed";
5
5
  export { persistedSignal } from "./persisted";
6
6
  export { syncedSignal } from "./synced";
7
7
  export { effect, untrack } from "./effect";
8
+ export { selector } from "./selector";
8
9
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/signal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAA+B,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAqB,YAAY,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/signal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAqD,eAAe,EAAE,MAAM,aAAa,CAAC;AACjG,OAAO,EAAqB,YAAY,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAsC,QAAQ,EAAE,MAAM,YAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"peek.d.ts","sourceRoot":"","sources":["../../src/signal/peek.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAIzD,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAQ1D"}
1
+ {"version":3,"file":"peek.d.ts","sourceRoot":"","sources":["../../src/signal/peek.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAIzD,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAE1D"}
@@ -1,12 +1,5 @@
1
- import { activeEffect, runEffect } from "./effect";
1
+ import { untrack } from "./effect";
2
2
  export function peek(source) {
3
- const prev = activeEffect;
4
- runEffect(null);
5
- try {
6
- return source();
7
- }
8
- finally {
9
- runEffect(prev);
10
- }
3
+ return untrack(source);
11
4
  }
12
5
  //# sourceMappingURL=peek.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"peek.js","sourceRoot":"","sources":["../../src/signal/peek.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEnD,MAAM,UAAU,IAAI,CAAI,MAA+B;IACrD,MAAM,IAAI,GAAG,YAAY,CAAC;IAC1B,SAAS,CAAC,IAAI,CAAC,CAAC;IAChB,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,CAAC;IAClB,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"peek.js","sourceRoot":"","sources":["../../src/signal/peek.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,MAAM,UAAU,IAAI,CAAI,MAA+B;IACrD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;AACzB,CAAC"}
@@ -3,6 +3,16 @@ export interface PersistedSignalOptions<T> {
3
3
  serialize?: (value: T) => string;
4
4
  deserialize?: (value: string) => T;
5
5
  syncTabs?: boolean;
6
+ /**
7
+ * Milliseconds to hold back writes to `localStorage`. Writes within the window are coalesced
8
+ * into one, using the latest value; pending data is flushed on `pagehide` and `close()`.
9
+ * `0` (default) writes on every `set`.
10
+ */
11
+ writeDelay?: number;
6
12
  }
7
- export declare function persistedSignal<T>(key: string, initialValue: T, options?: PersistedSignalOptions<T>): Signal<T>;
13
+ export interface PersistedSignal<T> extends Signal<T> {
14
+ /** Stops listening for `storage` events from other tabs. The signal keeps working locally. */
15
+ close(): void;
16
+ }
17
+ export declare function persistedSignal<T>(key: string, initialValue: T, options?: PersistedSignalOptions<T>): PersistedSignal<T>;
8
18
  //# sourceMappingURL=persisted.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"persisted.d.ts","sourceRoot":"","sources":["../../src/signal/persisted.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAI/C,MAAM,WAAW,sBAAsB,CAAC,CAAC;IACvC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;IACjC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,CAAC,EACf,OAAO,GAAE,sBAAsB,CAAC,CAAC,CAAM,aAwExC"}
1
+ {"version":3,"file":"persisted.d.ts","sourceRoot":"","sources":["../../src/signal/persisted.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAI/C,MAAM,WAAW,sBAAsB,CAAC,CAAC;IACvC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;IACjC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe,CAAC,CAAC,CAAE,SAAQ,MAAM,CAAC,CAAC,CAAC;IACnD,8FAA8F;IAC9F,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,eAAe,CAAC,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,CAAC,EACf,OAAO,GAAE,sBAAsB,CAAC,CAAC,CAAM,sBA8GxC"}
@@ -1,6 +1,6 @@
1
1
  import { signal } from "./signal";
2
2
  export function persistedSignal(key, initialValue, options = {}) {
3
- const { serialize = JSON.stringify, deserialize = JSON.parse, syncTabs = true, } = options;
3
+ const { serialize = JSON.stringify, deserialize = JSON.parse, syncTabs = true, writeDelay = 0, } = options;
4
4
  function getStoredValue() {
5
5
  try {
6
6
  const stored = localStorage.getItem(key);
@@ -24,23 +24,47 @@ export function persistedSignal(key, initialValue, options = {}) {
24
24
  console.warn(`Failed to serialize value for key "${key}":`, e);
25
25
  }
26
26
  }
27
+ let pendingWrite;
28
+ let writeTimer;
29
+ function flushWrite() {
30
+ clearTimeout(writeTimer);
31
+ writeTimer = undefined;
32
+ if (!pendingWrite)
33
+ return;
34
+ const { value } = pendingWrite;
35
+ pendingWrite = undefined;
36
+ setStoredValue(value);
37
+ }
38
+ // The timer starts at the first write of a burst (throttle, not debounce) so a value that keeps
39
+ // changing still reaches storage within `writeDelay`.
40
+ function scheduleWrite(value) {
41
+ if (writeDelay <= 0) {
42
+ setStoredValue(value);
43
+ return;
44
+ }
45
+ pendingWrite = { value };
46
+ writeTimer ??= setTimeout(flushWrite, writeDelay);
47
+ }
27
48
  const inner = signal(getStoredValue());
28
49
  function read() {
29
50
  return inner();
30
51
  }
31
52
  function set(value) {
32
- setStoredValue(value);
53
+ scheduleWrite(value);
33
54
  inner.set(value);
34
55
  }
35
56
  function update(fh) {
36
57
  const newValue = fh(inner());
37
- setStoredValue(newValue);
58
+ scheduleWrite(newValue);
38
59
  inner.set(newValue);
39
60
  }
61
+ let removeStorageListener;
40
62
  if (syncTabs) {
41
- window.addEventListener("storage", (event) => {
63
+ const onStorage = (event) => {
42
64
  if (event.key !== key || event.storageArea !== localStorage)
43
65
  return;
66
+ // Another tab's value wins; a still-pending local write would overwrite it later.
67
+ pendingWrite = undefined;
44
68
  try {
45
69
  const newValue = event.newValue
46
70
  ? deserialize(event.newValue)
@@ -51,13 +75,23 @@ export function persistedSignal(key, initialValue, options = {}) {
51
75
  console.warn(`Failed to deserialize value for key "${key}" from storage event:`, e);
52
76
  inner.set(initialValue);
53
77
  }
54
- });
78
+ };
79
+ window.addEventListener("storage", onStorage);
80
+ removeStorageListener = () => { window.removeEventListener("storage", onStorage); };
55
81
  }
82
+ if (writeDelay > 0)
83
+ window.addEventListener("pagehide", flushWrite);
56
84
  const source = read;
57
85
  source.set = set;
58
86
  source.update = update;
59
87
  source.subscribe = inner.subscribe.bind(inner);
60
88
  source.__isSignal = true;
89
+ source.close = () => {
90
+ removeStorageListener?.();
91
+ removeStorageListener = undefined;
92
+ window.removeEventListener("pagehide", flushWrite);
93
+ flushWrite();
94
+ };
61
95
  return source;
62
96
  }
63
97
  //# sourceMappingURL=persisted.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"persisted.js","sourceRoot":"","sources":["../../src/signal/persisted.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAQlC,MAAM,UAAU,eAAe,CAC7B,GAAW,EACX,YAAe,EACf,UAAqC,EAAE;IAEvC,MAAM,EACJ,SAAS,GAAG,IAAI,CAAC,SAAS,EAC1B,WAAW,GAAG,IAAI,CAAC,KAA6B,EAChD,QAAQ,GAAG,IAAI,GAChB,GAAG,OAAO,CAAC;IAEZ,SAAS,cAAc;QACrB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACzC,OAAO,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;QACrD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,wCAAwC,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;YACjE,OAAO,YAAY,CAAC;QACtB,CAAC;IACH,CAAC;IAED,SAAS,cAAc,CAAC,KAAQ;QAC9B,IAAI,CAAC;YACH,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBAC1C,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YAC/B,CAAC;iBAAM,CAAC;gBACN,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,sCAAsC,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;IAEvC,SAAS,IAAI;QACX,OAAO,KAAK,EAAE,CAAC;IACjB,CAAC;IAED,SAAS,GAAG,CAAC,KAAQ;QACnB,cAAc,CAAC,KAAK,CAAC,CAAC;QACtB,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;IAED,SAAS,MAAM,CAAC,EAAkB;QAChC,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7B,cAAc,CAAC,QAAQ,CAAC,CAAC;QACzB,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtB,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;YAC3C,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,IAAI,KAAK,CAAC,WAAW,KAAK,YAAY;gBAAE,OAAO;YACpE,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ;oBAC7B,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC;oBAC7B,CAAC,CAAC,YAAY,CAAC;gBACjB,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACtB,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,CAAC,IAAI,CACV,wCAAwC,GAAG,uBAAuB,EAClE,CAAC,CACF,CAAC;gBACF,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,MAAM,GAAG,IAAiB,CAAC;IACjC,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,MAAM,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/C,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;IAEzB,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"persisted.js","sourceRoot":"","sources":["../../src/signal/persisted.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export type Selector<K> = (key: K) => boolean;
2
+ export type SelectorEquals<K, T> = (key: K, value: T) => boolean;
3
+ export declare function selector<T>(source: () => T): Selector<T>;
4
+ export declare function selector<T, K>(source: () => T, equals: SelectorEquals<K, T>): Selector<K>;
5
+ //# sourceMappingURL=selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../../src/signal/selector.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;AAC9C,MAAM,MAAM,cAAc,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;AAyCjE,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC1D,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,128 @@
1
+ import { activeSub, createEffect, DIRTY, disposeEffect, endBatch, notify, PENDING, startBatch, track, WatchedNode } from "./graph";
2
+ // Keys live in a Map, so the default comparison has to be the Map's own (SameValueZero): a key
3
+ // that compares equal must be the one the lookup finds, or a bucket would never be notified.
4
+ function sameValueZero(a, b) {
5
+ return a === b || (Number.isNaN(a) && Number.isNaN(b));
6
+ }
7
+ // One class for every selector's buckets (rather than one declared per selector call), so the
8
+ // graph code that reads and links them always sees the same shape.
9
+ // Declared and assigned rather than parameter properties, which compile to class fields that are
10
+ // defined one by one and make buckets (one per row reading a selector) slower to create.
11
+ class Bucket extends WatchedNode {
12
+ constructor(key, owner) {
13
+ super();
14
+ this.key = key;
15
+ this.owner = owner;
16
+ }
17
+ // A bucket drops itself from its selector once its last subscriber unlinks (re-run or disposal).
18
+ unwatched() {
19
+ this.owner.drop(this.key);
20
+ }
21
+ }
22
+ // Keeps the bucket shape alive across full GCs; see the shape keepers in graph.ts.
23
+ Object.defineProperty(Bucket, "shapeKeeper", {
24
+ value: new Bucket(undefined, {
25
+ drop() {
26
+ // Never called: the keeper has no subscriber to lose.
27
+ },
28
+ }),
29
+ });
30
+ export function selector(source, equals) {
31
+ const compare = equals ?? sameValueZero;
32
+ const buckets = new Map();
33
+ let current;
34
+ let sourceEffect = null;
35
+ let releaseScheduled = false;
36
+ // The source subscription goes with the last bucket, so removed rows leave nothing behind.
37
+ const owner = {
38
+ drop(key) {
39
+ buckets.delete(key);
40
+ if (buckets.size === 0)
41
+ scheduleRelease();
42
+ },
43
+ };
44
+ function changedBuckets(prev, next) {
45
+ const changed = [];
46
+ if (equals === undefined) {
47
+ if (sameValueZero(prev, next))
48
+ return changed;
49
+ const prevBucket = buckets.get(prev);
50
+ const nextBucket = buckets.get(next);
51
+ if (prevBucket !== undefined)
52
+ changed.push(prevBucket);
53
+ if (nextBucket !== undefined)
54
+ changed.push(nextBucket);
55
+ return changed;
56
+ }
57
+ for (const [key, bucket] of buckets) {
58
+ if (compare(key, prev) !== compare(key, next))
59
+ changed.push(bucket);
60
+ }
61
+ return changed;
62
+ }
63
+ // Batched so an effect reading several affected keys re-runs once, and so no subscriber runs
64
+ // (and unlinks) while the buckets are still being walked.
65
+ function notifyChanged(prev, next) {
66
+ const changed = changedBuckets(prev, next);
67
+ if (changed.length === 0)
68
+ return;
69
+ startBatch();
70
+ try {
71
+ for (const bucket of changed) {
72
+ bucket.version++;
73
+ notify(bucket);
74
+ }
75
+ }
76
+ finally {
77
+ endBatch();
78
+ }
79
+ }
80
+ function activate() {
81
+ let initialized = false;
82
+ sourceEffect = createEffect(() => {
83
+ const next = source();
84
+ const prev = current;
85
+ current = next;
86
+ if (!initialized) {
87
+ initialized = true;
88
+ return;
89
+ }
90
+ notifyChanged(prev, next);
91
+ });
92
+ }
93
+ // An effect re-running unlinks its bucket right before reading the key again, and a list
94
+ // re-render unlinks every bucket before the new rows subscribe. Waiting a microtask keeps the
95
+ // source subscribed through both instead of stopping and re-reading it each time.
96
+ function scheduleRelease() {
97
+ if (releaseScheduled)
98
+ return;
99
+ releaseScheduled = true;
100
+ queueMicrotask(() => {
101
+ releaseScheduled = false;
102
+ if (buckets.size > 0)
103
+ return;
104
+ const stop = sourceEffect;
105
+ sourceEffect = null;
106
+ disposeEffect(stop);
107
+ });
108
+ }
109
+ return function select(key) {
110
+ if (activeSub === undefined) {
111
+ // `current` lags the source while the source effect waits to run (a write inside a batch, or
112
+ // a computed source, whose effects run a microtask later). A tracked read keeps the cached
113
+ // value, which the effect's run then corrects through the buckets; nothing corrects this one.
114
+ const fresh = sourceEffect !== null && (sourceEffect.flags & (DIRTY | PENDING)) === 0;
115
+ return compare(key, fresh ? current : source());
116
+ }
117
+ if (sourceEffect === null)
118
+ activate();
119
+ let bucket = buckets.get(key);
120
+ if (bucket === undefined) {
121
+ bucket = new Bucket(key, owner);
122
+ buckets.set(key, bucket);
123
+ }
124
+ track(bucket);
125
+ return compare(key, current);
126
+ };
127
+ }
128
+ //# sourceMappingURL=selector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.js","sourceRoot":"","sources":["../../src/signal/selector.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,3 @@
1
1
  import type { Signal } from "@praxisjs/shared";
2
- import { type Effect, type SubList, type SubscriberHolder } from "./effect";
3
- export type { SubList } from "./effect";
4
- export declare function addSub(holder: SubscriberHolder, eff: Effect): void;
5
- export declare function removeSub(holder: SubscriberHolder, eff: Effect): void;
6
- export declare function notifySubs(subs: Exclude<SubList, null>, batching: boolean): void;
7
2
  export declare function signal<T>(initialValue: T): Signal<T>;
8
3
  //# sourceMappingURL=signal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"signal.d.ts","sourceRoot":"","sources":["../../src/signal/signal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,OAAO,EACZ,KAAK,gBAAgB,EACtB,MAAM,UAAU,CAAC;AAElB,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,wBAAgB,MAAM,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAUlE;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAUrE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAqBhF;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CA4BpD"}
1
+ {"version":3,"file":"signal.d.ts","sourceRoot":"","sources":["../../src/signal/signal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAK/C,wBAAgB,MAAM,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAepD"}
@@ -1,86 +1,16 @@
1
- import { isBatching, enqueueEffect } from "./batch";
2
- import { activeEffect, recordDependency, } from "./effect";
3
- export function addSub(holder, eff) {
4
- const subs = holder.subs;
5
- if (subs === null) {
6
- holder.subs = eff;
7
- }
8
- else if (typeof subs === "function") {
9
- if (subs !== eff)
10
- holder.subs = [subs, eff];
11
- }
12
- else {
13
- if (!subs.includes(eff))
14
- subs.push(eff);
15
- }
16
- recordDependency(eff, holder);
17
- }
18
- export function removeSub(holder, eff) {
19
- const subs = holder.subs;
20
- if (subs === null)
21
- return;
22
- if (typeof subs === "function") {
23
- if (subs === eff)
24
- holder.subs = null;
25
- }
26
- else {
27
- const idx = subs.indexOf(eff);
28
- if (idx >= 0)
29
- subs.splice(idx, 1);
30
- if (subs.length === 0)
31
- holder.subs = null;
32
- }
33
- }
34
- export function notifySubs(subs, batching) {
35
- if (typeof subs === "function") {
36
- if (batching)
37
- enqueueEffect(subs);
38
- else
39
- subs();
40
- return;
41
- }
42
- const snapshot = [...subs];
43
- if (batching) {
44
- for (const sub of snapshot)
45
- enqueueEffect(sub);
46
- return;
47
- }
48
- let lastError;
49
- let hasError = false;
50
- for (const sub of snapshot) {
51
- try {
52
- sub();
53
- }
54
- catch (e) {
55
- lastError = e;
56
- hasError = true;
57
- }
58
- }
59
- if (hasError)
60
- throw lastError;
61
- }
1
+ import { untrack } from "./effect";
2
+ import { createEffect, disposeEffect, readSignal, SignalNode, writeSignal } from "./graph";
62
3
  export function signal(initialValue) {
63
- let value = initialValue;
64
- const holder = { subs: null };
65
- function read() {
66
- if (activeEffect !== null)
67
- addSub(holder, activeEffect);
68
- return value;
69
- }
70
- const s = read;
71
- s.set = (newValue) => {
72
- if (Object.is(value, newValue))
73
- return;
74
- value = newValue;
75
- if (holder.subs !== null)
76
- notifySubs(holder.subs, isBatching());
77
- };
78
- s.update = (fn) => { s.set(fn(value)); };
4
+ const node = new SignalNode(initialValue);
5
+ const s = (() => readSignal(node));
6
+ s.set = (newValue) => { writeSignal(node, newValue); };
7
+ s.update = (fn) => { writeSignal(node, fn(node.value)); };
79
8
  s.subscribe = (fn) => {
80
- const wrapped = () => { fn(value); };
81
- addSub(holder, wrapped);
82
- wrapped();
83
- return () => { removeSub(holder, wrapped); };
9
+ const sub = createEffect(() => {
10
+ const value = readSignal(node);
11
+ untrack(() => { fn(value); });
12
+ });
13
+ return () => { disposeEffect(sub); };
84
14
  };
85
15
  s.__isSignal = true;
86
16
  return s;
@@ -1 +1 @@
1
- {"version":3,"file":"signal.js","sourceRoot":"","sources":["../../src/signal/signal.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EACL,YAAY,EACZ,gBAAgB,GAIjB,MAAM,UAAU,CAAC;AAIlB,MAAM,UAAU,MAAM,CAAC,MAAwB,EAAE,GAAW;IAC1D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACzB,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,MAAM,CAAC,IAAI,GAAG,GAAG,CAAC;IACpB,CAAC;SAAM,IAAI,OAAO,IAAI,KAAK,UAAU,EAAE,CAAC;QACtC,IAAI,IAAI,KAAK,GAAG;YAAE,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC;IACD,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAwB,EAAE,GAAW;IAC7D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACzB,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO;IAC1B,IAAI,OAAO,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,IAAI,IAAI,KAAK,GAAG;YAAE,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,GAAG,IAAI,CAAC;YAAE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAClC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;IAC5C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAA4B,EAAE,QAAiB;IACxE,IAAI,OAAO,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,IAAI,QAAQ;YAAE,aAAa,CAAC,IAAI,CAAC,CAAC;;YAAM,IAAI,EAAE,CAAC;QAC/C,OAAO;IACT,CAAC;IACD,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3B,IAAI,QAAQ,EAAE,CAAC;QACb,KAAK,MAAM,GAAG,IAAI,QAAQ;YAAE,aAAa,CAAC,GAAG,CAAC,CAAC;QAC/C,OAAO;IACT,CAAC;IACD,IAAI,SAAkB,CAAC;IACvB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,GAAG,EAAE,CAAC;QACR,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,SAAS,GAAG,CAAC,CAAC;YACd,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;IACH,CAAC;IACD,IAAI,QAAQ;QAAE,MAAM,SAAS,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,MAAM,CAAI,YAAe;IACvC,IAAI,KAAK,GAAG,YAAY,CAAC;IACzB,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,IAAe,EAAE,CAAC;IAEzC,SAAS,IAAI;QACX,IAAI,YAAY,KAAK,IAAI;YAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QACxD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,CAAC,GAAG,IAAiB,CAAC;IAE5B,CAAC,CAAC,GAAG,GAAG,CAAC,QAAW,EAAE,EAAE;QACtB,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC;YAAE,OAAO;QACvC,KAAK,GAAG,QAAQ,CAAC;QACjB,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI;YAAE,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC;IAEF,CAAC,CAAC,MAAM,GAAG,CAAC,EAAkB,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEzD,CAAC,CAAC,SAAS,GAAG,CAAC,EAAsB,EAAE,EAAE;QACvC,MAAM,OAAO,GAAW,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACxB,OAAO,EAAE,CAAC;QACV,OAAO,GAAG,EAAE,GAAG,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC;IAEF,CAAC,CAAC,UAAU,GAAG,IAAI,CAAC;IACpB,OAAO,CAAC,CAAC;AACX,CAAC"}
1
+ {"version":3,"file":"signal.js","sourceRoot":"","sources":["../../src/signal/signal.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3F,MAAM,UAAU,MAAM,CAAI,YAAe;IACvC,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;IAE1C,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAc,CAAC;IAChD,CAAC,CAAC,GAAG,GAAG,CAAC,QAAW,EAAE,EAAE,GAAG,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,MAAM,GAAG,CAAC,EAAkB,EAAE,EAAE,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1E,CAAC,CAAC,SAAS,GAAG,CAAC,EAAsB,EAAE,EAAE;QACvC,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,EAAE;YAC5B,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YAC/B,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC;IACF,CAAC,CAAC,UAAU,GAAG,IAAI,CAAC;IACpB,OAAO,CAAC,CAAC;AACX,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@praxisjs/core",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "type": "module",
5
+ "sideEffects": false,
5
6
  "main": "./dist/index.js",
6
7
  "types": "./dist/index.d.ts",
7
8
  "exports": {
@@ -14,14 +15,22 @@
14
15
  "types": "./dist/internal.d.ts"
15
16
  }
16
17
  },
18
+ "files": [
19
+ "dist",
20
+ "src",
21
+ "!dist/**/__tests__",
22
+ "!src/**/__tests__",
23
+ "CHANGELOG.md"
24
+ ],
17
25
  "devDependencies": {
18
26
  "typescript": "^6.0.3"
19
27
  },
20
28
  "dependencies": {
21
- "@praxisjs/shared": "0.3.1"
29
+ "@praxisjs/shared": "0.3.2"
22
30
  },
23
31
  "scripts": {
24
- "build": "tsc",
25
- "dev": "tsc --watch"
32
+ "build": "tsc -p tsconfig.build.json",
33
+ "dev": "tsc -p tsconfig.build.json --watch",
34
+ "typecheck": "tsc --noEmit"
26
35
  }
27
36
  }
@@ -3,15 +3,22 @@ export const componentPropsType: unique symbol = Symbol("praxis.componentPropsTy
3
3
  interface ComponentInternals<T extends object> {
4
4
  rawProps: T;
5
5
  mounted: boolean;
6
- anchor?: Comment;
6
+ anchor: Node | undefined;
7
+ start: Comment | undefined;
7
8
  defaults: Record<string, unknown>;
8
9
  stateDirty: boolean;
9
10
  }
10
11
 
11
- const componentInternals = new WeakMap<object, ComponentInternals<object>>();
12
+ // Kept on the instance under a symbol rather than in a WeakMap: every @Prop read goes through
13
+ // here, and a property load is much cheaper than a WeakMap lookup.
14
+ const INTERNALS: unique symbol = Symbol("praxis.internals");
15
+
16
+ interface InternalsHost {
17
+ [INTERNALS]?: ComponentInternals<object>;
18
+ }
12
19
 
13
20
  function getInternals(instance: object): ComponentInternals<object> {
14
- const internals = componentInternals.get(instance);
21
+ const internals = (instance as InternalsHost)[INTERNALS];
15
22
  if (!internals) {
16
23
  throw new TypeError("Expected a PraxisJS component instance.");
17
24
  }
@@ -22,12 +29,14 @@ export function initComponentInternals(
22
29
  instance: object,
23
30
  props: object,
24
31
  ): void {
25
- componentInternals.set(instance, {
32
+ (instance as InternalsHost)[INTERNALS] = {
26
33
  rawProps: { ...props },
27
34
  mounted: false,
35
+ anchor: undefined,
36
+ start: undefined,
28
37
  defaults: {},
29
38
  stateDirty: false,
30
- });
39
+ };
31
40
  }
32
41
 
33
42
  export function getComponentProps(instance: object): object {
@@ -73,13 +82,17 @@ export function getComponentDefaults(
73
82
  return getInternals(instance).defaults;
74
83
  }
75
84
 
76
- export function getComponentAnchor(instance: object): Comment | undefined {
85
+ /**
86
+ * A node of the mounted component's output: its only root node, or the comment that closes its
87
+ * region when it renders several nodes. Its `parentElement` is the element the component sits in.
88
+ */
89
+ export function getComponentAnchor(instance: object): Node | undefined {
77
90
  return getInternals(instance).anchor;
78
91
  }
79
92
 
80
93
  export function setComponentAnchor(
81
94
  instance: object,
82
- anchor: Comment | undefined,
95
+ anchor: Node | undefined,
83
96
  ): void {
84
97
  getInternals(instance).anchor = anchor;
85
98
  }
@@ -96,14 +109,14 @@ export function setComponentMounted(
96
109
  }
97
110
 
98
111
  export function markStateDirty(instance: object): void {
99
- const internals = componentInternals.get(instance);
112
+ const internals = (instance as InternalsHost)[INTERNALS];
100
113
  if (internals) {
101
114
  internals.stateDirty = true;
102
115
  }
103
116
  }
104
117
 
105
118
  export function isStateDirty(instance: object): boolean {
106
- const internals = componentInternals.get(instance);
119
+ const internals = (instance as InternalsHost)[INTERNALS];
107
120
  if (internals) return internals.stateDirty;
108
121
  return false;
109
122
  }
@@ -112,8 +125,20 @@ export function setStateDirty(
112
125
  instance: object,
113
126
  stateDirty: boolean,
114
127
  ): void {
115
- const internals = componentInternals.get(instance);
128
+ const internals = (instance as InternalsHost)[INTERNALS];
116
129
  if (internals) {
117
130
  internals.stateDirty = stateDirty;
118
131
  }
119
132
  }
133
+
134
+ /**
135
+ * The comment that opens a component's region, when it has one: a component whose render sets it
136
+ * (with the closing comment as its anchor) brings its own boundaries, and mountComponent adds none.
137
+ */
138
+ export function getComponentStart(instance: object): Comment | undefined {
139
+ return getInternals(instance).start;
140
+ }
141
+
142
+ export function setComponentStart(instance: object, start: Comment | undefined): void {
143
+ getInternals(instance).start = start;
144
+ }
package/src/internal.ts CHANGED
@@ -15,10 +15,35 @@ export {
15
15
  batch,
16
16
  effect,
17
17
  untrack,
18
+ selector,
19
+ type Selector,
20
+ type SelectorEquals,
18
21
  type PersistedSignalOptions,
22
+ type PersistedSignal,
19
23
  type SyncedSignal,
20
24
  } from "./signal";
21
25
  export { RootComponent } from "./component";
26
+ export {
27
+ createEffect,
28
+ createContextEffect,
29
+ disposeEffect,
30
+ startBatch,
31
+ endBatch,
32
+ EffectNode,
33
+ SignalNode,
34
+ readSignal,
35
+ writeSignal,
36
+ ComputedNode,
37
+ readComputed,
38
+ type ReactiveNode,
39
+ pauseTracking,
40
+ resumeTracking,
41
+ setReactiveObserver,
42
+ setCurrentOwner,
43
+ getEffectOwner,
44
+ type ReactiveObserver,
45
+ type EffectCleanup,
46
+ } from "./signal/graph";
22
47
  export {
23
48
  componentPropsType,
24
49
  getComponentAnchor,
@@ -26,6 +51,8 @@ export {
26
51
  getComponentDefaults,
27
52
  getComponentProps,
28
53
  getComponentRawProp,
54
+ getComponentStart,
55
+ setComponentStart,
29
56
  isComponentMounted,
30
57
  isStateDirty,
31
58
  markStateDirty,
@@ -12,6 +12,8 @@ export interface HistoryElement<T> {
12
12
  undo(): void;
13
13
  redo(): void;
14
14
  clear(): void;
15
+ /** Stops tracking the source. Existing entries stay readable; no new ones are recorded. */
16
+ destroy(): void;
15
17
  }
16
18
 
17
19
  export function history<T>(
@@ -25,7 +27,7 @@ export function history<T>(
25
27
  let _ignoreNext = false;
26
28
  let _initialized = false;
27
29
 
28
- effect(() => {
30
+ const stop = effect(() => {
29
31
  const value = source();
30
32
 
31
33
  if (!_initialized) {
@@ -83,5 +85,6 @@ export function history<T>(
83
85
  _past.set([]);
84
86
  _future.set([]);
85
87
  },
88
+ destroy: stop,
86
89
  };
87
90
  }