saltfish 0.3.38 → 0.3.41

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.
@@ -2845,6 +2845,9 @@ class PlaylistOrchestrator {
2845
2845
  if (updatedStore.manifest.cursorColor) {
2846
2846
  this.managers.cursorManager.setColor(updatedStore.manifest.cursorColor);
2847
2847
  }
2848
+ if (updatedStore.manifest.cursorLabel) {
2849
+ this.managers.cursorManager.setLabel(updatedStore.manifest.cursorLabel);
2850
+ }
2848
2851
  const isTriggeredAutomatically = finalOptions._triggeredByTriggerManager === true;
2849
2852
  const isFirstStep = updatedStore.currentStepId === ((_e = updatedStore.manifest.steps[0]) == null ? void 0 : _e.id);
2850
2853
  if (updatedStore.manifest.idleMode && isFirstStep) {
@@ -6713,6 +6716,9 @@ class CursorManager {
6713
6716
  __publicField(this, "isSelectionMode", false);
6714
6717
  __publicField(this, "selectionPadding", 4);
6715
6718
  // Default padding in pixels
6719
+ // Label element for cursor name/text
6720
+ __publicField(this, "labelElement", null);
6721
+ __publicField(this, "labelText", null);
6716
6722
  // Selection drag properties
6717
6723
  __publicField(this, "dragStartX", null);
6718
6724
  __publicField(this, "dragStartY", null);
@@ -7107,9 +7113,12 @@ class CursorManager {
7107
7113
  <path d="M3.5 3.5L10.5 20.5L13.3 13.3L20.5 10.5L3.5 3.5Z" fill="#ff7614" stroke="white" stroke-width="0.7" stroke-linejoin="round" stroke-linecap="round" filter="url(#cursor-shadow)" />
7108
7114
  </svg>
7109
7115
  `;
7116
+ this.labelElement = document.createElement("div");
7117
+ this.labelElement.className = "sf-cursor-label";
7110
7118
  this.selectionElement = document.createElement("div");
7111
7119
  this.selectionElement.className = "sf-selection";
7112
7120
  document.body.appendChild(this.cursor);
7121
+ document.body.appendChild(this.labelElement);
7113
7122
  document.body.appendChild(this.selectionElement);
7114
7123
  this.flashlightOverlay = document.createElement("div");
7115
7124
  this.flashlightOverlay.className = "sf-flashlight-overlay";
@@ -7196,6 +7205,35 @@ class CursorManager {
7196
7205
  .sf-flashlight-overlay--visible {
7197
7206
  display: block;
7198
7207
  }
7208
+
7209
+ .sf-cursor-label {
7210
+ position: fixed;
7211
+ top: 0;
7212
+ left: 0;
7213
+ z-index: 9999999;
7214
+ pointer-events: none;
7215
+ display: none;
7216
+ will-change: transform;
7217
+ transform: var(--sf-cursor-label-transform, translate(0, 0));
7218
+ opacity: var(--sf-cursor-opacity, 1);
7219
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
7220
+ font-size: 13px;
7221
+ font-weight: 500;
7222
+ color: var(--sf-cursor-label-text, #fff);
7223
+ background: var(--sf-cursor-label-bg, #ff7614);
7224
+ padding: 4px 10px;
7225
+ border-radius: 12px;
7226
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
7227
+ white-space: nowrap;
7228
+ margin: 0;
7229
+ border: 0;
7230
+ box-sizing: border-box;
7231
+ line-height: 1.4;
7232
+ }
7233
+
7234
+ .sf-cursor-label--visible {
7235
+ display: block;
7236
+ }
7199
7237
  `;
7200
7238
  try {
7201
7239
  const sheet = new CSSStyleSheet();
@@ -7305,6 +7343,10 @@ class CursorManager {
7305
7343
  this.cursor.classList.add("sf-cursor--visible");
7306
7344
  this.cursor.style.setProperty("--sf-cursor-transform", `translate(${x}px, ${y}px) translate(-50%, -50%)`);
7307
7345
  }
7346
+ if (this.labelElement && this.labelText) {
7347
+ this.labelElement.classList.add("sf-cursor-label--visible");
7348
+ this.labelElement.style.setProperty("--sf-cursor-label-transform", `translate(${x + 24}px, ${y + 8}px)`);
7349
+ }
7308
7350
  if (this.flashlightOverlay) {
7309
7351
  this.flashlightOverlay.classList.add("sf-flashlight-overlay--visible");
7310
7352
  this.flashlightOverlay.style.setProperty("--sf-flashlight-bg", `radial-gradient(circle 150px at ${x}px ${y}px, transparent 0%, rgba(0, 0, 0, 0.4) 100%)`);
@@ -7317,6 +7359,9 @@ class CursorManager {
7317
7359
  if (this.cursor) {
7318
7360
  this.cursor.classList.remove("sf-cursor--visible");
7319
7361
  }
7362
+ if (this.labelElement) {
7363
+ this.labelElement.classList.remove("sf-cursor-label--visible");
7364
+ }
7320
7365
  if (this.selectionElement) {
7321
7366
  this.selectionElement.classList.remove("sf-selection--visible");
7322
7367
  }
@@ -7902,6 +7947,11 @@ class CursorManager {
7902
7947
  this.cursor.remove();
7903
7948
  this.cursor = null;
7904
7949
  }
7950
+ if (this.labelElement && this.labelElement.parentNode) {
7951
+ this.labelElement.remove();
7952
+ this.labelElement = null;
7953
+ }
7954
+ this.labelText = null;
7905
7955
  if (this.selectionElement && this.selectionElement.parentNode) {
7906
7956
  this.selectionElement.remove();
7907
7957
  this.selectionElement = null;
@@ -7922,7 +7972,7 @@ class CursorManager {
7922
7972
  document.documentElement.style.removeProperty("--sf-cursor-y");
7923
7973
  }
7924
7974
  /**
7925
- * Sets the color of the cursor SVG and selection highlight
7975
+ * Sets the color of the cursor SVG, selection highlight, and label
7926
7976
  * @param color - The color to set (hex, rgb, etc)
7927
7977
  */
7928
7978
  setColor(color) {
@@ -7946,6 +7996,46 @@ class CursorManager {
7946
7996
  const rgbaBackground = this.colorToRgba(color, 0);
7947
7997
  this.selectionElement.style.setProperty("--sf-selection-bg-color", rgbaBackground);
7948
7998
  }
7999
+ if (this.labelElement) {
8000
+ this.labelElement.style.setProperty("--sf-cursor-label-bg", color);
8001
+ const textColor = this.getContrastingTextColor(color);
8002
+ this.labelElement.style.setProperty("--sf-cursor-label-text", textColor);
8003
+ }
8004
+ }
8005
+ /**
8006
+ * Calculates whether white or black text provides better contrast against a background color
8007
+ * Uses relative luminance calculation for accessibility
8008
+ * @param bgColor - The background color (hex, rgb, etc)
8009
+ * @returns '#fff' for dark backgrounds, '#333' for light backgrounds
8010
+ */
8011
+ getContrastingTextColor(bgColor) {
8012
+ const rgb = this.parseColorToRgb(bgColor);
8013
+ if (!rgb) {
8014
+ return "#fff";
8015
+ }
8016
+ const luminance = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1e3;
8017
+ return luminance > 150 ? "#333" : "#fff";
8018
+ }
8019
+ /**
8020
+ * Parses a color string to RGB values
8021
+ * @param color - The color to parse (hex, rgb, rgba)
8022
+ * @returns RGB object or null if parsing fails
8023
+ */
8024
+ parseColorToRgb(color) {
8025
+ const tempElement = document.createElement("div");
8026
+ tempElement.style.color = color;
8027
+ document.body.appendChild(tempElement);
8028
+ const computedColor = window.getComputedStyle(tempElement).color;
8029
+ document.body.removeChild(tempElement);
8030
+ const rgbMatch = computedColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
8031
+ if (rgbMatch) {
8032
+ return {
8033
+ r: parseInt(rgbMatch[1], 10),
8034
+ g: parseInt(rgbMatch[2], 10),
8035
+ b: parseInt(rgbMatch[3], 10)
8036
+ };
8037
+ }
8038
+ return null;
7949
8039
  }
7950
8040
  /**
7951
8041
  * Converts a color string (hex, rgb, rgba) to rgba format with specified opacity
@@ -7968,6 +8058,25 @@ class CursorManager {
7968
8058
  }
7969
8059
  return color;
7970
8060
  }
8061
+ /**
8062
+ * Sets the label text to display beside the cursor
8063
+ * @param label - The label text to display (e.g., avatar name), or null to hide
8064
+ */
8065
+ setLabel(label) {
8066
+ var _a;
8067
+ this.labelText = label;
8068
+ if (this.labelElement) {
8069
+ if (label) {
8070
+ this.labelElement.textContent = label;
8071
+ if ((_a = this.cursor) == null ? void 0 : _a.classList.contains("sf-cursor--visible")) {
8072
+ this.labelElement.classList.add("sf-cursor-label--visible");
8073
+ }
8074
+ } else {
8075
+ this.labelElement.textContent = "";
8076
+ this.labelElement.classList.remove("sf-cursor-label--visible");
8077
+ }
8078
+ }
8079
+ }
7971
8080
  }
7972
8081
  class InteractionManager {
7973
8082
  constructor() {
@@ -9614,6 +9723,9 @@ class TriggerManager {
9614
9723
  const elementVisibleCondition = this.evaluateElementVisibleCondition(triggers.elementVisible);
9615
9724
  conditions.push(elementVisibleCondition);
9616
9725
  log(`TriggerManager: ElementVisible condition for playlist ${playlistId}: ${elementVisibleCondition} (selector: ${triggers.elementVisible})`);
9726
+ const userAttributesCondition = this.evaluateUserAttributesCondition(triggers.userAttributes);
9727
+ conditions.push(userAttributesCondition);
9728
+ log(`TriggerManager: UserAttributes condition for playlist ${playlistId}: ${userAttributesCondition} (conditions: ${JSON.stringify(triggers.userAttributes)})`);
9617
9729
  const shouldTrigger = this.applyOperators(conditions, triggers.operators);
9618
9730
  log(`TriggerManager: Final evaluation for playlist ${playlistId}: ${shouldTrigger} (operator: ${triggers.operators.join(", ")})`);
9619
9731
  if (shouldTrigger) {
@@ -9747,6 +9859,98 @@ class TriggerManager {
9747
9859
  const isVisible = this.visibleElements.has(selector);
9748
9860
  return isVisible;
9749
9861
  }
9862
+ /**
9863
+ * Evaluates user attribute conditions for a playlist
9864
+ * All conditions must be met (AND logic)
9865
+ * @param conditions - Array of user attribute conditions to evaluate
9866
+ */
9867
+ evaluateUserAttributesCondition(conditions) {
9868
+ if (!conditions || conditions.length === 0) {
9869
+ return true;
9870
+ }
9871
+ const store = getSaltfishStore();
9872
+ const user = store.user;
9873
+ if (!user) {
9874
+ return false;
9875
+ }
9876
+ for (const condition of conditions) {
9877
+ const { attributeKey, attributeType, operator, value: expectedValue } = condition;
9878
+ const userValue = user[attributeKey];
9879
+ if (userValue === void 0 || userValue === null) {
9880
+ return false;
9881
+ }
9882
+ const result = this.compareValues(userValue, expectedValue, attributeType, operator);
9883
+ if (!result) {
9884
+ return false;
9885
+ }
9886
+ }
9887
+ return true;
9888
+ }
9889
+ /**
9890
+ * Compares two values based on attribute type and operator
9891
+ * @param userValue - The user's actual value
9892
+ * @param expectedValue - The expected value from trigger condition (always a string)
9893
+ * @param attributeType - The data type for comparison
9894
+ * @param operator - The comparison operator
9895
+ */
9896
+ compareValues(userValue, expectedValue, attributeType, operator) {
9897
+ try {
9898
+ switch (attributeType) {
9899
+ case "string": {
9900
+ const userStr = String(userValue);
9901
+ const expectedStr = expectedValue;
9902
+ return this.applyOperator(userStr, expectedStr, operator);
9903
+ }
9904
+ case "boolean": {
9905
+ const userBool = typeof userValue === "boolean" ? userValue : String(userValue).toLowerCase() === "true";
9906
+ const expectedBool = expectedValue.toLowerCase() === "true";
9907
+ if (operator === "equals") return userBool === expectedBool;
9908
+ if (operator === "notEquals") return userBool !== expectedBool;
9909
+ return false;
9910
+ }
9911
+ case "int": {
9912
+ const userNum = typeof userValue === "number" ? userValue : parseFloat(String(userValue));
9913
+ const expectedNum = parseFloat(expectedValue);
9914
+ if (isNaN(userNum) || isNaN(expectedNum)) {
9915
+ log(`TriggerManager: Invalid number comparison - userValue: ${userValue}, expected: ${expectedValue}`);
9916
+ return false;
9917
+ }
9918
+ return this.applyOperator(userNum, expectedNum, operator);
9919
+ }
9920
+ case "date": {
9921
+ const userDate = userValue instanceof Date ? userValue : new Date(String(userValue));
9922
+ const expectedDate = new Date(expectedValue);
9923
+ if (isNaN(userDate.getTime()) || isNaN(expectedDate.getTime())) {
9924
+ log(`TriggerManager: Invalid date comparison - userValue: ${userValue}, expected: ${expectedValue}`);
9925
+ return false;
9926
+ }
9927
+ return this.applyOperator(userDate.getTime(), expectedDate.getTime(), operator);
9928
+ }
9929
+ default:
9930
+ log(`TriggerManager: Unknown attribute type: ${attributeType}`);
9931
+ return false;
9932
+ }
9933
+ } catch (error2) {
9934
+ return false;
9935
+ }
9936
+ }
9937
+ /**
9938
+ * Applies the comparison operator to two values
9939
+ */
9940
+ applyOperator(userValue, expectedValue, operator) {
9941
+ switch (operator) {
9942
+ case "equals":
9943
+ return userValue === expectedValue;
9944
+ case "notEquals":
9945
+ return userValue !== expectedValue;
9946
+ case "greaterThan":
9947
+ return userValue > expectedValue;
9948
+ case "lessThan":
9949
+ return userValue < expectedValue;
9950
+ default:
9951
+ return false;
9952
+ }
9953
+ }
9750
9954
  /**
9751
9955
  * Sets up click event listeners for all playlists with elementClicked triggers
9752
9956
  */
@@ -11787,7 +11991,7 @@ const SaltfishPlayer$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.de
11787
11991
  __proto__: null,
11788
11992
  SaltfishPlayer
11789
11993
  }, Symbol.toStringTag, { value: "Module" }));
11790
- const version = "0.3.38";
11994
+ const version = "0.3.41";
11791
11995
  const packageJson = {
11792
11996
  version
11793
11997
  };