@ninetailed/experience.js-plugin-preview 7.12.1 → 7.13.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs.js CHANGED
@@ -128,9 +128,10 @@ class NinetailedPreviewPlugin extends experience_jsPluginAnalytics.NinetailedPlu
128
128
  this.audienceDefinitions = [];
129
129
  this.audienceOverwrites = {};
130
130
  this.experienceVariantIndexOverwrites = {};
131
+ this.variableOverwrites = {};
131
132
  this.profile = null;
133
+ this.changes = [];
132
134
  this.container = null;
133
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
134
135
  this.bridge = null;
135
136
  /**
136
137
  * Since several instances of the plugin can be created, we need to make sure only one is marked as active.
@@ -140,7 +141,7 @@ class NinetailedPreviewPlugin extends experience_jsPluginAnalytics.NinetailedPlu
140
141
  this.clientId = null;
141
142
  this.environment = null;
142
143
  this.initialize = () => __awaiter(this, void 0, void 0, function* () {
143
- var _b;
144
+ var _b, _c;
144
145
  if (typeof window !== 'undefined') {
145
146
  if (WidgetContainer.isContainerAttached()) {
146
147
  experience_jsShared.logger.warn('Preview plugin is already attached.');
@@ -166,6 +167,9 @@ class NinetailedPreviewPlugin extends experience_jsPluginAnalytics.NinetailedPlu
166
167
  this.bridge.updateProps({
167
168
  props: this.pluginApi
168
169
  });
170
+ if (!((_c = this.changes) === null || _c === void 0 ? void 0 : _c.length)) {
171
+ this.onChange();
172
+ }
169
173
  }
170
174
  });
171
175
  this.loaded = () => true;
@@ -176,9 +180,31 @@ class NinetailedPreviewPlugin extends experience_jsPluginAnalytics.NinetailedPlu
176
180
  return;
177
181
  }
178
182
  if (payload === null || payload === void 0 ? void 0 : payload.profile) {
179
- this.onProfileChange(payload.profile);
183
+ this.onProfileChange(payload.profile, payload.changes || []);
180
184
  }
181
185
  };
186
+ /**
187
+ * Implements the HasChangesModificationMiddleware interface
188
+ * Returns a middleware function that applies variable overwrites to changes
189
+ */
190
+ this.getChangesModificationMiddleware = () => {
191
+ if (!this.isActiveInstance || Object.keys(this.variableOverwrites).length === 0) {
192
+ return undefined;
193
+ }
194
+ return ({
195
+ changes: inputChanges
196
+ }) => {
197
+ if (!inputChanges || inputChanges.length === 0) {
198
+ return {
199
+ changes: inputChanges
200
+ };
201
+ }
202
+ // Calculate and return overridden changes on demand instead of storing them
203
+ return {
204
+ changes: this.getEffectiveChanges(inputChanges)
205
+ };
206
+ };
207
+ };
182
208
  this.getExperienceSelectionMiddleware = ({
183
209
  baseline,
184
210
  experiences
@@ -198,7 +224,35 @@ class NinetailedPreviewPlugin extends experience_jsPluginAnalytics.NinetailedPlu
198
224
  variantIndex: 0
199
225
  };
200
226
  }
201
- const baselineComponent = experience.components.find(component => component.baseline.id === baseline.id);
227
+ // Handle entry replacements as before
228
+ const entryReplacementComponents = experience.components.filter(component => component.type === experience_jsShared.ComponentTypeEnum.EntryReplacement && 'id' in component.baseline);
229
+ const baselineComponent = entryReplacementComponents.find(component => component.baseline.id === baseline.id);
230
+ // Get the selected variant index
231
+ const variantIndex = this.pluginApi.experienceVariantIndexes[experience.id];
232
+ // Handle variable components for this experience (NEW CODE)
233
+ if (variantIndex !== undefined) {
234
+ // Process all variable components for this experience
235
+ const variableComponents = experience.components.filter(component => component.type === experience_jsShared.ComponentTypeEnum.InlineVariable);
236
+ // Set variable values based on the selected variant index
237
+ variableComponents.forEach(component => {
238
+ const key = component.key;
239
+ let value;
240
+ if (variantIndex === 0) {
241
+ value = component.baseline;
242
+ } else {
243
+ const variant = component.variants[variantIndex - 1];
244
+ value = variant && 'value' in variant ? variant.value : component.baseline;
245
+ }
246
+ // Set the variable in our changes system
247
+ this.setVariableValue({
248
+ experienceId: experience.id,
249
+ key,
250
+ value,
251
+ variantIndex
252
+ });
253
+ });
254
+ }
255
+ // Continue with entry replacement handling
202
256
  if (!baselineComponent) {
203
257
  return {
204
258
  experience,
@@ -207,7 +261,6 @@ class NinetailedPreviewPlugin extends experience_jsPluginAnalytics.NinetailedPlu
207
261
  };
208
262
  }
