@player-ui/react-subscribe 0.16.0--canary.866.36538 → 1.0.0--canary.865.36694

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.
@@ -31,7 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
33
  Subscribe: () => Subscribe,
34
- useSubscribedState: () => useSubscribedState
34
+ useSubscribedState: () => useSubscribedState,
35
+ useSubscriber: () => useSubscriber
35
36
  });
36
37
  module.exports = __toCommonJS(src_exports);
37
38
  var import_react = __toESM(require("react"));
@@ -65,6 +66,7 @@ var Subscribe = class {
65
66
  this.deferredResult = deferred();
66
67
  this.lastValue = NOT_CALLED;
67
68
  this.resetDeferred = null;
69
+ this.lastId = 0;
68
70
  this.publish = this.publish.bind(this);
69
71
  this.add = this.add.bind(this);
70
72
  this.remove = this.remove.bind(this);
@@ -83,7 +85,7 @@ var Subscribe = class {
83
85
  * Subscribe to updates
84
86
  */
85
87
  add(callback, options) {
86
- const id = this.callbacks.size;
88
+ const id = this.lastId++;
87
89
  this.callbacks.set(id, callback);
88
90
  if (this.lastValue !== NOT_CALLED && options?.initializeWithPreviousValue === true) {
89
91
  callback(this.lastValue);
@@ -164,9 +166,33 @@ function useSubscribedState(subscriber) {
164
166
  );
165
167
  return state;
166
168
  }
169
+ function useSubscriber(subscriber) {
170
+ const subscriptions = import_react.default.useMemo(
171
+ () => /* @__PURE__ */ new Set(),
172
+ []
173
+ );
174
+ import_react.default.useEffect(() => {
175
+ return () => {
176
+ for (const subId of subscriptions.values()) {
177
+ subscriber.remove(subId);
178
+ }
179
+ };
180
+ }, [subscriptions]);
181
+ const unsubscribe = (0, import_react.useCallback)((id) => {
182
+ subscriptions.delete(id);
183
+ subscriber.remove(id);
184
+ }, []);
185
+ const subscribe = (0, import_react.useCallback)((callback, options) => {
186
+ const id = subscriber.add(callback, options);
187
+ subscriptions.add(id);
188
+ return id;
189
+ }, []);
190
+ return { subscribe, unsubscribe };
191
+ }
167
192
  // Annotate the CommonJS export names for ESM import in node:
168
193
  0 && (module.exports = {
169
194
  Subscribe,
170
- useSubscribedState
195
+ useSubscribedState,
196
+ useSubscriber
171
197
  });
172
198
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/react/subscribe/src/index.tsx"],"sourcesContent":["import React from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim\";\n\nexport type SubscribeID = number;\n\ntype ResolveType<T> = (arg?: T) => void;\ntype RejectType = (error?: Error) => void;\ntype StatusType = \"success\" | \"failure\" | \"pending\";\ntype DefferedReturnType<T> = {\n /** a function to resolve the promise */\n resolve: ResolveType<T>;\n\n /** a function to reject the promise */\n reject: RejectType;\n\n /** the status of the promise */\n status: StatusType;\n\n /** a promise to express the above */\n promise: Promise<T>;\n};\n\n/** create a deferred promise */\nfunction deferred<T>(): DefferedReturnType<T> {\n /** the default resolve handler is a noop */\n let resolve: ResolveType<T> = () => undefined;\n\n /** the default reject handler is a noop */\n let reject: RejectType = () => undefined;\n\n let status: StatusType = \"pending\";\n\n const promise = new Promise<T>((res, rej) => {\n resolve = (a?: T) => {\n status = \"success\";\n const resolveFunc = res as ResolveType<T>;\n resolveFunc(a);\n };\n\n reject = (error?: Error) => {\n status = \"failure\";\n rej(error);\n };\n });\n\n return {\n resolve,\n status,\n reject,\n promise,\n };\n}\n\nconst NOT_CALLED = Symbol(\"Subscribe -- Empty Value\");\n/**\n * A pub-sub module that works across the React bridge\n */\nexport class Subscribe<T> {\n private callbacks: Map<SubscribeID, (val: T | undefined) => void> = new Map();\n private deferredResult = deferred<T>();\n private lastValue: T | typeof NOT_CALLED = NOT_CALLED;\n private resetDeferred: DefferedReturnType<void> | null = null;\n constructor() {\n this.publish = this.publish.bind(this);\n this.add = this.add.bind(this);\n this.remove = this.remove.bind(this);\n }\n\n /**\n * Trigger the subscriptions using the provided value\n * if there is a reset in progress, wait for it before publishing a new value.\n */\n async publish(val: T): Promise<void> {\n await this.resetDeferred?.promise;\n this.lastValue = val;\n this.deferredResult.resolve(val);\n this.callbacks.forEach((c) => c(val));\n }\n\n /**\n * Subscribe to updates\n */\n add(\n callback: (arg: T | undefined) => void,\n options?: {\n /** Use the last updated value for this subscription to immediately trigger the onSet callback */\n initializeWithPreviousValue?: boolean;\n },\n ): SubscribeID {\n const id = this.callbacks.size;\n this.callbacks.set(id, callback);\n\n if (\n this.lastValue !== NOT_CALLED &&\n options?.initializeWithPreviousValue === true\n ) {\n callback(this.lastValue);\n }\n\n return id;\n }\n\n /**\n * Remove any updates from the given listener\n */\n remove(id: SubscribeID): void {\n this.callbacks.delete(id);\n }\n\n /**\n * Reset the state of the listener\n * Passing in a promise will defer resetting the view until the promise is resolved\n */\n async reset(promise?: Promise<void>): Promise<void> {\n if (promise) {\n this.resetDeferred = deferred<void>();\n await promise;\n }\n\n if (this.lastValue !== NOT_CALLED) {\n this.deferredResult = deferred();\n }\n\n this.lastValue = NOT_CALLED;\n this.callbacks.forEach((c) => c(undefined));\n\n this.resetDeferred?.resolve();\n this.resetDeferred = null;\n }\n\n /**\n * _Throws_ a promise if the value is still pending\n * Otherwise returns it\n */\n suspend(): T {\n if (this.lastValue === NOT_CALLED) {\n throw this.deferredResult.promise;\n }\n\n return this.lastValue;\n }\n\n /** Get the current value of the subscription */\n get(): T | undefined {\n if (this.lastValue === NOT_CALLED) {\n return undefined;\n }\n\n return this.lastValue;\n }\n}\n\nexport interface SubscribedStateHookOptions {\n /** if the state should trigger suspense when waiting to resolve */\n suspend?: boolean;\n}\n\n/** Subscribe to a state change event in a react component */\nexport function useSubscribedState<T>(subscriber: Subscribe<T>): T | undefined {\n const subscription = React.useMemo(() => {\n function subscribe(callback: (val?: T) => void) {\n const id = subscriber.add(\n (resp) => {\n callback(resp);\n },\n {\n initializeWithPreviousValue: true,\n },\n );\n\n return () => {\n if (subscriber) {\n subscriber.remove(id);\n }\n };\n }\n\n return subscribe;\n }, [subscriber]);\n\n function getSnapshot() {\n try {\n return subscriber.get();\n } catch (err) {\n return undefined;\n }\n }\n\n const state = useSyncExternalStore(\n subscription,\n getSnapshot,\n () => undefined,\n );\n\n return state;\n}\n"],"mappings":"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","names":["React"]}
1
+ {"version":3,"sources":["../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/react/subscribe/src/index.tsx"],"sourcesContent":["import React, { useCallback } from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim\";\n\nexport type SubscribeID = number;\n\ntype ResolveType<T> = (arg?: T) => void;\ntype RejectType = (error?: Error) => void;\ntype StatusType = \"success\" | \"failure\" | \"pending\";\ntype DefferedReturnType<T> = {\n /** a function to resolve the promise */\n resolve: ResolveType<T>;\n\n /** a function to reject the promise */\n reject: RejectType;\n\n /** the status of the promise */\n status: StatusType;\n\n /** a promise to express the above */\n promise: Promise<T>;\n};\n\n/** create a deferred promise */\nfunction deferred<T>(): DefferedReturnType<T> {\n /** the default resolve handler is a noop */\n let resolve: ResolveType<T> = () => undefined;\n\n /** the default reject handler is a noop */\n let reject: RejectType = () => undefined;\n\n let status: StatusType = \"pending\";\n\n const promise = new Promise<T>((res, rej) => {\n resolve = (a?: T) => {\n status = \"success\";\n const resolveFunc = res as ResolveType<T>;\n resolveFunc(a);\n };\n\n reject = (error?: Error) => {\n status = \"failure\";\n rej(error);\n };\n });\n\n return {\n resolve,\n status,\n reject,\n promise,\n };\n}\n\nconst NOT_CALLED = Symbol(\"Subscribe -- Empty Value\");\n/**\n * A pub-sub module that works across the React bridge\n */\nexport class Subscribe<T> {\n private callbacks: Map<SubscribeID, (val: T | undefined) => void> = new Map();\n private deferredResult = deferred<T>();\n private lastValue: T | typeof NOT_CALLED = NOT_CALLED;\n private resetDeferred: DefferedReturnType<void> | null = null;\n constructor() {\n this.publish = this.publish.bind(this);\n this.add = this.add.bind(this);\n this.remove = this.remove.bind(this);\n }\n\n /**\n * Trigger the subscriptions using the provided value\n * if there is a reset in progress, wait for it before publishing a new value.\n */\n async publish(val: T): Promise<void> {\n await this.resetDeferred?.promise;\n this.lastValue = val;\n this.deferredResult.resolve(val);\n this.callbacks.forEach((c) => c(val));\n }\n\n private lastId: number = 0;\n /**\n * Subscribe to updates\n */\n add(\n callback: (arg: T | undefined) => void,\n options?: {\n /** Use the last updated value for this subscription to immediately trigger the onSet callback */\n initializeWithPreviousValue?: boolean;\n },\n ): SubscribeID {\n const id = this.lastId++;\n this.callbacks.set(id, callback);\n\n if (\n this.lastValue !== NOT_CALLED &&\n options?.initializeWithPreviousValue === true\n ) {\n callback(this.lastValue);\n }\n\n return id;\n }\n\n /**\n * Remove any updates from the given listener\n */\n remove(id: SubscribeID): void {\n this.callbacks.delete(id);\n }\n\n /**\n * Reset the state of the listener\n * Passing in a promise will defer resetting the view until the promise is resolved\n */\n async reset(promise?: Promise<void>): Promise<void> {\n if (promise) {\n this.resetDeferred = deferred<void>();\n await promise;\n }\n\n if (this.lastValue !== NOT_CALLED) {\n this.deferredResult = deferred();\n }\n\n this.lastValue = NOT_CALLED;\n this.callbacks.forEach((c) => c(undefined));\n\n this.resetDeferred?.resolve();\n this.resetDeferred = null;\n }\n\n /**\n * _Throws_ a promise if the value is still pending\n * Otherwise returns it\n */\n suspend(): T {\n if (this.lastValue === NOT_CALLED) {\n throw this.deferredResult.promise;\n }\n\n return this.lastValue;\n }\n\n /** Get the current value of the subscription */\n get(): T | undefined {\n if (this.lastValue === NOT_CALLED) {\n return undefined;\n }\n\n return this.lastValue;\n }\n}\n\nexport interface SubscribedStateHookOptions {\n /** if the state should trigger suspense when waiting to resolve */\n suspend?: boolean;\n}\n\n/** Subscribe to a state change event in a react component */\nexport function useSubscribedState<T>(subscriber: Subscribe<T>): T | undefined {\n const subscription = React.useMemo(() => {\n function subscribe(callback: (val?: T) => void) {\n const id = subscriber.add(\n (resp) => {\n callback(resp);\n },\n {\n initializeWithPreviousValue: true,\n },\n );\n\n return () => {\n if (subscriber) {\n subscriber.remove(id);\n }\n };\n }\n\n return subscribe;\n }, [subscriber]);\n\n function getSnapshot() {\n try {\n return subscriber.get();\n } catch (err) {\n return undefined;\n }\n }\n\n const state = useSyncExternalStore(\n subscription,\n getSnapshot,\n () => undefined,\n );\n\n return state;\n}\n\ntype SubOptions = {\n initializeWithPreviousValue?: boolean;\n};\n\ntype UnsubFunction = (id: SubscribeID) => void;\n\ntype SubFunction<T> = (\n callback: (arg: T | undefined) => void,\n options?: SubOptions,\n) => SubscribeID;\n\nexport type ReactSubscriber<T> = {\n subscribe: SubFunction<T>;\n unsubscribe: UnsubFunction;\n};\n\n/** Hook to manage subscriptions within a react component. Any subscriptions setup using the subscribe callback will be unsubscribed on unmount. */\nexport function useSubscriber<T>(subscriber: Subscribe<T>): ReactSubscriber<T> {\n const subscriptions = React.useMemo<Set<SubscribeID>>(\n () => new Set<SubscribeID>(),\n [],\n );\n React.useEffect(() => {\n return () => {\n for (const subId of subscriptions.values()) {\n subscriber.remove(subId);\n }\n };\n }, [subscriptions]);\n\n const unsubscribe = useCallback<UnsubFunction>((id) => {\n subscriptions.delete(id);\n subscriber.remove(id);\n }, []);\n\n const subscribe = useCallback<SubFunction<T>>((callback, options) => {\n const id = subscriber.add(callback, options);\n subscriptions.add(id);\n return id;\n }, []);\n\n return { subscribe, unsubscribe };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAmC;AACnC,kBAAqC;AAsBrC,SAAS,WAAqC;AAE5C,MAAI,UAA0B,MAAM;AAGpC,MAAI,SAAqB,MAAM;AAE/B,MAAI,SAAqB;AAEzB,QAAM,UAAU,IAAI,QAAW,CAAC,KAAK,QAAQ;AAC3C,cAAU,CAAC,MAAU;AACnB,eAAS;AACT,YAAM,cAAc;AACpB,kBAAY,CAAC;AAAA,IACf;AAEA,aAAS,CAAC,UAAkB;AAC1B,eAAS;AACT,UAAI,KAAK;AAAA,IACX;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,IAAM,aAAa,OAAO,0BAA0B;AAI7C,IAAM,YAAN,MAAmB;AAAA,EAKxB,cAAc;AAJd,SAAQ,YAA4D,oBAAI,IAAI;AAC5E,SAAQ,iBAAiB,SAAY;AACrC,SAAQ,YAAmC;AAC3C,SAAQ,gBAAiD;AAkBzD,SAAQ,SAAiB;AAhBvB,SAAK,UAAU,KAAK,QAAQ,KAAK,IAAI;AACrC,SAAK,MAAM,KAAK,IAAI,KAAK,IAAI;AAC7B,SAAK,SAAS,KAAK,OAAO,KAAK,IAAI;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAQ,KAAuB;AACnC,UAAM,KAAK,eAAe;AAC1B,SAAK,YAAY;AACjB,SAAK,eAAe,QAAQ,GAAG;AAC/B,SAAK,UAAU,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAMA,IACE,UACA,SAIa;AACb,UAAM,KAAK,KAAK;AAChB,SAAK,UAAU,IAAI,IAAI,QAAQ;AAE/B,QACE,KAAK,cAAc,cACnB,SAAS,gCAAgC,MACzC;AACA,eAAS,KAAK,SAAS;AAAA,IACzB;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,IAAuB;AAC5B,SAAK,UAAU,OAAO,EAAE;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAM,SAAwC;AAClD,QAAI,SAAS;AACX,WAAK,gBAAgB,SAAe;AACpC,YAAM;AAAA,IACR;AAEA,QAAI,KAAK,cAAc,YAAY;AACjC,WAAK,iBAAiB,SAAS;AAAA,IACjC;AAEA,SAAK,YAAY;AACjB,SAAK,UAAU,QAAQ,CAAC,MAAM,EAAE,MAAS,CAAC;AAE1C,SAAK,eAAe,QAAQ;AAC5B,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAa;AACX,QAAI,KAAK,cAAc,YAAY;AACjC,YAAM,KAAK,eAAe;AAAA,IAC5B;AAEA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAqB;AACnB,QAAI,KAAK,cAAc,YAAY;AACjC,aAAO;AAAA,IACT;AAEA,WAAO,KAAK;AAAA,EACd;AACF;AAQO,SAAS,mBAAsB,YAAyC;AAC7E,QAAM,eAAe,aAAAA,QAAM,QAAQ,MAAM;AACvC,aAAS,UAAU,UAA6B;AAC9C,YAAM,KAAK,WAAW;AAAA,QACpB,CAAC,SAAS;AACR,mBAAS,IAAI;AAAA,QACf;AAAA,QACA;AAAA,UACE,6BAA6B;AAAA,QAC/B;AAAA,MACF;AAEA,aAAO,MAAM;AACX,YAAI,YAAY;AACd,qBAAW,OAAO,EAAE;AAAA,QACtB;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT,GAAG,CAAC,UAAU,CAAC;AAEf,WAAS,cAAc;AACrB,QAAI;AACF,aAAO,WAAW,IAAI;AAAA,IACxB,SAAS,KAAK;AACZ,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,YAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,MAAM;AAAA,EACR;AAEA,SAAO;AACT;AAmBO,SAAS,cAAiB,YAA8C;AAC7E,QAAM,gBAAgB,aAAAA,QAAM;AAAA,IAC1B,MAAM,oBAAI,IAAiB;AAAA,IAC3B,CAAC;AAAA,EACH;AACA,eAAAA,QAAM,UAAU,MAAM;AACpB,WAAO,MAAM;AACX,iBAAW,SAAS,cAAc,OAAO,GAAG;AAC1C,mBAAW,OAAO,KAAK;AAAA,MACzB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,aAAa,CAAC;AAElB,QAAM,kBAAc,0BAA2B,CAAC,OAAO;AACrD,kBAAc,OAAO,EAAE;AACvB,eAAW,OAAO,EAAE;AAAA,EACtB,GAAG,CAAC,CAAC;AAEL,QAAM,gBAAY,0BAA4B,CAAC,UAAU,YAAY;AACnE,UAAM,KAAK,WAAW,IAAI,UAAU,OAAO;AAC3C,kBAAc,IAAI,EAAE;AACpB,WAAO;AAAA,EACT,GAAG,CAAC,CAAC;AAEL,SAAO,EAAE,WAAW,YAAY;AAClC;","names":["React"]}
@@ -1,5 +1,5 @@
1
1
  // ../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/react/subscribe/src/index.tsx
2
- import React from "react";
2
+ import React, { useCallback } from "react";
3
3
  import { useSyncExternalStore } from "use-sync-external-store/shim";
4
4
  function deferred() {
5
5
  let resolve = () => void 0;
@@ -30,6 +30,7 @@ var Subscribe = class {
30
30
  this.deferredResult = deferred();
31
31
  this.lastValue = NOT_CALLED;
32
32
  this.resetDeferred = null;
33
+ this.lastId = 0;
33
34
  this.publish = this.publish.bind(this);
34
35
  this.add = this.add.bind(this);
35
36
  this.remove = this.remove.bind(this);
@@ -48,7 +49,7 @@ var Subscribe = class {
48
49
  * Subscribe to updates
49
50
  */
50
51
  add(callback, options) {
51
- const id = this.callbacks.size;
52
+ const id = this.lastId++;
52
53
  this.callbacks.set(id, callback);
53
54
  if (this.lastValue !== NOT_CALLED && options?.initializeWithPreviousValue === true) {
54
55
  callback(this.lastValue);
@@ -129,8 +130,32 @@ function useSubscribedState(subscriber) {
129
130
  );
130
131
  return state;
131
132
  }
133
+ function useSubscriber(subscriber) {
134
+ const subscriptions = React.useMemo(
135
+ () => /* @__PURE__ */ new Set(),
136
+ []
137
+ );
138
+ React.useEffect(() => {
139
+ return () => {
140
+ for (const subId of subscriptions.values()) {
141
+ subscriber.remove(subId);
142
+ }
143
+ };
144
+ }, [subscriptions]);
145
+ const unsubscribe = useCallback((id) => {
146
+ subscriptions.delete(id);
147
+ subscriber.remove(id);
148
+ }, []);
149
+ const subscribe = useCallback((callback, options) => {
150
+ const id = subscriber.add(callback, options);
151
+ subscriptions.add(id);
152
+ return id;
153
+ }, []);
154
+ return { subscribe, unsubscribe };
155
+ }
132
156
  export {
133
157
  Subscribe,
134
- useSubscribedState
158
+ useSubscribedState,
159
+ useSubscriber
135
160
  };
136
161
  //# sourceMappingURL=index.mjs.map
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // ../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/react/subscribe/src/index.tsx
2
- import React from "react";
2
+ import React, { useCallback } from "react";
3
3
  import { useSyncExternalStore } from "use-sync-external-store/shim";
4
4
  function deferred() {
5
5
  let resolve = () => void 0;
@@ -30,6 +30,7 @@ var Subscribe = class {
30
30
  this.deferredResult = deferred();
31
31
  this.lastValue = NOT_CALLED;
32
32
  this.resetDeferred = null;
33
+ this.lastId = 0;
33
34
  this.publish = this.publish.bind(this);
34
35
  this.add = this.add.bind(this);
35
36
  this.remove = this.remove.bind(this);
@@ -48,7 +49,7 @@ var Subscribe = class {
48
49
  * Subscribe to updates
49
50
  */
50
51
  add(callback, options) {
51
- const id = this.callbacks.size;
52
+ const id = this.lastId++;
52
53
  this.callbacks.set(id, callback);
53
54
  if (this.lastValue !== NOT_CALLED && options?.initializeWithPreviousValue === true) {
54
55
  callback(this.lastValue);
@@ -129,8 +130,32 @@ function useSubscribedState(subscriber) {
129
130
  );
130
131
  return state;
131
132
  }
133
+ function useSubscriber(subscriber) {
134
+ const subscriptions = React.useMemo(
135
+ () => /* @__PURE__ */ new Set(),
136
+ []
137
+ );
138
+ React.useEffect(() => {
139
+ return () => {
140
+ for (const subId of subscriptions.values()) {
141
+ subscriber.remove(subId);
142
+ }
143
+ };
144
+ }, [subscriptions]);
145
+ const unsubscribe = useCallback((id) => {
146
+ subscriptions.delete(id);
147
+ subscriber.remove(id);
148
+ }, []);
149
+ const subscribe = useCallback((callback, options) => {
150
+ const id = subscriber.add(callback, options);
151
+ subscriptions.add(id);
152
+ return id;
153
+ }, []);
154
+ return { subscribe, unsubscribe };
155
+ }
132
156
  export {
133
157
  Subscribe,
134
- useSubscribedState
158
+ useSubscribedState,
159
+ useSubscriber
135
160
  };
136
161
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/react/subscribe/src/index.tsx"],"sourcesContent":["import React from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim\";\n\nexport type SubscribeID = number;\n\ntype ResolveType<T> = (arg?: T) => void;\ntype RejectType = (error?: Error) => void;\ntype StatusType = \"success\" | \"failure\" | \"pending\";\ntype DefferedReturnType<T> = {\n /** a function to resolve the promise */\n resolve: ResolveType<T>;\n\n /** a function to reject the promise */\n reject: RejectType;\n\n /** the status of the promise */\n status: StatusType;\n\n /** a promise to express the above */\n promise: Promise<T>;\n};\n\n/** create a deferred promise */\nfunction deferred<T>(): DefferedReturnType<T> {\n /** the default resolve handler is a noop */\n let resolve: ResolveType<T> = () => undefined;\n\n /** the default reject handler is a noop */\n let reject: RejectType = () => undefined;\n\n let status: StatusType = \"pending\";\n\n const promise = new Promise<T>((res, rej) => {\n resolve = (a?: T) => {\n status = \"success\";\n const resolveFunc = res as ResolveType<T>;\n resolveFunc(a);\n };\n\n reject = (error?: Error) => {\n status = \"failure\";\n rej(error);\n };\n });\n\n return {\n resolve,\n status,\n reject,\n promise,\n };\n}\n\nconst NOT_CALLED = Symbol(\"Subscribe -- Empty Value\");\n/**\n * A pub-sub module that works across the React bridge\n */\nexport class Subscribe<T> {\n private callbacks: Map<SubscribeID, (val: T | undefined) => void> = new Map();\n private deferredResult = deferred<T>();\n private lastValue: T | typeof NOT_CALLED = NOT_CALLED;\n private resetDeferred: DefferedReturnType<void> | null = null;\n constructor() {\n this.publish = this.publish.bind(this);\n this.add = this.add.bind(this);\n this.remove = this.remove.bind(this);\n }\n\n /**\n * Trigger the subscriptions using the provided value\n * if there is a reset in progress, wait for it before publishing a new value.\n */\n async publish(val: T): Promise<void> {\n await this.resetDeferred?.promise;\n this.lastValue = val;\n this.deferredResult.resolve(val);\n this.callbacks.forEach((c) => c(val));\n }\n\n /**\n * Subscribe to updates\n */\n add(\n callback: (arg: T | undefined) => void,\n options?: {\n /** Use the last updated value for this subscription to immediately trigger the onSet callback */\n initializeWithPreviousValue?: boolean;\n },\n ): SubscribeID {\n const id = this.callbacks.size;\n this.callbacks.set(id, callback);\n\n if (\n this.lastValue !== NOT_CALLED &&\n options?.initializeWithPreviousValue === true\n ) {\n callback(this.lastValue);\n }\n\n return id;\n }\n\n /**\n * Remove any updates from the given listener\n */\n remove(id: SubscribeID): void {\n this.callbacks.delete(id);\n }\n\n /**\n * Reset the state of the listener\n * Passing in a promise will defer resetting the view until the promise is resolved\n */\n async reset(promise?: Promise<void>): Promise<void> {\n if (promise) {\n this.resetDeferred = deferred<void>();\n await promise;\n }\n\n if (this.lastValue !== NOT_CALLED) {\n this.deferredResult = deferred();\n }\n\n this.lastValue = NOT_CALLED;\n this.callbacks.forEach((c) => c(undefined));\n\n this.resetDeferred?.resolve();\n this.resetDeferred = null;\n }\n\n /**\n * _Throws_ a promise if the value is still pending\n * Otherwise returns it\n */\n suspend(): T {\n if (this.lastValue === NOT_CALLED) {\n throw this.deferredResult.promise;\n }\n\n return this.lastValue;\n }\n\n /** Get the current value of the subscription */\n get(): T | undefined {\n if (this.lastValue === NOT_CALLED) {\n return undefined;\n }\n\n return this.lastValue;\n }\n}\n\nexport interface SubscribedStateHookOptions {\n /** if the state should trigger suspense when waiting to resolve */\n suspend?: boolean;\n}\n\n/** Subscribe to a state change event in a react component */\nexport function useSubscribedState<T>(subscriber: Subscribe<T>): T | undefined {\n const subscription = React.useMemo(() => {\n function subscribe(callback: (val?: T) => void) {\n const id = subscriber.add(\n (resp) => {\n callback(resp);\n },\n {\n initializeWithPreviousValue: true,\n },\n );\n\n return () => {\n if (subscriber) {\n subscriber.remove(id);\n }\n };\n }\n\n return subscribe;\n }, [subscriber]);\n\n function getSnapshot() {\n try {\n return subscriber.get();\n } catch (err) {\n return undefined;\n }\n }\n\n const state = useSyncExternalStore(\n subscription,\n getSnapshot,\n () => undefined,\n );\n\n return state;\n}\n"],"mappings":";AAAA,OAAO,WAAW;AAClB,SAAS,4BAA4B;AAsBrC,SAAS,WAAqC;AAE5C,MAAI,UAA0B,MAAM;AAGpC,MAAI,SAAqB,MAAM;AAE/B,MAAI,SAAqB;AAEzB,QAAM,UAAU,IAAI,QAAW,CAAC,KAAK,QAAQ;AAC3C,cAAU,CAAC,MAAU;AACnB,eAAS;AACT,YAAM,cAAc;AACpB,kBAAY,CAAC;AAAA,IACf;AAEA,aAAS,CAAC,UAAkB;AAC1B,eAAS;AACT,UAAI,KAAK;AAAA,IACX;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,IAAM,aAAa,OAAO,0BAA0B;AAI7C,IAAM,YAAN,MAAmB;AAAA,EAKxB,cAAc;AAJd,SAAQ,YAA4D,oBAAI,IAAI;AAC5E,SAAQ,iBAAiB,SAAY;AACrC,SAAQ,YAAmC;AAC3C,SAAQ,gBAAiD;AAEvD,SAAK,UAAU,KAAK,QAAQ,KAAK,IAAI;AACrC,SAAK,MAAM,KAAK,IAAI,KAAK,IAAI;AAC7B,SAAK,SAAS,KAAK,OAAO,KAAK,IAAI;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAQ,KAAuB;AACnC,UAAM,KAAK,eAAe;AAC1B,SAAK,YAAY;AACjB,SAAK,eAAe,QAAQ,GAAG;AAC/B,SAAK,UAAU,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,IACE,UACA,SAIa;AACb,UAAM,KAAK,KAAK,UAAU;AAC1B,SAAK,UAAU,IAAI,IAAI,QAAQ;AAE/B,QACE,KAAK,cAAc,cACnB,SAAS,gCAAgC,MACzC;AACA,eAAS,KAAK,SAAS;AAAA,IACzB;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,IAAuB;AAC5B,SAAK,UAAU,OAAO,EAAE;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAM,SAAwC;AAClD,QAAI,SAAS;AACX,WAAK,gBAAgB,SAAe;AACpC,YAAM;AAAA,IACR;AAEA,QAAI,KAAK,cAAc,YAAY;AACjC,WAAK,iBAAiB,SAAS;AAAA,IACjC;AAEA,SAAK,YAAY;AACjB,SAAK,UAAU,QAAQ,CAAC,MAAM,EAAE,MAAS,CAAC;AAE1C,SAAK,eAAe,QAAQ;AAC5B,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAa;AACX,QAAI,KAAK,cAAc,YAAY;AACjC,YAAM,KAAK,eAAe;AAAA,IAC5B;AAEA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAqB;AACnB,QAAI,KAAK,cAAc,YAAY;AACjC,aAAO;AAAA,IACT;AAEA,WAAO,KAAK;AAAA,EACd;AACF;AAQO,SAAS,mBAAsB,YAAyC;AAC7E,QAAM,eAAe,MAAM,QAAQ,MAAM;AACvC,aAAS,UAAU,UAA6B;AAC9C,YAAM,KAAK,WAAW;AAAA,QACpB,CAAC,SAAS;AACR,mBAAS,IAAI;AAAA,QACf;AAAA,QACA;AAAA,UACE,6BAA6B;AAAA,QAC/B;AAAA,MACF;AAEA,aAAO,MAAM;AACX,YAAI,YAAY;AACd,qBAAW,OAAO,EAAE;AAAA,QACtB;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT,GAAG,CAAC,UAAU,CAAC;AAEf,WAAS,cAAc;AACrB,QAAI;AACF,aAAO,WAAW,IAAI;AAAA,IACxB,SAAS,KAAK;AACZ,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,MAAM;AAAA,EACR;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/react/subscribe/src/index.tsx"],"sourcesContent":["import React, { useCallback } from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim\";\n\nexport type SubscribeID = number;\n\ntype ResolveType<T> = (arg?: T) => void;\ntype RejectType = (error?: Error) => void;\ntype StatusType = \"success\" | \"failure\" | \"pending\";\ntype DefferedReturnType<T> = {\n /** a function to resolve the promise */\n resolve: ResolveType<T>;\n\n /** a function to reject the promise */\n reject: RejectType;\n\n /** the status of the promise */\n status: StatusType;\n\n /** a promise to express the above */\n promise: Promise<T>;\n};\n\n/** create a deferred promise */\nfunction deferred<T>(): DefferedReturnType<T> {\n /** the default resolve handler is a noop */\n let resolve: ResolveType<T> = () => undefined;\n\n /** the default reject handler is a noop */\n let reject: RejectType = () => undefined;\n\n let status: StatusType = \"pending\";\n\n const promise = new Promise<T>((res, rej) => {\n resolve = (a?: T) => {\n status = \"success\";\n const resolveFunc = res as ResolveType<T>;\n resolveFunc(a);\n };\n\n reject = (error?: Error) => {\n status = \"failure\";\n rej(error);\n };\n });\n\n return {\n resolve,\n status,\n reject,\n promise,\n };\n}\n\nconst NOT_CALLED = Symbol(\"Subscribe -- Empty Value\");\n/**\n * A pub-sub module that works across the React bridge\n */\nexport class Subscribe<T> {\n private callbacks: Map<SubscribeID, (val: T | undefined) => void> = new Map();\n private deferredResult = deferred<T>();\n private lastValue: T | typeof NOT_CALLED = NOT_CALLED;\n private resetDeferred: DefferedReturnType<void> | null = null;\n constructor() {\n this.publish = this.publish.bind(this);\n this.add = this.add.bind(this);\n this.remove = this.remove.bind(this);\n }\n\n /**\n * Trigger the subscriptions using the provided value\n * if there is a reset in progress, wait for it before publishing a new value.\n */\n async publish(val: T): Promise<void> {\n await this.resetDeferred?.promise;\n this.lastValue = val;\n this.deferredResult.resolve(val);\n this.callbacks.forEach((c) => c(val));\n }\n\n private lastId: number = 0;\n /**\n * Subscribe to updates\n */\n add(\n callback: (arg: T | undefined) => void,\n options?: {\n /** Use the last updated value for this subscription to immediately trigger the onSet callback */\n initializeWithPreviousValue?: boolean;\n },\n ): SubscribeID {\n const id = this.lastId++;\n this.callbacks.set(id, callback);\n\n if (\n this.lastValue !== NOT_CALLED &&\n options?.initializeWithPreviousValue === true\n ) {\n callback(this.lastValue);\n }\n\n return id;\n }\n\n /**\n * Remove any updates from the given listener\n */\n remove(id: SubscribeID): void {\n this.callbacks.delete(id);\n }\n\n /**\n * Reset the state of the listener\n * Passing in a promise will defer resetting the view until the promise is resolved\n */\n async reset(promise?: Promise<void>): Promise<void> {\n if (promise) {\n this.resetDeferred = deferred<void>();\n await promise;\n }\n\n if (this.lastValue !== NOT_CALLED) {\n this.deferredResult = deferred();\n }\n\n this.lastValue = NOT_CALLED;\n this.callbacks.forEach((c) => c(undefined));\n\n this.resetDeferred?.resolve();\n this.resetDeferred = null;\n }\n\n /**\n * _Throws_ a promise if the value is still pending\n * Otherwise returns it\n */\n suspend(): T {\n if (this.lastValue === NOT_CALLED) {\n throw this.deferredResult.promise;\n }\n\n return this.lastValue;\n }\n\n /** Get the current value of the subscription */\n get(): T | undefined {\n if (this.lastValue === NOT_CALLED) {\n return undefined;\n }\n\n return this.lastValue;\n }\n}\n\nexport interface SubscribedStateHookOptions {\n /** if the state should trigger suspense when waiting to resolve */\n suspend?: boolean;\n}\n\n/** Subscribe to a state change event in a react component */\nexport function useSubscribedState<T>(subscriber: Subscribe<T>): T | undefined {\n const subscription = React.useMemo(() => {\n function subscribe(callback: (val?: T) => void) {\n const id = subscriber.add(\n (resp) => {\n callback(resp);\n },\n {\n initializeWithPreviousValue: true,\n },\n );\n\n return () => {\n if (subscriber) {\n subscriber.remove(id);\n }\n };\n }\n\n return subscribe;\n }, [subscriber]);\n\n function getSnapshot() {\n try {\n return subscriber.get();\n } catch (err) {\n return undefined;\n }\n }\n\n const state = useSyncExternalStore(\n subscription,\n getSnapshot,\n () => undefined,\n );\n\n return state;\n}\n\ntype SubOptions = {\n initializeWithPreviousValue?: boolean;\n};\n\ntype UnsubFunction = (id: SubscribeID) => void;\n\ntype SubFunction<T> = (\n callback: (arg: T | undefined) => void,\n options?: SubOptions,\n) => SubscribeID;\n\nexport type ReactSubscriber<T> = {\n subscribe: SubFunction<T>;\n unsubscribe: UnsubFunction;\n};\n\n/** Hook to manage subscriptions within a react component. Any subscriptions setup using the subscribe callback will be unsubscribed on unmount. */\nexport function useSubscriber<T>(subscriber: Subscribe<T>): ReactSubscriber<T> {\n const subscriptions = React.useMemo<Set<SubscribeID>>(\n () => new Set<SubscribeID>(),\n [],\n );\n React.useEffect(() => {\n return () => {\n for (const subId of subscriptions.values()) {\n subscriber.remove(subId);\n }\n };\n }, [subscriptions]);\n\n const unsubscribe = useCallback<UnsubFunction>((id) => {\n subscriptions.delete(id);\n subscriber.remove(id);\n }, []);\n\n const subscribe = useCallback<SubFunction<T>>((callback, options) => {\n const id = subscriber.add(callback, options);\n subscriptions.add(id);\n return id;\n }, []);\n\n return { subscribe, unsubscribe };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "types"
7
7
  ],
8
8
  "name": "@player-ui/react-subscribe",
9
- "version": "0.16.0--canary.866.36538",
9
+ "version": "1.0.0--canary.865.36694",
10
10
  "main": "dist/cjs/index.cjs",
11
11
  "module": "dist/index.legacy-esm.js",
12
12
  "types": "types/index.d.ts",
@@ -1,6 +1,6 @@
1
1
  import { test, vitest, expect, describe, vi } from "vitest";
2
2
  import { renderHook } from "@testing-library/react";
3
- import { Subscribe, useSubscribedState } from ".";
3
+ import { Subscribe, useSubscribedState, useSubscriber } from ".";
4
4
 
5
5
  test("Passes events to subscriptions", async () => {
6
6
  const stateSub = new Subscribe<{
@@ -81,3 +81,59 @@ describe("useSubscribedState", () => {
81
81
  expect(removeSpy).toHaveBeenCalledTimes(1);
82
82
  });
83
83
  });
84
+
85
+ describe("useSubscriber", () => {
86
+ test("should expose subscribe and unsubscribe functions and unsubscribe all on unmount", async () => {
87
+ const stateSub = new Subscribe<{
88
+ value: boolean;
89
+ }>();
90
+ await stateSub.publish({ value: true });
91
+
92
+ const { result, unmount } = renderHook(() => useSubscriber(stateSub));
93
+
94
+ const firstSubFunction = vi.fn();
95
+ const secondSubFunction = vi.fn();
96
+
97
+ // 1. Subscribe and check options are passed as expected.
98
+ const firstSubId = result.current.subscribe(firstSubFunction, {
99
+ initializeWithPreviousValue: true,
100
+ });
101
+ result.current.subscribe(secondSubFunction, {
102
+ initializeWithPreviousValue: false,
103
+ });
104
+
105
+ expect(firstSubFunction).toHaveBeenCalledOnce();
106
+ expect(firstSubFunction).toHaveBeenCalledWith({ value: true });
107
+ expect(secondSubFunction).not.toHaveBeenCalled();
108
+
109
+ // Clear for future tests
110
+ firstSubFunction.mockClear();
111
+
112
+ // 2. Check both subscriptions work
113
+ await stateSub.publish({ value: false });
114
+ expect(firstSubFunction).toHaveBeenCalledOnce();
115
+ expect(firstSubFunction).toHaveBeenCalledWith({ value: false });
116
+ expect(secondSubFunction).toHaveBeenCalledOnce();
117
+ expect(secondSubFunction).toHaveBeenCalledWith({ value: false });
118
+
119
+ // Clear for future tests
120
+ firstSubFunction.mockClear();
121
+ secondSubFunction.mockClear();
122
+
123
+ // 3. Check unsubscribe works
124
+ result.current.unsubscribe(firstSubId);
125
+ await stateSub.publish({ value: true });
126
+ expect(firstSubFunction).not.toHaveBeenCalled();
127
+ expect(secondSubFunction).toHaveBeenCalledOnce();
128
+ expect(secondSubFunction).toHaveBeenCalledWith({ value: true });
129
+
130
+ // Clear for future tests
131
+ secondSubFunction.mockClear();
132
+
133
+ // 4. Check unsub on unmount
134
+ unmount();
135
+ stateSub.publish({ value: false });
136
+ expect(firstSubFunction).not.toHaveBeenCalled();
137
+ expect(secondSubFunction).not.toHaveBeenCalled();
138
+ });
139
+ });
package/src/index.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useCallback } from "react";
2
2
  import { useSyncExternalStore } from "use-sync-external-store/shim";
3
3
 
4
4
  export type SubscribeID = number;
@@ -77,6 +77,7 @@ export class Subscribe<T> {
77
77
  this.callbacks.forEach((c) => c(val));
78
78
  }
79
79
 
80
+ private lastId: number = 0;
80
81
  /**
81
82
  * Subscribe to updates
82
83
  */
@@ -87,7 +88,7 @@ export class Subscribe<T> {
87
88
  initializeWithPreviousValue?: boolean;
88
89
  },
89
90
  ): SubscribeID {
90
- const id = this.callbacks.size;
91
+ const id = this.lastId++;
91
92
  this.callbacks.set(id, callback);
92
93
 
93
94
  if (
@@ -194,3 +195,47 @@ export function useSubscribedState<T>(subscriber: Subscribe<T>): T | undefined {
194
195
 
195
196
  return state;
196
197
  }
198
+
199
+ type SubOptions = {
200
+ initializeWithPreviousValue?: boolean;
201
+ };
202
+
203
+ type UnsubFunction = (id: SubscribeID) => void;
204
+
205
+ type SubFunction<T> = (
206
+ callback: (arg: T | undefined) => void,
207
+ options?: SubOptions,
208
+ ) => SubscribeID;
209
+
210
+ export type ReactSubscriber<T> = {
211
+ subscribe: SubFunction<T>;
212
+ unsubscribe: UnsubFunction;
213
+ };
214
+
215
+ /** Hook to manage subscriptions within a react component. Any subscriptions setup using the subscribe callback will be unsubscribed on unmount. */
216
+ export function useSubscriber<T>(subscriber: Subscribe<T>): ReactSubscriber<T> {
217
+ const subscriptions = React.useMemo<Set<SubscribeID>>(
218
+ () => new Set<SubscribeID>(),
219
+ [],
220
+ );
221
+ React.useEffect(() => {
222
+ return () => {
223
+ for (const subId of subscriptions.values()) {
224
+ subscriber.remove(subId);
225
+ }
226
+ };
227
+ }, [subscriptions]);
228
+
229
+ const unsubscribe = useCallback<UnsubFunction>((id) => {
230
+ subscriptions.delete(id);
231
+ subscriber.remove(id);
232
+ }, []);
233
+
234
+ const subscribe = useCallback<SubFunction<T>>((callback, options) => {
235
+ const id = subscriber.add(callback, options);
236
+ subscriptions.add(id);
237
+ return id;
238
+ }, []);
239
+
240
+ return { subscribe, unsubscribe };
241
+ }
package/types/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export declare class Subscribe<T> {
13
13
  * if there is a reset in progress, wait for it before publishing a new value.
14
14
  */
15
15
  publish(val: T): Promise<void>;
16
+ private lastId;
16
17
  /**
17
18
  * Subscribe to updates
18
19
  */
@@ -43,4 +44,16 @@ export interface SubscribedStateHookOptions {
43
44
  }
44
45
  /** Subscribe to a state change event in a react component */
45
46
  export declare function useSubscribedState<T>(subscriber: Subscribe<T>): T | undefined;
47
+ type SubOptions = {
48
+ initializeWithPreviousValue?: boolean;
49
+ };
50
+ type UnsubFunction = (id: SubscribeID) => void;
51
+ type SubFunction<T> = (callback: (arg: T | undefined) => void, options?: SubOptions) => SubscribeID;
52
+ export type ReactSubscriber<T> = {
53
+ subscribe: SubFunction<T>;
54
+ unsubscribe: UnsubFunction;
55
+ };
56
+ /** Hook to manage subscriptions within a react component. Any subscriptions setup using the subscribe callback will be unsubscribed on unmount. */
57
+ export declare function useSubscriber<T>(subscriber: Subscribe<T>): ReactSubscriber<T>;
58
+ export {};
46
59
  //# sourceMappingURL=index.d.ts.map