@rpgjs/client 5.0.0-beta.7 → 5.0.0-beta.8

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 (73) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/Game/Map.js +2 -1
  3. package/dist/Game/Map.js.map +1 -1
  4. package/dist/Game/Object.d.ts +17 -9
  5. package/dist/Game/Object.js +1 -12
  6. package/dist/Game/Object.js.map +1 -1
  7. package/dist/Gui/Gui.d.ts +17 -4
  8. package/dist/Gui/Gui.js +63 -33
  9. package/dist/Gui/Gui.js.map +1 -1
  10. package/dist/Gui/Gui.spec.d.ts +1 -0
  11. package/dist/Resource.js +1 -1
  12. package/dist/Resource.js.map +1 -1
  13. package/dist/RpgClient.d.ts +35 -2
  14. package/dist/RpgClientEngine.d.ts +41 -5
  15. package/dist/RpgClientEngine.js +43 -3
  16. package/dist/RpgClientEngine.js.map +1 -1
  17. package/dist/components/character.ce.js +225 -2
  18. package/dist/components/character.ce.js.map +1 -1
  19. package/dist/components/dynamics/bar.ce.js +96 -0
  20. package/dist/components/dynamics/bar.ce.js.map +1 -0
  21. package/dist/components/dynamics/image.ce.js +23 -0
  22. package/dist/components/dynamics/image.ce.js.map +1 -0
  23. package/dist/components/dynamics/parse-value.d.ts +3 -0
  24. package/dist/components/dynamics/parse-value.js +51 -35
  25. package/dist/components/dynamics/parse-value.js.map +1 -1
  26. package/dist/components/dynamics/parse-value.spec.d.ts +1 -0
  27. package/dist/components/dynamics/shape-utils.d.ts +16 -0
  28. package/dist/components/dynamics/shape-utils.js +73 -0
  29. package/dist/components/dynamics/shape-utils.js.map +1 -0
  30. package/dist/components/dynamics/shape-utils.spec.d.ts +1 -0
  31. package/dist/components/dynamics/shape.ce.js +83 -0
  32. package/dist/components/dynamics/shape.ce.js.map +1 -0
  33. package/dist/components/dynamics/text.ce.js +28 -41
  34. package/dist/components/dynamics/text.ce.js.map +1 -1
  35. package/dist/components/player-components-utils.d.ts +67 -0
  36. package/dist/components/player-components-utils.js +162 -0
  37. package/dist/components/player-components-utils.js.map +1 -0
  38. package/dist/components/player-components-utils.spec.d.ts +1 -0
  39. package/dist/components/player-components.ce.js +188 -0
  40. package/dist/components/player-components.ce.js.map +1 -0
  41. package/dist/core/setup.js.map +1 -1
  42. package/dist/module.js +3 -0
  43. package/dist/module.js.map +1 -1
  44. package/dist/node_modules/.pnpm/@signe_reactive@2.10.0/node_modules/@signe/reactive/dist/index.js +197 -3
  45. package/dist/node_modules/.pnpm/@signe_reactive@2.10.0/node_modules/@signe/reactive/dist/index.js.map +1 -1
  46. package/dist/node_modules/.pnpm/@signe_room@2.10.0/node_modules/@signe/room/dist/index.js +1 -1
  47. package/dist/services/loadMap.d.ts +6 -0
  48. package/dist/services/loadMap.js.map +1 -1
  49. package/package.json +4 -4
  50. package/src/Game/Map.ts +12 -2
  51. package/src/Game/Object.ts +22 -35
  52. package/src/Gui/Gui.spec.ts +273 -0
  53. package/src/Gui/Gui.ts +105 -50
  54. package/src/Resource.ts +1 -2
  55. package/src/RpgClient.ts +36 -2
  56. package/src/RpgClientEngine.ts +64 -10
  57. package/src/components/character.ce +281 -1
  58. package/src/components/dynamics/bar.ce +87 -0
  59. package/src/components/dynamics/image.ce +20 -0
  60. package/src/components/dynamics/parse-value.spec.ts +41 -0
  61. package/src/components/dynamics/parse-value.ts +102 -37
  62. package/src/components/dynamics/shape-utils.spec.ts +46 -0
  63. package/src/components/dynamics/shape-utils.ts +61 -0
  64. package/src/components/dynamics/shape.ce +89 -0
  65. package/src/components/dynamics/text.ce +34 -149
  66. package/src/components/player-components-utils.spec.ts +109 -0
  67. package/src/components/player-components-utils.ts +205 -0
  68. package/src/components/player-components.ce +221 -0
  69. package/src/core/setup.ts +2 -2
  70. package/src/module.ts +5 -1
  71. package/src/services/loadMap.ts +2 -0
  72. package/dist/node_modules/.pnpm/@signe_reactive@2.9.2/node_modules/@signe/reactive/dist/index.js +0 -227
  73. package/dist/node_modules/.pnpm/@signe_reactive@2.9.2/node_modules/@signe/reactive/dist/index.js.map +0 -1