209
263
  const allVariants = [baseline, ...baselineComponent.variants];
210
- const variantIndex = this.pluginApi.experienceVariantIndexes[experience.id];
211
264
  if (allVariants.length <= variantIndex) {
212
265
  return {
213
266
  experience,
@@ -233,18 +286,42 @@ class NinetailedPreviewPlugin extends experience_jsPluginAnalytics.NinetailedPlu
233
286
  this.onChange = () => {
234
287
  var _b;
235
288
  experience_jsShared.logger.debug('Ninetailed Preview Plugin onChange pluginApi:', this.pluginApi);
236
- Object.assign({}, window.ninetailed, {
237
- plugins: Object.assign(Object.assign({}, (_b = window.ninetailed) === null || _b === void 0 ? void 0 : _b.plugins), {
238
- preview: this.windowApi
239
- })
240
- });
289
+ if (typeof window !== 'undefined') {
290
+ window.ninetailed = Object.assign({}, window.ninetailed, {
291
+ plugins: Object.assign(Object.assign({}, (_b = window.ninetailed) === null || _b === void 0 ? void 0 : _b.plugins), {
292
+ preview: this.windowApi
293
+ })
294
+ });
295
+ }
241
296
  this.bridge.updateProps({
242
297
  props: this.pluginApi
243
298
  });
244
299
  this.onChangeEmitter.invokeListeners();
245
300
  };
246
- this.onProfileChange = profile => {
301
+ this.onProfileChange = (profile, changes) => {
247
302
  this.profile = profile;
303
+ experience_jsShared.logger.debug('Profile changed:', {
304
+ profile,
305
+ changes
306
+ });
307
+ // If changes are provided, update them
308
+ if (changes) {
309
+ this.onChangesChange(changes);
310
+ }
311
+ this.onChange();
312
+ };
313
+ /**
314
+ * Handles changes from the SDK and applies any variable overrides.
315
+ * This should be called whenever the original changes are updated.
316
+ */
317
+ this.onChangesChange = incomingChanges => {
318
+ if (!this.isActiveInstance) {
319
+ return;
320
+ }
321
+ experience_jsShared.logger.debug('Received changes:', incomingChanges);
322
+ // Store the original changes
323
+ this.changes = incomingChanges;
324
+ // Notify listeners and update UI
248
325
  this.onChange();
249
326
  };
250
327
  this.setCredentials = ({
@@ -370,23 +447,40 @@ class NinetailedPreviewPlugin extends experience_jsPluginAnalytics.NinetailedPlu
370
447
  }
371
448
  const experience = this.experiences.find(experience => experience.id === experienceId);
372
449
  if (!experience) {
373
- experience_jsShared.logger.warn(`You cannot active a variant for an unknown experience (id: ${experienceId})`);
450
+ experience_jsShared.logger.warn(`Cannot activate a variant for an unknown experience (id: ${experienceId})`);
374
451
  return;
375
452
  }
376
453
  if (experience.audience && !this.activeAudiences.some(id => {
377
454
  var _b;
378
455
  return id === ((_b = experience.audience) === null || _b === void 0 ? void 0 : _b.id);
379
456
  })) {
380
- experience_jsShared.logger.warn(`You cannot active a variant for an experience (id: ${experienceId}), which is not in the active audiences.`);
457
+ experience_jsShared.logger.warn(`Cannot activate a variant for an experience (id: ${experienceId}) which is not in the active audiences.`);
381
458
  return;
382
459
  }
383
460
  const isValidIndex = experience.components.map(component => component.variants.length + 1).every(length => length > variantIndex);
384
461
  if (!isValidIndex) {
385
462
  experience_jsShared.logger.warn(`You activated a variant at index ${variantIndex} for the experience (id: ${experienceId}). Not all components have that many variants, you may see the baseline for some.`);
386
463
  }
464
+ // Update the experience variant index
387
465
  this.experienceVariantIndexOverwrites = Object.assign(Object.assign({}, this.experienceVariantIndexOverwrites), {
388
466
  [experienceId]: variantIndex
389
467
  });
468
+ // Process all components to extract variable values
469
+ experience.components.forEach(component => {
470
+ var _b, _c;
471
+ if (component.type === experience_jsShared.ComponentTypeEnum.InlineVariable) {
472
+ const key = component.key;
473
+ const value = variantIndex === 0 ? component.baseline.value : (_c = (_b = component.variants[variantIndex - 1]) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : component.baseline.value;
474
+ // Set the variable value
475
+ this.setVariableValue({
476
+ experienceId,
477
+ key,
478
+ value,
479
+ variantIndex
480
+ });
481
+ }
482
+ });
483
+ // Trigger change notification - this updates the middleware
390
484
  this.onChange();
391
485
  }
392
486
  resetExperience(experienceId) {
@@ -408,6 +502,42 @@ class NinetailedPreviewPlugin extends experience_jsPluginAnalytics.NinetailedPlu
408
502
  window.ninetailed.reset();
409
503
  }
410
504
  }
505
+ /**
506
+ * Sets a variable value override for preview
507
+ */
508
+ setVariableValue({
509
+ experienceId,
510
+ key,
511
+ value,
512
+ variantIndex
513
+ }) {
514
+ var _b;
515
+ if (!this.isActiveInstance) {
516
+ return;
517
+ }
518
+ const overrideKey = `${experienceId}:${key}`;
519
+ // Only create new object if actually changing
520
+ if (((_b = this.variableOverwrites[overrideKey]) === null || _b === void 0 ? void 0 : _b.value) === value) {
521
+ return; // No change needed
522
+ }
523
+
524
+ const change = {
525
+ type: experience_jsShared.ChangeTypes.Variable,
526
+ key,
527
+ value,
528
+ meta: {
529
+ experienceId,
530
+ variantIndex
531
+ }
532
+ };
533
+ // Update variable overwrites
534
+ this.variableOverwrites = Object.assign(Object.assign({}, this.variableOverwrites), {
535
+ [overrideKey]: change
536
+ });
537
+ // Notify listeners
538
+ this.onChangeEmitter.invokeListeners();
539
+ this.onChange();
540
+ }
411
541
  openExperienceEditor(experience) {
412
542
  if (this.onOpenExperienceEditor && typeof this.onOpenExperienceEditor === 'function') return this.onOpenExperienceEditor(experience);
413
543
  }
@@ -420,7 +550,7 @@ class NinetailedPreviewPlugin extends experience_jsPluginAnalytics.NinetailedPlu
420
550
  get pluginApi() {
421
551
  var _b;
422
552
  return {
423
- version: "7.12.1" ,
553
+ version: "7.13.0-beta.1" ,
424
554
  open: this.open.bind(this),
425
555
  close: this.close.bind(this),
426
556
  toggle: this.toggle.bind(this),
@@ -456,7 +586,9 @@ class NinetailedPreviewPlugin extends experience_jsPluginAnalytics.NinetailedPlu
456
586
  activeAudiences: this.activeAudiences,
457
587
  setExperienceVariant: this.setExperienceVariant.bind(this),
458
588
  resetExperience: this.resetExperience.bind(this),
459
- experienceVariantIndexes: Object.assign(Object.assign({}, this.experienceVariantIndexes), this.experienceVariantIndexOverwrites)
589
+ experienceVariantIndexes: Object.assign(Object.assign({}, this.experienceVariantIndexes), this.experienceVariantIndexOverwrites),
590
+ setVariableValue: this.setVariableValue.bind(this),
591
+ variableOverwrites: this.variableOverwrites
460
592
  };
461
593
  }
462
594
  isKnownAudience(id) {
@@ -503,6 +635,31 @@ class NinetailedPreviewPlugin extends experience_jsPluginAnalytics.NinetailedPlu
503
635
  audiences: this.activeAudiences
504
636
  }));
505
637
  }
638
+ /**
639
+ * Get the override key for a variable
640
+ */
641
+ getOverrideKey(experienceId, key) {
642
+ return `${experienceId}:${key}`;
643
+ }
644
+ /**
645
+ * Get effective changes by applying overrides - compute on demand
646
+ */
647
+ getEffectiveChanges(inputChanges = this.changes) {
648
+ if (!inputChanges || Object.keys(this.variableOverwrites).length === 0) {
649
+ return inputChanges || [];
650
+ }
651
+ // Filter out changes that we're overriding
652
+ const filteredChanges = inputChanges.filter(change => {
653
+ var _b;
654
+ if (change.type !== experience_jsShared.ChangeTypes.Variable) return true;
655
+ const changeKey = ((_b = change.meta) === null || _b === void 0 ? void 0 : _b.experienceId) ? this.getOverrideKey(change.meta.experienceId, change.key) : change.key;
656
+ return !this.variableOverwrites[changeKey];
657
+ });
658
+ const effectiveChanges = [...filteredChanges, ...Object.values(this.variableOverwrites)];
659
+ experience_jsShared.logger.debug('Overridden changes after applying override:', effectiveChanges);
660
+ // Add our overrides to create the final result
661
+ return effectiveChanges;
662
+ }
506
663
  }
507
664
  _a = experience_js.PROFILE_CHANGE;