@@ -0,0 +1,205 @@
1
+ export const DEFAULT_HP_BAR_STYLE = { fillColor: '#ef4444' };
2
+ export const DEFAULT_SP_BAR_STYLE = { fillColor: '#3b82f6' };
3
+
4
+ const DEFAULT_CELL_HEIGHT = 16;
5
+ const DEFAULT_CELL_WIDTH = 32;
6
+
7
+ type NumberResolver = (value: any, fallback?: number) => number;
8
+
9
+ const defaultToNumber: NumberResolver = (value, fallback = 0) => {
10
+ const num = typeof value === 'number' ? value : parseFloat(value);
11
+ return Number.isFinite(num) ? num : fallback;
12
+ };
13
+
14
+ export function getPointBounds(points: any[] = [], toNumber: NumberResolver = defaultToNumber) {
15
+ if (!Array.isArray(points) || points.length < 2) {
16
+ return { width: 1, height: 1 };
17
+ }
18
+
19
+ let minX = Infinity;
20
+ let minY = Infinity;
21
+ let maxX = -Infinity;
22
+ let maxY = -Infinity;
23
+
24
+ for (let i = 0; i < points.length; i += 2) {
25
+ const x = toNumber(points[i], 0);
26
+ const y = toNumber(points[i + 1], 0);
27
+ minX = Math.min(minX, x);
28
+ minY = Math.min(minY, y);
29
+ maxX = Math.max(maxX, x);
30
+ maxY = Math.max(maxY, y);
31
+ }
32
+
33
+ return {
34
+ width: Math.max(1, maxX - minX),
35
+ height: Math.max(1, maxY - minY)
36
+ };
37
+ }
38
+
39
+ export function getComponentId(definition: any) {
40
+ if (!definition) return undefined;
41
+ if (definition.id) return definition.id;
42
+ if (definition.type === 'text') return 'rpg:text';
43
+ if (definition.type === 'hpBar') return 'rpg:hpBar';
44
+ if (definition.type === 'spBar') return 'rpg:spBar';
45
+ if (definition.type === 'bar') return 'rpg:bar';
46
+ if (definition.type === 'shape') return 'rpg:shape';
47
+ if (definition.type === 'image') return 'rpg:image';
48
+ if (definition.type === 'tile') return 'rpg:tile';
49
+ return definition.type;
50
+ }
51
+
52
+ export function getComponentProps(definition: any) {
53
+ if (definition.props) return definition.props;
54
+
55
+ if (definition.type === 'text') {
56
+ return { value: definition.value, style: definition.style };
57
+ }
58
+ if (definition.type === 'hpBar') {
59
+ return { current: '{hp}', max: '{param.maxHp}', style: { ...DEFAULT_HP_BAR_STYLE, ...definition.style }, text: definition.text };
60
+ }
61
+ if (definition.type === 'spBar') {
62
+ return { current: '{sp}', max: '{param.maxSp}', style: { ...DEFAULT_SP_BAR_STYLE, ...definition.style }, text: definition.text };
63
+ }
64
+ if (definition.type === 'bar') {
65
+ return { current: `{${definition.current}}`, max: `{${definition.max}}`, style: definition.style, text: definition.text };
66
+ }
67
+ if (definition.type === 'shape') {
68
+ return definition.value;
69
+ }
70
+ if (definition.type === 'image' || definition.type === 'tile') {
71
+ return { value: definition.value };
72
+ }
73
+
74
+ return {};
75
+ }
76
+
77
+ export function estimateComponentSize(
78
+ definition: any,
79
+ {
80
+ toNumber = defaultToNumber,
81
+ estimateTextWidth = (value: any) => String(value ?? '').length * 8
82
+ }: {
83
+ toNumber?: NumberResolver;
84
+ estimateTextWidth?: (value: any, style?: any) => number;
85
+ } = {}
86
+ ) {
87
+ const props = getComponentProps(definition);
88
+ const style = props?.style ?? definition?.style ?? {};
89
+
90
+ if (definition?.type === 'text' || definition?.id === 'rpg:text') {
91
+ return {
92
+ width: estimateTextWidth(props.value ?? definition.value, style),
93
+ height: toNumber(style.fontSize, 12)
94
+ };
95
+ }
96
+
97
+ if (definition?.type === 'hpBar' || definition?.type === 'spBar' || definition?.type === 'bar' || definition?.id === 'rpg:hpBar' || definition?.id === 'rpg:spBar' || definition?.id === 'rpg:bar') {
98
+ const barHeight = toNumber(style.height, 8);
99
+ const labelHeight = props.text != null && props.text !== '' ? toNumber(style.fontSize, 10) + 2 : 0;
100
+ return {
101
+ width: toNumber(style.width, 50),
102
+ height: barHeight + labelHeight
103
+ };
104
+ }
105
+
106
+ if (definition?.type === 'shape' || definition?.id === 'rpg:shape') {
107
+ const shape = props ?? {};
108
+ if (shape.type === 'circle') {
109
+ const radius = toNumber(shape.radius, 8);
110
+ return { width: radius * 2, height: radius * 2 };
111
+ }
112
+ if (shape.type === 'line') {
113
+ return {
114
+ width: Math.max(1, Math.abs(toNumber(shape.x2, 16) - toNumber(shape.x1, 0))),
115
+ height: Math.max(1, Math.abs(toNumber(shape.y2, 0) - toNumber(shape.y1, 0)))
116
+ };
117
+ }
118
+ if (shape.type === 'polygon') {
119
+ return getPointBounds(shape.points, toNumber);
120
+ }
121
+ return {
122
+ width: toNumber(shape.width, 16),
123
+ height: toNumber(shape.height, 16)
124
+ };
125
+ }
126
+
127
+ return {
128
+ width: toNumber(definition?.props?.width, DEFAULT_CELL_WIDTH),
129
+ height: toNumber(definition?.props?.height, DEFAULT_CELL_HEIGHT)
130
+ };
131
+ }
132
+
133
+ export function computeBlockSize({
134
+ position,
135
+ layout = {},
136
+ rowMetrics,
137
+ gap = { row: 0, column: 0 },
138
+ graphic,
139
+ hitbox
140
+ }: {
141
+ position: string;
142
+ layout?: any;
143
+ rowMetrics: Array<{ width: number; height: number; cells: any[] }>;
144
+ gap?: { row: number; column: number };
145
+ graphic: { width: number; height: number };
146
+ hitbox: { w: number; h: number };
147
+ }) {
148
+ const rowGapTotal = Math.max(0, rowMetrics.length - 1) * gap.row;
149
+ const maxColumns = rowMetrics.reduce((max, row) => Math.max(max, row.cells.length), 0);
150
+ const columnGapTotal = Math.max(0, maxColumns - 1) * gap.column;
151
+ const contentWidth = rowMetrics.reduce((max, row) => Math.max(max, row.width), 0) + columnGapTotal;
152
+ const contentHeight = rowMetrics.reduce((sum, row) => sum + row.height, 0) + rowGapTotal;
153
+ const width = layout.width ?? Math.max(contentWidth, position === 'bottom' ? hitbox.w : position === 'top' || position === 'center' ? graphic.width : contentWidth);
154
+ const height = layout.height ?? Math.max(contentHeight, position === 'bottom' ? hitbox.h : position === 'left' || position === 'right' || position === 'center' ? graphic.height : contentHeight);
155
+
156
+ return { width, height };
157
+ }
158
+
159
+ export function computeBlockPosition({
160
+ position,
161
+ size,
162
+ layout = {},
163
+ graphic,
164
+ hitbox
165
+ }: {
166
+ position: string;
167
+ size: { width: number; height: number };
168
+ layout?: any;
169
+ graphic: { left: number; top: number; right: number; centerX: number; centerY: number };
170
+ hitbox: { w: number; h: number };
171
+ }) {
172
+ const marginLeft = layout.marginLeft ?? 0;
173
+ const marginRight = layout.marginRight ?? 0;
174
+ const marginTop = layout.marginTop ?? 0;
175
+ const marginBottom = layout.marginBottom ?? 0;
176
+
177
+ switch (position) {
178
+ case 'bottom':
179
+ return {
180
+ x: (hitbox.w / 2) - (size.width / 2) + marginLeft - marginRight,
181
+ y: (hitbox.h / 2) - (size.height / 2) + marginBottom - marginTop
182
+ };
183
+ case 'center':
184
+ return {
185
+ x: graphic.centerX - (size.width / 2) + marginLeft - marginRight,
186
+ y: graphic.centerY - (size.height / 2) + marginTop - marginBottom
187
+ };
188
+ case 'left':
189
+ return {
190
+ x: graphic.left - size.width + marginLeft - marginRight,
191
+ y: graphic.centerY - (size.height / 2) + marginTop - marginBottom
192
+ };
193
+ case 'right':
194
+ return {
195
+ x: graphic.right + marginLeft - marginRight,
196
+ y: graphic.centerY - (size.height / 2) + marginTop - marginBottom
197
+ };
198
+ case 'top':
199
+ default:
200
+ return {
201
+ x: graphic.centerX - (size.width / 2) + marginLeft - marginRight,
202
+ y: graphic.top - size.height + marginTop - marginBottom
203
+ };
204
+ }
205
+ }
@@ -0,0 +1,221 @@
1
+ <Container
2
+ x={blockPosition().x}
3
+ y={blockPosition().y}
4
+ width={blockSize().width}
5
+ height={blockSize().height}
6
+ minWidth={blockSize().width}
7
+ minHeight={blockSize().height}
8
+ display="flex"
9
+ flexDirection="column"
10
+ justifyContent="center"
11
+ alignItems="center"
12
+ rowGap={gap().row}
13
+ >
14
+ @for (row of renderedRows) {
15
+ <Container
16
+ width="100%"
17
+ height={row.height}
18
+ display="flex"
19
+ flexDirection="row"
20
+ justifyContent="center"
21
+ alignItems="center"
22
+ columnGap={gap().column}
23
+ >
24
+ @for (entry of row.entries) {
25
+ <Container
26
+ width={entry.width}
27
+ height={entry.height}
28
+ display="flex"
29
+ justifyContent="center"
30
+ alignItems="center"
31
+ >
32
+ <entry.component object ...entry.props />
33
+ </Container>
34
+ }
35
+ </Container>
36
+ }
37
+ </Container>
38
+
39
+ <script>
40
+ import { computed } from "canvasengine";
41
+ import { RpgClientEngine } from "../RpgClientEngine";
42
+ import { inject } from "../core/inject";
43
+ import { resolveDynamicProps, resolveDynamicValue } from "./dynamics/parse-value";
44
+ import {
45
+ computeBlockPosition,
46
+ computeBlockSize,
47
+ estimateComponentSize,
48
+ getComponentId,
49
+ getComponentProps
50
+ } from "./player-components-utils";
51
+
52
+ const { object, position, graphicBounds } = defineProps({
53
+ position: {
54
+ default: 'top'
55
+ }
56
+ });
57
+
58
+ const client = inject(RpgClientEngine);
59
+ const warnedComponents = new Set();
60
+
61
+ const readPosition = computed(() => position?.() ?? 'top');
62
+
63
+ const componentSource = computed(() => {
64
+ switch (readPosition()) {
65
+ case 'bottom':
66
+ return object.componentsBottom?.();
67
+ case 'center':
68
+ return object.componentsCenter?.();
69
+ case 'left':
70
+ return object.componentsLeft?.();
71
+ case 'right':
72
+ return object.componentsRight?.();
73
+ case 'top':
74
+ default:
75
+ return object.componentsTop?.();
76
+ }
77
+ });
78
+
79
+ const componentData = computed(() => {
80
+ const value = componentSource();
81
+ if (!value) {
82
+ return { components: [], layout: {} };
83
+ }
84
+
85
+ if (typeof value !== 'string') {
86
+ return value;
87
+ }
88
+
89
+ try {
90
+ return JSON.parse(value);
91
+ } catch (error) {
92
+ if (typeof process === 'undefined' || process.env?.NODE_ENV !== 'production') {
93
+ console.warn('[RPGJS] Invalid server sprite component payload', error);
94
+ }
95
+ return { components: [], layout: {} };
96
+ }
97
+ });
98
+
99
+ const layout = computed(() => componentData()?.layout ?? {});
100
+ const rows = computed(() => componentData()?.components ?? []);
101
+ const hitbox = object.hitbox;
102
+
103
+ const toNumber = (value, fallback = 0) => {
104
+ const resolved = resolveDynamicValue(value, object);
105
+ const num = typeof resolved === 'number' ? resolved : parseFloat(resolved);
106
+ return Number.isFinite(num) ? num : fallback;
107
+ };
108
+
109
+ const estimateTextWidth = (value, style = {}) => {
110
+ const text = String(resolveDynamicValue(value ?? '', object) ?? '');
111
+ const fontSize = toNumber(style.fontSize, 12);
112
+ return Math.max(1, Math.ceil(text.length * fontSize * 0.6));
113
+ };
114
+
115
+ const rowMetrics = computed(() => {
116
+ return rows().map((row) => {
117
+ const cells = row.map((definition) => {
118
+ const intrinsic = estimateComponentSize(definition, { toNumber, estimateTextWidth });
119
+ return {
120
+ definition,
121
+ width: intrinsic.width,
122
+ height: intrinsic.height
123
+ };
124
+ });
125
+
126
+ return {
127
+ cells,
128
+ width: cells.reduce((sum, cell) => sum + cell.width, 0),
129
+ height: cells.reduce((max, cell) => Math.max(max, cell.height), 0)
130
+ };
131
+ });
132
+ });
133
+
134
+ const gap = computed(() => ({
135
+ row: toNumber(layout().rowGap ?? layout().gap, 0),
136
+ column: toNumber(layout().columnGap ?? layout().gap, 0)
137
+ }));
138
+
139
+ const fallbackBounds = computed(() => {
140
+ const box = hitbox();
141
+ const width = box?.w ?? 0;
142
+ const height = box?.h ?? 0;
143
+
144
+ return {
145
+ left: 0,
146
+ top: 0,
147
+ right: width,
148
+ bottom: height,
149
+ width,
150
+ height,
151
+ centerX: width / 2,
152
+ centerY: height / 2
153
+ };
154
+ });
155
+
156
+ const bounds = computed(() => {
157
+ const resolvedBounds = typeof graphicBounds === 'function' ? graphicBounds() : undefined;
158
+ return resolvedBounds ?? fallbackBounds();
159
+ });
160
+
161
+ const blockSize = computed(() => {
162
+ const box = hitbox() ?? { w: 0, h: 0 };
163
+ const graphic = bounds();
164
+ return computeBlockSize({
165
+ position: readPosition(),
166
+ layout: layout(),
167
+ rowMetrics: rowMetrics(),
168
+ gap: gap(),
169
+ graphic,
170
+ hitbox: box
171
+ });
172
+ });
173
+
174
+ const blockPosition = computed(() => {
175
+ const box = hitbox() ?? { w: 0, h: 0 };
176
+ const graphic = bounds();
177
+ return computeBlockPosition({
178
+ position: readPosition(),
179
+ size: blockSize(),
180
+ layout: layout(),
181
+ graphic,
182
+ hitbox: box
183
+ });
184
+ });
185
+
186
+ const warnMissingComponent = (id) => {
187
+ if (!id || warnedComponents.has(id)) return;
188
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') return;
189
+ warnedComponents.add(id);
190
+ console.warn(`[RPGJS] Server sprite component "${id}" is not registered on the client.`);
191
+ };
192
+
193
+ const renderedRows = computed(() => {
194
+ return rowMetrics().map((row) => {
195
+ const entries = [];
196
+
197
+ row.cells.forEach((cell) => {
198
+ const definition = cell.definition;
199
+ const id = getComponentId(definition);
200
+ const component = client.getSpriteComponent(id);
201
+
202
+ if (!component) {
203
+ warnMissingComponent(id);
204
+ return;
205
+ }
206
+
207
+ entries.push({
208
+ component,
209
+ props: resolveDynamicProps(getComponentProps(definition), object),
210
+ width: cell.width,
211
+ height: cell.height
212
+ });
213
+ });
214
+
215
+ return {
216
+ height: row.height,
217
+ entries
218
+ };
219
+ });
220
+ });
221
+ </script>
package/src/core/setup.ts CHANGED
@@ -14,7 +14,7 @@ export async function startGame(options: SetupOptions) {
14
14
 
15
15
  await injector(context, options.providers);
16
16
 
17
- const engine = inject(context, RpgClientEngine);
17
+ const engine = inject<RpgClientEngine>(context, RpgClientEngine);
18
18
  await engine.start();
19
19
  return context;
20
- }
20
+ }
package/src/module.ts CHANGED
@@ -195,6 +195,11 @@ export function provideClientModules(modules: RpgClientModule[]): FactoryProvide
195
195
  engine.addSpriteComponentInFront(component);