508
665
 
package/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import { logger, unionBy } from '@ninetailed/experience.js-shared';
1
+ import { logger, ComponentTypeEnum, ChangeTypes, unionBy } from '@ninetailed/experience.js-shared';
2
2
  import { OnChangeEmitter, PROFILE_CHANGE, isExperienceMatch, selectDistribution } from '@ninetailed/experience.js';
3
3
  import { NinetailedPlugin } from '@ninetailed/experience.js-plugin-analytics';
4
4
 
@@ -97,9 +97,10 @@ class NinetailedPreviewPlugin extends NinetailedPlugin {
97
97
  this.audienceDefinitions = [];
98
98
  this.audienceOverwrites = {};
99
99
  this.experienceVariantIndexOverwrites = {};
100
+ this.variableOverwrites = {};
100
101
  this.profile = null;
102
+ this.changes = [];
101
103
  this.container = null;
102
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
104
  this.bridge = null;
104
105
  /**
105
106
  * Since several instances of the plugin can be created, we need to make sure only one is marked as active.
@@ -112,7 +113,7 @@ class NinetailedPreviewPlugin extends NinetailedPlugin {
112
113
  this.environment = null;
113
114
  this.initialize = async function () {
114
115
  if (typeof window !== 'undefined') {
115
- var _window$ninetailed;
116
+ var _window$ninetailed, _this$changes;
116
117
  if (WidgetContainer.isContainerAttached()) {
117
118
  logger.warn('Preview plugin is already attached.');
118
119
  _this.isActiveInstance = false;
@@ -137,6 +138,9 @@ class NinetailedPreviewPlugin extends NinetailedPlugin {
137
138
  _this.bridge.updateProps({
138
139
  props: _this.pluginApi
139
140
  });
141
+ if (!((_this$changes = _this.changes) != null && _this$changes.length)) {
142
+ _this.onChange();
143
+ }
140
144
  }
141
145
  };
142
146
  this.loaded = () => true;
@@ -147,8 +151,31 @@ class NinetailedPreviewPlugin extends NinetailedPlugin {
147
151
  return;
148
152
  }
149
153
  if (payload != null && payload.profile) {
150
- this.onProfileChange(payload.profile);
154
+ this.onProfileChange(payload.profile, payload.changes || []);
155
+ }
156
+ };
157
+ /**
158
+ * Implements the HasChangesModificationMiddleware interface
159
+ * Returns a middleware function that applies variable overwrites to changes
160
+ */
161
+ this.getChangesModificationMiddleware = () => {
162
+ if (!this.isActiveInstance || Object.keys(this.variableOverwrites).length === 0) {
163
+ return undefined;
151
164
  }
165
+ return ({
166
+ changes: inputChanges
167
+ }) => {
168
+ if (!inputChanges || inputChanges.length === 0) {
169
+ return {
170
+ changes: inputChanges
171
+ };
172
+ }
173
+
174
+ // Calculate and return overridden changes on demand instead of storing them
175
+ return {
176
+ changes: this.getEffectiveChanges(inputChanges)
177
+ };
178
+ };
152
179
  };
153
180
  this.getExperienceSelectionMiddleware = ({
154
181
  baseline,
@@ -169,7 +196,41 @@ class NinetailedPreviewPlugin extends NinetailedPlugin {
169
196
  variantIndex: 0
170
197
  };
171
198
  }
172
- const baselineComponent = experience.components.find(component => component.baseline.id === baseline.id);
199
+
200
+ // Handle entry replacements as before
201
+ const entryReplacementComponents = experience.components.filter(component => component.type === ComponentTypeEnum.EntryReplacement && 'id' in component.baseline);
202
+ const baselineComponent = entryReplacementComponents.find(component => component.baseline.id === baseline.id);
203
+
204
+ // Get the selected variant index
205
+ const variantIndex = this.pluginApi.experienceVariantIndexes[experience.id];
206
+
207
+ // Handle variable components for this experience (NEW CODE)
208
+ if (variantIndex !== undefined) {
209
+ // Process all variable components for this experience
210
+ const variableComponents = experience.components.filter(component => component.type === ComponentTypeEnum.InlineVariable);
211
+
212
+ // Set variable values based on the selected variant index
213
+ variableComponents.forEach(component => {
214
+ const key = component.key;
215
+ let value;
216
+ if (variantIndex === 0) {
217
+ value = component.baseline;
218
+ } else {
219
+ const variant = component.variants[variantIndex - 1];
220
+ value = variant && 'value' in variant ? variant.value : component.baseline;
221
+ }
222
+
223
+ // Set the variable in our changes system
224
+ this.setVariableValue({
225
+ experienceId: experience.id,
226
+ key,
227
+ value,
228
+ variantIndex
229
+ });
230
+ });
231
+ }
232
+
233
+ // Continue with entry replacement handling
173
234
  if (!baselineComponent) {
174
235
  return {
175
236
  experience,
@@ -178,7 +239,6 @@ class NinetailedPreviewPlugin extends NinetailedPlugin {
178
239
  };
179
240
  }
180
241
  const allVariants = [baseline, ...baselineComponent.variants];
181
- const variantIndex = this.pluginApi.experienceVariantIndexes[experience.id];
182
242
  if (allVariants.length <= variantIndex) {
183
243
  return {
184
244
  experience,
@@ -202,20 +262,47 @@ class NinetailedPreviewPlugin extends NinetailedPlugin {
202
262
  };
203
263
  };
204
264
  this.onChange = () => {
205
- var _window$ninetailed2;
206
265
  logger.debug('Ninetailed Preview Plugin onChange pluginApi:', this.pluginApi);
207
- Object.assign({}, window.ninetailed, {
208
- plugins: Object.assign({}, (_window$ninetailed2 = window.ninetailed) == null ? void 0 : _window$ninetailed2.plugins, {
209
- preview: this.windowApi
210
- })
211
- });
266
+ if (typeof window !== 'undefined') {
267
+ var _window$ninetailed2;
268
+ window.ninetailed = Object.assign({}, window.ninetailed, {
269
+ plugins: Object.assign({}, (_window$ninetailed2 = window.ninetailed) == null ? void 0 : _window$ninetailed2.plugins, {
270
+ preview: this.windowApi
271
+ })
272
+ });
273
+ }
212
274
  this.bridge.updateProps({
213
275
  props: this.pluginApi
214
276
  });
215
277
  this.onChangeEmitter.invokeListeners();
216
278
  };
217
- this.onProfileChange = profile => {
279
+ this.onProfileChange = (profile, changes) => {
218
280
  this.profile = profile;
281
+ logger.debug('Profile changed:', {
282
+ profile,
283
+ changes
284
+ });
285
+
286
+ // If changes are provided, update them
287
+ if (changes) {
288
+ this.onChangesChange(changes);
289
+ }
290
+ this.onChange();
291
+ };
292
+ /**
293
+ * Handles changes from the SDK and applies any variable overrides.
294
+ * This should be called whenever the original changes are updated.
295
+ */
296
+ this.onChangesChange = incomingChanges => {
297
+ if (!this.isActiveInstance) {
298
+ return;
299
+ }
300
+ logger.debug('Received changes:', incomingChanges);
301
+
302
+ // Store the original changes
303
+ this.changes = incomingChanges;
304
+
305
+ // Notify listeners and update UI
219
306
  this.onChange();
220
307
  };
221
308
  this.setCredentials = ({
@@ -340,23 +427,44 @@ class NinetailedPreviewPlugin extends NinetailedPlugin {
340
427
  }
341
428
  const experience = this.experiences.find(experience => experience.id === experienceId);
342
429
  if (!experience) {
343
- logger.warn(`You cannot active a variant for an unknown experience (id: ${experienceId})`);
430
+ logger.warn(`Cannot activate a variant for an unknown experience (id: ${experienceId})`);
344
431
  return;
345
432
  }
346
433
  if (experience.audience && !this.activeAudiences.some(id => {
347
434
  var _experience$audience4;
348
435
  return id === ((_experience$audience4 = experience.audience) == null ? void 0 : _experience$audience4.id);
349
436
  })) {
350
- logger.warn(`You cannot active a variant for an experience (id: ${experienceId}), which is not in the active audiences.`);
437
+ logger.warn(`Cannot activate a variant for an experience (id: ${experienceId}) which is not in the active audiences.`);
351
438
  return;
352
439
  }
353
440
  const isValidIndex = experience.components.map(component => component.variants.length + 1).every(length => length > variantIndex);
354
441
  if (!isValidIndex) {
355
442
  logger.warn(`You activated a variant at index ${variantIndex} for the experience (id: ${experienceId}). Not all components have that many variants, you may see the baseline for some.`);
356
443
  }
444
+
445
+ // Update the experience variant index
357
446
  this.experienceVariantIndexOverwrites = Object.assign({}, this.experienceVariantIndexOverwrites, {
358
447
  [experienceId]: variantIndex
359
448
  });
449
+
450
+ // Process all components to extract variable values
451
+ experience.components.forEach(component => {
452
+ if (component.type === ComponentTypeEnum.InlineVariable) {
453
+ var _component$variants$v, _component$variants;
454
+ const key = component.key;
455
+ const value = variantIndex === 0 ? component.baseline.value : (_component$variants$v = (_component$variants = component.variants[variantIndex - 1]) == null ? void 0 : _component$variants.value) != null ? _component$variants$v : component.baseline.value;
456
+
457
+ // Set the variable value
458
+ this.setVariableValue({
459
+ experienceId,
460
+ key,
461
+ value,
462
+ variantIndex
463
+ });
464
+ }
465
+ });
466
+
467
+ // Trigger change notification - this updates the middleware
360
468
  this.onChange();
361
469
  }
362
470
  resetExperience(experienceId) {
@@ -376,6 +484,45 @@ class NinetailedPreviewPlugin extends NinetailedPlugin {
376
484
  window.ninetailed.reset();
377
485
  }
378
486
  }
487
+ /**
488
+ * Sets a variable value override for preview
489
+ */
490
+ setVariableValue({
491
+ experienceId,
492
+ key,
493
+ value,
494
+ variantIndex
495
+ }) {
496
+ var _this$variableOverwri;
497
+ if (!this.isActiveInstance) {
498
+ return;
499
+ }
500
+ const overrideKey = `${experienceId}:${key}`;
501
+
502
+ // Only create new object if actually changing
503
+ if (((_this$variableOverwri = this.variableOverwrites[overrideKey]) == null ? void 0 : _this$variableOverwri.value) === value) {
504
+ return; // No change needed
505
+ }
506
+
507
+ const change = {
508
+ type: ChangeTypes.Variable,
509
+ key,
510
+ value,
511
+ meta: {
512
+ experienceId,
513
+ variantIndex
514
+ }
515
+ };
516
+
517
+ // Update variable overwrites
518
+ this.variableOverwrites = Object.assign({}, this.variableOverwrites, {
519
+ [overrideKey]: change
520
+ });
521
+
522
+ // Notify listeners
523
+ this.onChangeEmitter.invokeListeners();
524
+ this.onChange();
525
+ }
379
526
  openExperienceEditor(experience) {
380
527
  if (this.onOpenExperienceEditor && typeof this.onOpenExperienceEditor === 'function') return this.onOpenExperienceEditor(experience);
381
528
  }
@@ -388,7 +535,7 @@ class NinetailedPreviewPlugin extends NinetailedPlugin {
388
535
  get pluginApi() {
389
536
  var _this$profile;
390
537
  return {
391
- version: "7.12.1" ,
538
+ version: "7.13.0-beta.1" ,
392
539
  open: this.open.bind(this),
393
540
  close: this.close.bind(this),
394
541
  toggle: this.toggle.bind(this),
@@ -424,7 +571,9 @@ class NinetailedPreviewPlugin extends NinetailedPlugin {
424
571
  activeAudiences: this.activeAudiences,
425
572
  setExperienceVariant: this.setExperienceVariant.bind(this),
426
573
  resetExperience: this.resetExperience.bind(this),
427
- experienceVariantIndexes: Object.assign({}, this.experienceVariantIndexes, this.experienceVariantIndexOverwrites)
574
+ experienceVariantIndexes: Object.assign({}, this.experienceVariantIndexes, this.experienceVariantIndexOverwrites),
575
+ setVariableValue: this.setVariableValue.bind(this),
576
+ variableOverwrites: this.variableOverwrites
428
577
  };
429
578
  }
430
579
  isKnownAudience(id) {
@@ -471,6 +620,35 @@ class NinetailedPreviewPlugin extends NinetailedPlugin {
471
620
  audiences: this.activeAudiences
472
621
  }));
473
622
  }
623
+
624
+ /**
625
+ * Get the override key for a variable
626
+ */
627
+ getOverrideKey(experienceId, key) {
628
+ return `${experienceId}:${key}`;
629
+ }
630
+
631
+ /**
632
+ * Get effective changes by applying overrides - compute on demand
633
+ */
634
+ getEffectiveChanges(inputChanges = this.changes) {
635
+ if (!inputChanges || Object.keys(this.variableOverwrites).length === 0) {
636
+ return inputChanges || [];
637
+ }
638
+
639
+ // Filter out changes that we're overriding
640
+ const filteredChanges = inputChanges.filter(change => {
641
+ var _change$meta;
642
+ if (change.type !== ChangeTypes.Variable) return true;
643
+ const changeKey = (_change$meta = change.meta) != null && _change$meta.experienceId ? this.getOverrideKey(change.meta.experienceId, change.key) : change.key;
644
+ return !this.variableOverwrites[changeKey];
645
+ });
646
+ const effectiveChanges = [...filteredChanges, ...Object.values(this.variableOverwrites)];
647
+ logger.debug('Overridden changes after applying override:', effectiveChanges);
648
+
649
+ // Add our overrides to create the final result
650
+ return effectiveChanges;
651
+ }
474
652
  }
475
653
 
476
654
  if (typeof window === 'object' && !('process' in window)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ninetailed/experience.js-plugin-preview",
3
- "version": "7.12.1",
3
+ "version": "7.13.0-beta.1",
4
4
  "description": "Ninetailed SDK plugin for preview",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -15,10 +15,10 @@
15
15
  "a/b testing"
16
16
  ],
17
17
  "dependencies": {
18
- "@ninetailed/experience.js-shared": "7.12.1",
19
- "@ninetailed/experience.js": "7.12.1",
20
- "@ninetailed/experience.js-preview-bridge": "7.12.1",
21
- "@ninetailed/experience.js-plugin-analytics": "7.12.1"
18
+ "@ninetailed/experience.js-shared": "7.13.0-beta.1",
19
+ "@ninetailed/experience.js": "7.13.0-beta.1",
20
+ "@ninetailed/experience.js-preview-bridge": "7.13.0-beta.1",
21
+ "@ninetailed/experience.js-plugin-analytics": "7.13.0-beta.1"
22
22
  },
23
23
  "module": "./index.esm.js",
24
24
  "main": "./index.cjs.js"
@@ -1,5 +1,5 @@
1
- import { Reference } from '@ninetailed/experience.js-shared';
2
- import { ExperienceConfiguration, PROFILE_CHANGE, HasExperienceSelectionMiddleware, BuildExperienceSelectionMiddleware, type ProfileChangedPayload, type InterestedInProfileChange } from '@ninetailed/experience.js';
1
+ import { Reference, AllowedVariableType } from '@ninetailed/experience.js-shared';
2
+ import { ExperienceConfiguration, PROFILE_CHANGE, HasExperienceSelectionMiddleware, BuildExperienceSelectionMiddleware, HasChangesModificationMiddleware, BuildChangesModificationMiddleware, type ProfileChangedPayload, type InterestedInProfileChange } from '@ninetailed/experience.js';
3
3
  import type { ExposedAudienceDefinition } from '@ninetailed/experience.js-preview-bridge';
4
4
  import { type EventHandler, NinetailedPlugin } from '@ninetailed/experience.js-plugin-analytics';
5
5
  export declare const NINETAILED_PREVIEW_EVENTS: {
@@ -18,7 +18,7 @@ type NinetailedPreviewPluginOptions = {
18
18
  };
19
19
  };
20
20
  };
21
- export declare class NinetailedPreviewPlugin extends NinetailedPlugin implements HasExperienceSelectionMiddleware<Reference, Reference>, InterestedInProfileChange {
21
+ export declare class NinetailedPreviewPlugin extends NinetailedPlugin implements HasExperienceSelectionMiddleware<Reference, Reference>, HasChangesModificationMiddleware, InterestedInProfileChange {
22
22
  private readonly options;
23
23
  name: string;
24
24
  private isOpen;
@@ -26,7 +26,9 @@ export declare class NinetailedPreviewPlugin extends NinetailedPlugin implements
26
26
  private readonly audienceDefinitions;
27
27
  private audienceOverwrites;
28
28
  private experienceVariantIndexOverwrites;
29
+ private variableOverwrites;
29
30
  private profile;
31
+ private changes;
30
32
  private container;
31
33
  private bridge;
32
34
  /**
@@ -54,6 +56,20 @@ export declare class NinetailedPreviewPlugin extends NinetailedPlugin implements
54
56
  }): void;
55
57
  resetExperience(experienceId: string): void;
56
58
  reset(): void;
59
+ /**
60
+ * Implements the HasChangesModificationMiddleware interface
61
+ * Returns a middleware function that applies variable overwrites to changes
62
+ */
63
+ getChangesModificationMiddleware: BuildChangesModificationMiddleware;
64
+ /**
65
+ * Sets a variable value override for preview
66
+ */
67
+ setVariableValue({ experienceId, key, value, variantIndex, }: {
68
+ experienceId: string;
69
+ key: string;
70
+ value: AllowedVariableType;
71
+ variantIndex: number;
72
+ }): void;
57
73
  getExperienceSelectionMiddleware: BuildExperienceSelectionMiddleware<Reference, Reference>;
58
74
  openExperienceEditor(experience: ExperienceConfiguration): void;
59
75
  openExperienceAnalytics(experience: ExperienceConfiguration): void;
@@ -66,8 +82,21 @@ export declare class NinetailedPreviewPlugin extends NinetailedPlugin implements
66
82
  private calculateExperienceVariantIndexes;
67
83
  private get apiExperienceVariantIndexes();
68
84
  private get experienceVariantIndexes();
85
+ /**
86
+ * Get the override key for a variable
87
+ */
88
+ private getOverrideKey;
89
+ /**
90
+ * Get effective changes by applying overrides - compute on demand
91
+ */
92
+ private getEffectiveChanges;
69
93
  private onChange;
70
94
  private onProfileChange;
95
+ /**
96
+ * Handles changes from the SDK and applies any variable overrides.
97
+ * This should be called whenever the original changes are updated.
98
+ */
99
+ private onChangesChange;
71
100
  private setCredentials;
72
101
  }
73
102
  export {};