196
196
  });
197
197
  }
198
+ if (sprite.components) {
199
+ Object.entries(sprite.components).forEach(([id, component]) => {
200
+ engine.registerSpriteComponent(id, component);
201
+ });
202
+ }
198
203
  },
199
204
  };
200
205
  }
@@ -226,4 +231,3 @@ export function provideClientGlobalConfig(config: any = {}) {
226
231
  }
227
232
  return provideGlobalConfig(config)
228
233
  }
229
-
@@ -20,6 +20,8 @@ type MapData = {
20
20
  height?: number;
21
21
  /** Optional map events data (NPCs, interactive objects, etc.) */
22
22
  events?: any;
23
+ /** Optional named positions, for example Tiled point objects used by changeMap("map", "name") */
24
+ positions?: Record<string, { x: number; y: number; z?: number }>;
23
25
  /** Optional map identifier, defaults to the mapId parameter if not provided */
24
26
  id?: string;
25
27
  }
@@ -1,227 +0,0 @@
1
- import { BehaviorSubject, filter } from "rxjs";
2
- //#region ../../node_modules/.pnpm/@signe+reactive@2.9.2/node_modules/@signe/reactive/dist/index.js
3
- var ArraySubject = class extends BehaviorSubject {
4
- constructor(items = []) {
5
- super({
6
- type: "init",
7
- items
8
- });
9
- this._items = [];
10
- this.createProxy(items);
11
- }
12
- createProxy(items) {
13
- this._items = new Proxy(items, {
14
- get: (target, prop, receiver) => {
15
- const origMethod = target[prop];
16
- if (typeof origMethod === "function") return (...args) => {
17
- let changeType = "update";
18
- let index = void 0;
19
- let isMutateFn = false;
20
- let itemsToEmit = [];
21
- let changeSplice = true;
22
- switch (prop) {
23
- case "push":
24
- index = target.length;
25
- changeType = "add";
26
- isMutateFn = true;
27
- break;
28
- case "pop":
29
- index = target.length - 1;
30
- changeType = "remove";
31
- isMutateFn = true;
32
- break;
33
- case "unshift":
34
- index = 0;
35
- changeType = "add";
36
- isMutateFn = true;
37
- break;
38
- case "shift":
39
- index = 0;
40
- changeType = "remove";
41
- isMutateFn = true;
42
- break;
43
- case "splice":
44
- index = args[0];
45
- const deleteCount = args[1];
46
- const newItems = args.slice(2);
47
- itemsToEmit = newItems;
48
- if (deleteCount > 0 && newItems.length === 0) changeType = "remove";
49
- else if (deleteCount === 0 && newItems.length > 0) changeType = "add";
50
- else if (deleteCount === 0 && newItems.length === 0) changeSplice = false;
51
- else changeType = "update";
52
- isMutateFn = true;
53
- break;
54
- }
55
- const result = origMethod.apply(target, args);
56
- if (isMutateFn && changeSplice) if (prop === "splice") this.next({
57
- type: changeType,
58
- index,
59
- items: itemsToEmit
60
- });
61
- else this.next({
62
- type: changeType,
63
- index,
64
- items: args
65
- });
66
- return result;
67
- };
68
- return Reflect.get(target, prop, receiver);
69
- },
70
- set: (target, prop, value) => {
71
- const index = !isNaN(Number(prop)) ? Number(prop) : void 0;
72
- target[prop] = value;
73
- this.next({
74
- type: "update",
75
- index,
76
- items: [value]
77
- });
78
- return true;
79
- }
80
- });
81
- }
82
- get items() {
83
- return this._items;
84
- }
85
- set items(newItems) {
86
- this.createProxy(newItems);
87
- this.next({
88
- type: "reset",
89
- items: newItems
90
- });
91
- }
92
- };
93
- var ObjectSubject = class extends BehaviorSubject {
94
- constructor(obj = {}) {
95
- super({
96
- type: "init",
97
- value: obj
98
- });
99
- this.createProxy(obj);
100
- }
101
- createProxy(obj) {
102
- this._obj = new Proxy(obj, {
103
- get: (target, prop, receiver) => {
104
- return Reflect.get(target, prop, receiver);
105
- },
106
- set: (target, prop, value, receiver) => {
107
- const key = prop;
108
- const changeType = key in target ? "update" : "add";
109
- target[key] = value;
110
- this.next({
111
- type: changeType,
112
- key,
113
- value
114
- });
115
- return true;
116
- },
117
- deleteProperty: (target, prop) => {
118
- const key = prop;
119
- if (key in target) {
120
- const value = target[key];
121
- delete target[key];
122
- this.next({
123
- type: "remove",
124
- key,
125
- value
126
- });
127
- return true;
128
- }
129
- return false;
130
- }
131
- });
132
- }
133
- get obj() {
134
- return this._obj;
135
- }
136
- set obj(newObj) {
137
- this.createProxy(newObj);
138
- this.next({
139
- type: "reset",
140
- value: newObj
141
- });
142
- }
143
- };
144
- var getGlobalReactiveStore = () => {
145
- const globalKey = "__REACTIVE_STORE__";
146
- if (typeof globalThis !== "undefined") {
147
- if (!globalThis[globalKey]) globalThis[globalKey] = {
148
- currentDependencyTracker: null,
149
- currentSubscriptionsTracker: null
150
- };
151
- return globalThis[globalKey];
152
- }
153
- let globalObj;
154
- if (typeof window !== "undefined") globalObj = window;
155
- else if (typeof self !== "undefined") globalObj = self;
156
- else if (typeof Function !== "undefined") globalObj = Function("return this")();
157
- else {
158
- console.warn("Unable to find global object, using local instance");
159
- return {
160
- currentDependencyTracker: null,
161
- currentSubscriptionsTracker: null
162
- };
163
- }
164
- if (!globalObj[globalKey]) globalObj[globalKey] = {
165
- currentDependencyTracker: null,
166
- currentSubscriptionsTracker: null
167
- };
168
- return globalObj[globalKey];
169
- };
170
- var reactiveStore = getGlobalReactiveStore();
171
- var trackDependency = (signal2) => {
172
- if (reactiveStore.currentDependencyTracker) reactiveStore.currentDependencyTracker(signal2);
173
- };
174
- function signal(defaultValue, options) {
175
- let subject;
176
- if (Array.isArray(defaultValue)) subject = new ArraySubject(defaultValue);
177
- else if (typeof defaultValue === "object" && defaultValue !== null) subject = new ObjectSubject(defaultValue);
178
- else subject = new BehaviorSubject(defaultValue);
179
- const getValue = () => {
180
- if (subject instanceof ArraySubject) return subject.items;
181
- else if (subject instanceof ObjectSubject) return subject.obj;
182
- return subject.value;
183
- };
184
- const fn = function() {
185
- trackDependency(fn);
186
- return getValue();
187
- };
188
- fn.set = (value) => {
189
- const currentValue = getValue();
190
- let shouldEmit = true;
191
- if (options?.equal) shouldEmit = !options.equal(currentValue, value);
192
- else shouldEmit = currentValue !== value;
193
- if (shouldEmit) if (subject instanceof ArraySubject) subject.items = value;
194
- else if (subject instanceof ObjectSubject) subject.obj = value;
195
- else subject.next(value);
196
- };
197
- fn._isFrozen = false;
198
- fn.freeze = () => {
199
- fn._isFrozen = true;
200
- };
201
- fn.unfreeze = () => {
202
- fn._isFrozen = false;
203
- if (subject instanceof ArraySubject) subject.next({
204
- type: "init",
205
- items: subject.items
206
- });
207
- else if (subject instanceof ObjectSubject) subject.next({
208
- type: "init",
209
- value: subject.obj
210
- });
211
- else subject.next(subject.value);
212
- };
213
- fn.mutate = (mutateFn) => {
214
- mutateFn(getValue());
215
- };
216
- fn.update = (updateFn) => {
217
- const updatedValue = updateFn(getValue());
218
- fn.set(updatedValue);
219
- };
220
- fn.observable = subject.asObservable().pipe(filter(() => !fn._isFrozen));
221
- fn._subject = subject;
222
- return fn;
223
- }
224
- //#endregion
225
- export { signal };
226
-
227
- //# sourceMappingURL=index.js.map