@einblick/sdk 0.5.5 → 0.6.2

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.
@@ -1,5 +1,28 @@
1
1
  import { EINBLICK_AUTH_SOURCE, EINBLICK_BRIDGE_SOURCE, EINBLICK_EDITOR_SOURCE, EINBLICK_PARENT_SOURCE, formatValueForText, isMediaEditableBinding, isEditSessionExpired, readBindingFromElement, resolveAppOrigin, serializeBindingValue, shouldUseInlineEditor, } from './shared.js';
2
2
  import { getEinblickSdkMessages, resolveEinblickSdkLocale, } from './i18n.js';
3
+ function normalizeResourceHints(hints) {
4
+ const normalized = [];
5
+ const seen = new Set();
6
+ for (const hint of hints ?? []) {
7
+ const resourceSlug = hint.resourceSlug?.trim();
8
+ if (!resourceSlug)
9
+ continue;
10
+ const sourceType = hint.sourceType ?? 'cms';
11
+ const key = `${sourceType}:${resourceSlug}`;
12
+ if (seen.has(key))
13
+ continue;
14
+ seen.add(key);
15
+ normalized.push({
16
+ sourceType,
17
+ resourceSlug,
18
+ label: hint.label?.trim() || undefined,
19
+ mode: hint.mode,
20
+ recordIds: hint.recordIds?.filter((id) => id.trim().length > 0),
21
+ showInBottomBar: hint.showInBottomBar,
22
+ });
23
+ }
24
+ return normalized;
25
+ }
3
26
  const RUNTIME_STYLE_ID = 'einblick-edit-runtime-styles';
4
27
  const EINBLICK_ACCENT_RGB = '250, 105, 62';
5
28
  const EINBLICK_ACCENT_COLOR = `rgb(${EINBLICK_ACCENT_RGB})`;
@@ -60,6 +83,9 @@ function isTextInputEvent(event) {
60
83
  event.key === 'Backspace' ||
61
84
  event.key === 'Delete');
62
85
  }
86
+ function isRecordEditableBinding(binding) {
87
+ return Boolean(binding?.recordId);
88
+ }
63
89
  function fieldTypeAllowsMultiline(binding) {
64
90
  return binding.fieldType === 'text' || binding.fieldType === 'markdown';
65
91
  }
@@ -87,43 +113,41 @@ function injectRuntimeStyles() {
87
113
  scroll-margin-top: 96px;
88
114
  }
89
115
 
90
- [data-einblick-edit-outline],
91
- [data-einblick-edit-field-outline] {
92
- position: fixed;
93
- z-index: 2147483643;
94
- box-sizing: border-box;
95
- pointer-events: none;
96
- border-radius: var(--einblick-overlay-radius, 0.35rem);
116
+ [data-einblick-editable="true"][data-einblick-hover-active="true"] {
117
+ outline: 1px solid ${EINBLICK_ACCENT_BORDER};
118
+ outline-offset: -1px;
119
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.72);
97
120
  }
98
121
 
99
- [data-einblick-edit-outline] {
100
- border: 1px solid ${EINBLICK_ACCENT_BORDER};
101
- box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.7);
122
+ [data-einblick-editable="true"][data-einblick-field-hover-active="true"] {
123
+ outline: 1px dashed rgba(${EINBLICK_ACCENT_RGB}, 0.78);
124
+ outline-offset: -1px;
102
125
  }
103
126
 
104
- [data-einblick-edit-field-outline] {
105
- border: 1px dashed rgba(${EINBLICK_ACCENT_RGB}, 0.78);
106
- background: rgba(${EINBLICK_ACCENT_RGB}, 0.04);
127
+ [data-einblick-editable="true"][data-einblick-inline-editing="true"] {
128
+ outline: 2px solid ${EINBLICK_ACCENT_COLOR};
129
+ outline-offset: -2px;
107
130
  }
108
131
 
109
132
  [data-einblick-edit-controls] {
110
- position: fixed;
133
+ position: absolute;
111
134
  z-index: 2147483644;
112
135
  display: inline-flex;
113
136
  align-items: center;
114
- gap: 0.45rem;
137
+ gap: 0;
115
138
  }
116
139
 
117
140
  [data-einblick-edit-button],
118
141
  [data-einblick-create-button],
119
142
  [data-einblick-field-button] {
120
143
  border: 1px solid ${EINBLICK_ACCENT_COLOR};
121
- border-radius: 999px;
122
- padding: 0.3rem 0.7rem;
144
+ border-radius: 0;
145
+ min-height: 2rem;
146
+ padding: 0 0.72rem;
123
147
  font: 600 12px/1 system-ui, sans-serif;
124
- letter-spacing: 0.01em;
148
+ letter-spacing: 0;
125
149
  cursor: pointer;
126
- box-shadow: 0 8px 20px rgba(15, 23, 42, 0.16);
150
+ box-shadow: 0 8px 18px rgba(15, 23, 42, 0.16);
127
151
  }
128
152
 
129
153
  [data-einblick-edit-button],
@@ -133,7 +157,6 @@ function injectRuntimeStyles() {
133
157
  }
134
158
 
135
159
  [data-einblick-edit-button] {
136
- padding: 0.42rem 0.85rem;
137
160
  font-size: 13px;
138
161
  }
139
162
 
@@ -151,19 +174,19 @@ function injectRuntimeStyles() {
151
174
  }
152
175
 
153
176
  [data-einblick-field-overlays] {
154
- position: fixed;
155
- inset: 0;
177
+ position: absolute;
178
+ left: 0;
179
+ top: 0;
156
180
  z-index: 2147483644;
157
181
  pointer-events: none;
158
182
  }
159
183
 
160
184
  [data-einblick-field-button] {
161
- position: fixed;
185
+ position: absolute;
162
186
  z-index: 2147483644;
163
187
  pointer-events: auto;
164
- padding: 0.24rem 0.58rem;
165
188
  font-size: 11px;
166
- box-shadow: 0 8px 18px rgba(15, 23, 42, 0.12);
189
+ min-height: 1.65rem;
167
190
  }
168
191
 
169
192
  [data-einblick-brand-badge] {
@@ -394,6 +417,7 @@ class EinblickEditRuntime {
394
417
  callbacks;
395
418
  chrome;
396
419
  reserveBottomSpace;
420
+ resourceHints;
397
421
  locale;
398
422
  messages;
399
423
  bridgeNonce = createRandomId();
@@ -407,7 +431,6 @@ class EinblickEditRuntime {
407
431
  bridgeFrame = null;
408
432
  bridgePingInterval = null;
409
433
  activeElement = null;
410
- activeOutline = null;
411
434
  hoverButtonContainer = null;
412
435
  hoverButton = null;
413
436
  hoverCreateButton = null;
@@ -422,7 +445,7 @@ class EinblickEditRuntime {
422
445
  brandPopoverLogoutButton = null;
423
446
  bottomBarHost = null;
424
447
  bottomBarSiteName = null;
425
- bottomBarCmsButton = null;
448
+ bottomBarResourceList = null;
426
449
  bottomBarMoreButton = null;
427
450
  bottomBarMoreMenu = null;
428
451
  bottomBarOpenAppButton = null;
@@ -444,6 +467,7 @@ class EinblickEditRuntime {
444
467
  navigatorFrame = null;
445
468
  navigatorNonce = null;
446
469
  navigatorOpen = false;
470
+ navigatorFilter = null;
447
471
  pendingInlineSaves = new Map();
448
472
  pendingInlineMediaSaves = new Map();
449
473
  pendingLogin = null;
@@ -454,6 +478,7 @@ class EinblickEditRuntime {
454
478
  this.chrome = options.chrome ?? 'bar';
455
479
  this.reserveBottomSpace =
456
480
  options.reserveBottomSpace ?? this.chrome === 'bar';
481
+ this.resourceHints = normalizeResourceHints(options.resourceHints);
457
482
  this.locale = resolveEinblickSdkLocale(options.locale);
458
483
  this.messages = getEinblickSdkMessages(this.locale);
459
484
  this.session =
@@ -476,6 +501,10 @@ class EinblickEditRuntime {
476
501
  this.messages = getEinblickSdkMessages(nextLocale);
477
502
  this.refreshLocalizedUi();
478
503
  }
504
+ setResourceHints(hints) {
505
+ this.resourceHints = normalizeResourceHints(hints);
506
+ this.updateBottomBarResources();
507
+ }
479
508
  start() {
480
509
  if (this.started ||
481
510
  typeof window === 'undefined' ||
@@ -508,6 +537,7 @@ class EinblickEditRuntime {
508
537
  if (this.currentInlineTarget && !this.currentInlineTarget.isConnected) {
509
538
  this.closeInlineEditor();
510
539
  }
540
+ this.updateBottomBarResources();
511
541
  });
512
542
  this.mutationObserver.observe(document.body, {
513
543
  childList: true,
@@ -539,8 +569,6 @@ class EinblickEditRuntime {
539
569
  this.bridgeFrame?.remove();
540
570
  this.bridgeFrame = null;
541
571
  this.setActiveElement(null);
542
- this.activeOutline?.remove();
543
- this.activeOutline = null;
544
572
  this.hoverButtonContainer?.remove();
545
573
  this.hoverButtonContainer = null;
546
574
  this.hoverButton?.remove();
@@ -560,7 +588,7 @@ class EinblickEditRuntime {
560
588
  this.bottomBarHost?.remove();
561
589
  this.bottomBarHost = null;
562
590
  this.bottomBarSiteName = null;
563
- this.bottomBarCmsButton = null;
591
+ this.bottomBarResourceList = null;
564
592
  this.bottomBarMoreButton = null;
565
593
  this.bottomBarMoreMenu = null;
566
594
  this.bottomBarOpenAppButton = null;
@@ -689,8 +717,6 @@ class EinblickEditRuntime {
689
717
  };
690
718
  handleViewportChange = () => {
691
719
  if (this.currentInlineTarget?.isConnected) {
692
- this.ensureActiveOutline();
693
- this.positionOutline(this.activeOutline, this.currentInlineTarget);
694
720
  return;
695
721
  }
696
722
  if (this.activeElement && !this.activeElement.isConnected) {
@@ -743,7 +769,8 @@ class EinblickEditRuntime {
743
769
  return;
744
770
  }
745
771
  const binding = readBindingFromElement(this.activeElement);
746
- if (!binding) {
772
+ if (!isRecordEditableBinding(binding) ||
773
+ !this.shouldShowEditButton(this.activeElement, binding)) {
747
774
  return;
748
775
  }
749
776
  if (binding.fieldKey && isMediaEditableBinding(binding)) {
@@ -942,6 +969,7 @@ class EinblickEditRuntime {
942
969
  collectionId: message.collectionId,
943
970
  intent: message.intent,
944
971
  recordId: message.recordId,
972
+ sourceType: message.sourceType,
945
973
  resourceSlug: message.resourceSlug,
946
974
  });
947
975
  return;
@@ -950,6 +978,7 @@ class EinblickEditRuntime {
950
978
  if (message.resourceSlug) {
951
979
  void this.callbacks.onSave?.({
952
980
  binding: {
981
+ sourceType: message.sourceType ?? 'cms',
953
982
  resourceSlug: message.resourceSlug,
954
983
  recordId: message.recordId ?? '',
955
984
  resourceMode: 'collection',
@@ -957,6 +986,7 @@ class EinblickEditRuntime {
957
986
  },
958
987
  value: {
959
988
  action: message.action,
989
+ sourceType: message.sourceType ?? 'cms',
960
990
  recordId: message.recordId,
961
991
  collectionId: message.collectionId,
962
992
  },
@@ -986,15 +1016,19 @@ class EinblickEditRuntime {
986
1016
  });
987
1017
  }
988
1018
  else if (this.editorBinding) {
989
- const nextBinding = {
990
- ...this.editorBinding,
991
- recordId: message.recordId ?? this.editorBinding.recordId,
992
- };
993
- void this.callbacks.onSave?.({
994
- binding: nextBinding,
995
- value: message.fields,
996
- source: 'drawer',
997
- });
1019
+ const nextRecordId = message.recordId ?? this.editorBinding.recordId;
1020
+ if (nextRecordId) {
1021
+ const nextBinding = {
1022
+ ...this.editorBinding,
1023
+ sourceType: message.sourceType ?? this.editorBinding.sourceType ?? 'cms',
1024
+ recordId: nextRecordId,
1025
+ };
1026
+ void this.callbacks.onSave?.({
1027
+ binding: nextBinding,
1028
+ value: message.fields,
1029
+ source: 'drawer',
1030
+ });
1031
+ }
998
1032
  }
999
1033
  this.closeDrawer();
1000
1034
  return;
@@ -1003,6 +1037,7 @@ class EinblickEditRuntime {
1003
1037
  if (this.editorBinding) {
1004
1038
  const nextBinding = {
1005
1039
  ...this.editorBinding,
1040
+ sourceType: message.sourceType ?? this.editorBinding.sourceType ?? 'cms',
1006
1041
  resourceSlug: message.resourceSlug ?? this.editorBinding.resourceSlug,
1007
1042
  recordId: message.recordId,
1008
1043
  };
@@ -1190,8 +1225,6 @@ class EinblickEditRuntime {
1190
1225
  height: ${CONTROL_BAR_HEIGHT}px;
1191
1226
  padding: 0 0.75rem;
1192
1227
  background: ${EINBLICK_ACCENT_COLOR};
1193
- border-top: 1px solid rgba(255, 255, 255, 0.24);
1194
- box-shadow: 0 -8px 24px rgba(15, 23, 42, 0.18);
1195
1228
  }
1196
1229
 
1197
1230
  .brand,
@@ -1221,6 +1254,25 @@ class EinblickEditRuntime {
1221
1254
  font: 700 0.84rem/1.1 system-ui, sans-serif;
1222
1255
  }
1223
1256
 
1257
+ .resources {
1258
+ display: inline-flex;
1259
+ align-items: center;
1260
+ gap: 0.35rem;
1261
+ min-width: 0;
1262
+ max-width: min(48rem, 54vw);
1263
+ overflow-x: auto;
1264
+ scrollbar-width: none;
1265
+ }
1266
+
1267
+ .resources::-webkit-scrollbar {
1268
+ display: none;
1269
+ }
1270
+
1271
+ .resource-button {
1272
+ flex: 0 0 auto;
1273
+ max-width: 12rem;
1274
+ }
1275
+
1224
1276
  button {
1225
1277
  appearance: none;
1226
1278
  display: inline-flex;
@@ -1230,10 +1282,10 @@ class EinblickEditRuntime {
1230
1282
  min-width: 0;
1231
1283
  height: 2rem;
1232
1284
  margin: 0;
1233
- border: 1px solid rgba(255, 255, 255, 0.36);
1285
+ border: 0;
1234
1286
  border-radius: 0.35rem;
1235
- background: rgba(255, 255, 255, 0.13);
1236
- color: rgb(255, 255, 255);
1287
+ background: #ffffff;
1288
+ color: #fa693e;
1237
1289
  padding: 0 0.7rem;
1238
1290
  font: 700 0.78rem/1 system-ui, sans-serif;
1239
1291
  letter-spacing: 0;
@@ -1252,11 +1304,11 @@ class EinblickEditRuntime {
1252
1304
 
1253
1305
  button:hover,
1254
1306
  button[aria-expanded="true"] {
1255
- background: rgba(255, 255, 255, 0.23);
1307
+ opacity: 0.8;
1256
1308
  }
1257
1309
 
1258
1310
  button:focus-visible {
1259
- outline: 2px solid rgba(255, 255, 255, 0.72);
1311
+ outline: 2px solid #ffffff;
1260
1312
  outline-offset: 2px;
1261
1313
  }
1262
1314
 
@@ -1342,14 +1394,8 @@ class EinblickEditRuntime {
1342
1394
  brand.append(mark, siteName);
1343
1395
  const actions = document.createElement('div');
1344
1396
  actions.className = 'actions';
1345
- const cmsButton = document.createElement('button');
1346
- cmsButton.type = 'button';
1347
- cmsButton.addEventListener('click', (event) => {
1348
- event.preventDefault();
1349
- event.stopPropagation();
1350
- this.setBottomBarMenuOpen(false);
1351
- this.setNavigatorOpen(!this.isNavigatorOpen());
1352
- });
1397
+ const resourceList = document.createElement('div');
1398
+ resourceList.className = 'resources';
1353
1399
  const moreButton = document.createElement('button');
1354
1400
  moreButton.type = 'button';
1355
1401
  moreButton.className = 'secondary icon-button';
@@ -1387,13 +1433,13 @@ class EinblickEditRuntime {
1387
1433
  void this.logoutFromEinblick();
1388
1434
  });
1389
1435
  moreMenu.append(openAppButton, logoutButton);
1390
- actions.append(cmsButton, moreButton);
1436
+ actions.append(resourceList, moreButton);
1391
1437
  bar.append(brand, actions, moreMenu);
1392
1438
  shadowRoot.append(style, bar);
1393
1439
  document.body.append(host);
1394
1440
  this.bottomBarHost = host;
1395
1441
  this.bottomBarSiteName = siteName;
1396
- this.bottomBarCmsButton = cmsButton;
1442
+ this.bottomBarResourceList = resourceList;
1397
1443
  this.bottomBarMoreButton = moreButton;
1398
1444
  this.bottomBarMoreMenu = moreMenu;
1399
1445
  this.bottomBarOpenAppButton = openAppButton;
@@ -1430,6 +1476,24 @@ class EinblickEditRuntime {
1430
1476
  ? this.messages.brand.authenticatedHint
1431
1477
  : this.messages.brand.unauthenticatedHint;
1432
1478
  }
1479
+ updateBottomBarResources() {
1480
+ if (!this.bottomBarResourceList) {
1481
+ return;
1482
+ }
1483
+ this.bottomBarResourceList.replaceChildren();
1484
+ const button = document.createElement('button');
1485
+ button.type = 'button';
1486
+ button.className = 'resource-button';
1487
+ button.textContent = this.messages.actions.cmsCollections;
1488
+ button.setAttribute('aria-expanded', this.isNavigatorOpen() ? 'true' : 'false');
1489
+ button.addEventListener('click', (event) => {
1490
+ event.preventDefault();
1491
+ event.stopPropagation();
1492
+ this.setBottomBarMenuOpen(false);
1493
+ this.setNavigatorOpen(!this.isNavigatorOpen());
1494
+ });
1495
+ this.bottomBarResourceList.append(button);
1496
+ }
1433
1497
  updateBottomBar() {
1434
1498
  if (!this.bottomBarHost) {
1435
1499
  this.applyBottomLayout(false);
@@ -1449,10 +1513,7 @@ class EinblickEditRuntime {
1449
1513
  this.state.siteName?.trim() || this.messages.brand.connectedSite;
1450
1514
  this.bottomBarSiteName.title = this.bottomBarSiteName.textContent;
1451
1515
  }
1452
- if (this.bottomBarCmsButton) {
1453
- this.bottomBarCmsButton.textContent = this.messages.actions.cmsCollections;
1454
- this.bottomBarCmsButton.setAttribute('aria-expanded', this.isNavigatorOpen() ? 'true' : 'false');
1455
- }
1516
+ this.updateBottomBarResources();
1456
1517
  if (this.bottomBarMoreButton) {
1457
1518
  this.bottomBarMoreButton.setAttribute('aria-label', this.messages.actions.moreActions);
1458
1519
  this.bottomBarMoreButton.setAttribute('aria-expanded', this.isBottomBarMenuOpen() ? 'true' : 'false');
@@ -1464,7 +1525,7 @@ class EinblickEditRuntime {
1464
1525
  if (this.bottomBarLogoutButton) {
1465
1526
  this.bottomBarLogoutButton.textContent = this.messages.actions.logout;
1466
1527
  }
1467
- this.ensureNavigatorPanel();
1528
+ this.ensureNavigatorPanel(false, this.navigatorFilter);
1468
1529
  if (this.navigatorFrame) {
1469
1530
  this.navigatorFrame.title = this.messages.actions.cmsCollections;
1470
1531
  }
@@ -1528,7 +1589,7 @@ class EinblickEditRuntime {
1528
1589
  isNavigatorOpen() {
1529
1590
  return this.navigatorOpen && !!this.navigatorPanel;
1530
1591
  }
1531
- ensureNavigatorPanel(reportErrors = false) {
1592
+ ensureNavigatorPanel(reportErrors = false, filter) {
1532
1593
  if (this.navigatorPanel) {
1533
1594
  return true;
1534
1595
  }
@@ -1538,7 +1599,7 @@ class EinblickEditRuntime {
1538
1599
  let navigatorUrl;
1539
1600
  const nonce = createRandomId();
1540
1601
  try {
1541
- navigatorUrl = this.buildNavigatorUrl(nonce);
1602
+ navigatorUrl = this.buildNavigatorUrl(nonce, filter);
1542
1603
  }
1543
1604
  catch (error) {
1544
1605
  if (reportErrors) {
@@ -1565,18 +1626,22 @@ class EinblickEditRuntime {
1565
1626
  this.navigatorPanel = panel;
1566
1627
  this.navigatorFrame = iframe;
1567
1628
  this.navigatorNonce = nonce;
1629
+ this.navigatorFilter = filter ?? null;
1568
1630
  return true;
1569
1631
  }
1570
- openNavigator() {
1632
+ openNavigator(filter) {
1571
1633
  if (!this.state.isAuthenticated) {
1572
1634
  this.callbacks.onError?.(this.messages.errors.signInToEditContent);
1573
1635
  return;
1574
1636
  }
1575
- if (this.isNavigatorOpen()) {
1637
+ if (this.isNavigatorOpen() && !filter) {
1576
1638
  return;
1577
1639
  }
1640
+ if (this.isNavigatorOpen() && filter) {
1641
+ this.removeNavigatorPanel(false);
1642
+ }
1578
1643
  this.setBottomBarMenuOpen(false);
1579
- if (!this.ensureNavigatorPanel(true) || !this.navigatorPanel) {
1644
+ if (!this.ensureNavigatorPanel(true, filter) || !this.navigatorPanel) {
1580
1645
  return;
1581
1646
  }
1582
1647
  this.navigatorOpen = true;
@@ -1603,6 +1668,7 @@ class EinblickEditRuntime {
1603
1668
  this.navigatorPanel = null;
1604
1669
  this.navigatorFrame = null;
1605
1670
  this.navigatorNonce = null;
1671
+ this.navigatorFilter = null;
1606
1672
  this.navigatorOpen = false;
1607
1673
  if (hadNavigator && updateBottomBar) {
1608
1674
  this.updateBottomBar();
@@ -1696,6 +1762,9 @@ class EinblickEditRuntime {
1696
1762
  this.updateHoverChrome();
1697
1763
  return;
1698
1764
  }
1765
+ if (this.activeElement) {
1766
+ delete this.activeElement.dataset.einblickHoverActive;
1767
+ }
1699
1768
  this.activeElement = element;
1700
1769
  if (!element ||
1701
1770
  !this.hoverButton ||
@@ -1704,34 +1773,27 @@ class EinblickEditRuntime {
1704
1773
  if (this.hoverButtonContainer) {
1705
1774
  this.hoverButtonContainer.hidden = true;
1706
1775
  }
1707
- this.activeOutline?.remove();
1708
- this.activeOutline = null;
1709
1776
  this.clearQuickFieldOverlays();
1777
+ if (this.hoverButton) {
1778
+ this.hoverButton.hidden = true;
1779
+ }
1710
1780
  if (this.hoverCreateButton) {
1711
1781
  this.hoverCreateButton.hidden = true;
1712
1782
  }
1713
1783
  return;
1714
1784
  }
1715
- this.ensureActiveOutline();
1785
+ element.dataset.einblickHoverActive = 'true';
1716
1786
  this.hoverButtonContainer.hidden = false;
1717
1787
  this.updateHoverButtonLabel();
1718
1788
  this.updateHoverButtonVisibility(element);
1719
1789
  this.renderQuickFieldOverlays(element);
1720
1790
  this.updateHoverChrome();
1721
1791
  }
1722
- ensureActiveOutline() {
1723
- if (this.activeOutline || typeof document === 'undefined') {
1724
- return;
1725
- }
1726
- const outline = document.createElement('div');
1727
- outline.setAttribute('data-einblick-edit-outline', 'true');
1728
- document.body.append(outline);
1729
- this.activeOutline = outline;
1730
- }
1731
1792
  updateHoverChrome() {
1732
1793
  if (!this.activeElement || !this.state.isAuthenticated) {
1733
- this.activeOutline?.remove();
1734
- this.activeOutline = null;
1794
+ if (this.activeElement) {
1795
+ delete this.activeElement.dataset.einblickHoverActive;
1796
+ }
1735
1797
  if (this.hoverButtonContainer) {
1736
1798
  this.hoverButtonContainer.hidden = true;
1737
1799
  }
@@ -1742,10 +1804,9 @@ class EinblickEditRuntime {
1742
1804
  this.setActiveElement(null);
1743
1805
  return;
1744
1806
  }
1745
- this.ensureActiveOutline();
1746
1807
  const rect = this.activeElement.getBoundingClientRect();
1747
- const isVisible = this.positionOutline(this.activeOutline, this.activeElement);
1748
- if (!isVisible || rect.width <= 0 || rect.height <= 0) {
1808
+ const isVisible = this.isElementRectVisible(rect);
1809
+ if (!isVisible) {
1749
1810
  if (this.hoverButtonContainer) {
1750
1811
  this.hoverButtonContainer.hidden = true;
1751
1812
  }
@@ -1757,38 +1818,32 @@ class EinblickEditRuntime {
1757
1818
  this.positionHoverControls(rect);
1758
1819
  this.updateQuickFieldOverlayPositions();
1759
1820
  }
1760
- positionOutline(outline, target) {
1761
- if (!outline) {
1762
- return false;
1763
- }
1764
- const rect = target.getBoundingClientRect();
1765
- if (rect.width <= 0 || rect.height <= 0) {
1766
- outline.hidden = true;
1767
- return false;
1768
- }
1769
- const style = window.getComputedStyle(target);
1770
- outline.hidden = false;
1771
- outline.style.setProperty('--einblick-overlay-radius', style.borderRadius || '0.35rem');
1772
- outline.style.left = `${Math.round(rect.left)}px`;
1773
- outline.style.top = `${Math.round(rect.top)}px`;
1774
- outline.style.width = `${Math.round(rect.width)}px`;
1775
- outline.style.height = `${Math.round(rect.height)}px`;
1776
- return true;
1821
+ isElementRectVisible(rect) {
1822
+ return (rect.width > 0 &&
1823
+ rect.height > 0 &&
1824
+ rect.bottom >= 0 &&
1825
+ rect.right >= 0 &&
1826
+ rect.top <= window.innerHeight &&
1827
+ rect.left <= window.innerWidth);
1777
1828
  }
1778
1829
  positionHoverControls(rect) {
1779
1830
  if (!this.hoverButtonContainer) {
1780
1831
  return;
1781
1832
  }
1782
1833
  this.hoverButtonContainer.hidden = false;
1783
- const margin = 8;
1784
1834
  const width = this.hoverButtonContainer.offsetWidth || 180;
1785
1835
  const height = this.hoverButtonContainer.offsetHeight || 32;
1786
- const left = clamp(rect.right - width - margin, margin, Math.max(margin, window.innerWidth - width - margin));
1787
- const top = clamp(rect.top + margin, margin, Math.max(margin, window.innerHeight - height - margin));
1836
+ const scrollX = window.scrollX || document.documentElement.scrollLeft;
1837
+ const scrollY = window.scrollY || document.documentElement.scrollTop;
1838
+ const left = clamp(rect.right + scrollX - width, scrollX, Math.max(scrollX, scrollX + window.innerWidth - width));
1839
+ const top = clamp(rect.top + scrollY, scrollY, Math.max(scrollY, scrollY + window.innerHeight - height));
1788
1840
  this.hoverButtonContainer.style.left = `${Math.round(left)}px`;
1789
1841
  this.hoverButtonContainer.style.top = `${Math.round(top)}px`;
1790
1842
  }
1791
1843
  clearQuickFieldOverlays() {
1844
+ for (const overlay of this.quickFieldOverlays) {
1845
+ delete overlay.target.dataset.einblickFieldHoverActive;
1846
+ }
1792
1847
  this.quickFieldOverlayContainer?.remove();
1793
1848
  this.quickFieldOverlayContainer = null;
1794
1849
  this.quickFieldOverlays = [];
@@ -1803,13 +1858,13 @@ class EinblickEditRuntime {
1803
1858
  container.setAttribute('data-einblick-field-overlays', 'true');
1804
1859
  this.quickFieldOverlays = targets.map((target) => {
1805
1860
  const binding = readBindingFromElement(target);
1806
- const outline = document.createElement('div');
1807
- outline.setAttribute('data-einblick-edit-field-outline', 'true');
1861
+ const isMediaBinding = isRecordEditableBinding(binding) && isMediaEditableBinding(binding);
1862
+ target.dataset.einblickFieldHoverActive = 'true';
1808
1863
  const button = document.createElement('button');
1809
1864
  button.type = 'button';
1810
- button.setAttribute('data-einblick-field-button', binding && isMediaEditableBinding(binding) ? 'media' : 'text');
1865
+ button.setAttribute('data-einblick-field-button', isMediaBinding ? 'media' : 'text');
1811
1866
  button.textContent =
1812
- binding && isMediaEditableBinding(binding)
1867
+ isMediaBinding
1813
1868
  ? getMediaActionLabel(binding, this.messages)
1814
1869
  : this.messages.actions.editText;
1815
1870
  button.addEventListener('pointerdown', (event) => {
@@ -1818,7 +1873,7 @@ class EinblickEditRuntime {
1818
1873
  button.addEventListener('click', (event) => {
1819
1874
  stopEvent(event);
1820
1875
  const nextBinding = readBindingFromElement(target);
1821
- if (!nextBinding) {
1876
+ if (!isRecordEditableBinding(nextBinding)) {
1822
1877
  return;
1823
1878
  }
1824
1879
  if (isMediaEditableBinding(nextBinding)) {
@@ -1827,8 +1882,8 @@ class EinblickEditRuntime {
1827
1882
  }
1828
1883
  this.openInlineEditor(target, nextBinding);
1829
1884
  });
1830
- container.append(outline, button);
1831
- return { target, outline, button };
1885
+ container.append(button);
1886
+ return { target, button };
1832
1887
  });
1833
1888
  document.body.append(container);
1834
1889
  this.quickFieldOverlayContainer = container;
@@ -1840,21 +1895,21 @@ class EinblickEditRuntime {
1840
1895
  }
1841
1896
  for (const overlay of this.quickFieldOverlays) {
1842
1897
  if (!overlay.target.isConnected) {
1843
- overlay.outline.hidden = true;
1844
1898
  overlay.button.hidden = true;
1845
1899
  continue;
1846
1900
  }
1847
1901
  const rect = overlay.target.getBoundingClientRect();
1848
- const isVisible = this.positionOutline(overlay.outline, overlay.target);
1902
+ const isVisible = this.isElementRectVisible(rect);
1849
1903
  overlay.button.hidden = !isVisible;
1850
1904
  if (!isVisible) {
1851
1905
  continue;
1852
1906
  }
1853
- const margin = 4;
1854
1907
  const width = overlay.button.offsetWidth || 96;
1855
1908
  const height = overlay.button.offsetHeight || 26;
1856
- const left = clamp(rect.left + margin, margin, Math.max(margin, window.innerWidth - width - margin));
1857
- const top = clamp(rect.top + margin, margin, Math.max(margin, window.innerHeight - height - margin));
1909
+ const scrollX = window.scrollX || document.documentElement.scrollLeft;
1910
+ const scrollY = window.scrollY || document.documentElement.scrollTop;
1911
+ const left = clamp(rect.right + scrollX - width, scrollX, Math.max(scrollX, scrollX + window.innerWidth - width));
1912
+ const top = clamp(rect.top + scrollY, scrollY, Math.max(scrollY, scrollY + window.innerHeight - height));
1858
1913
  overlay.button.style.left = `${Math.round(left)}px`;
1859
1914
  overlay.button.style.top = `${Math.round(top)}px`;
1860
1915
  }
@@ -1945,8 +2000,14 @@ class EinblickEditRuntime {
1945
2000
  url.searchParams.set('nonce', nonce);
1946
2001
  url.searchParams.set('sessionToken', sessionToken);
1947
2002
  url.searchParams.set('returnTo', window.location.href);
2003
+ url.searchParams.set('sourceType', binding.sourceType ?? 'cms');
1948
2004
  url.searchParams.set('resourceSlug', binding.resourceSlug);
1949
- url.searchParams.set('recordId', binding.recordId);
2005
+ if (binding.recordId) {
2006
+ url.searchParams.set('recordId', binding.recordId);
2007
+ }
2008
+ else if (intent !== 'create') {
2009
+ throw new Error(this.messages.errors.openEditorFailed);
2010
+ }
1950
2011
  if (intent === 'create') {
1951
2012
  url.searchParams.set('intent', intent);
1952
2013
  }
@@ -1955,7 +2016,7 @@ class EinblickEditRuntime {
1955
2016
  }
1956
2017
  return url.toString();
1957
2018
  }
1958
- buildNavigatorUrl(nonce) {
2019
+ buildNavigatorUrl(nonce, filter) {
1959
2020
  const sessionToken = this.getActiveSessionToken();
1960
2021
  if (!sessionToken) {
1961
2022
  throw new Error(this.messages.errors.editSessionExpired);
@@ -1964,6 +2025,15 @@ class EinblickEditRuntime {
1964
2025
  url.searchParams.set('origin', window.location.origin);
1965
2026
  url.searchParams.set('nonce', nonce);
1966
2027
  url.searchParams.set('sessionToken', sessionToken);
2028
+ if (filter?.sourceType) {
2029
+ url.searchParams.set('sourceType', filter.sourceType);
2030
+ }
2031
+ if (filter?.resourceSlug) {
2032
+ url.searchParams.set('resourceSlug', filter.resourceSlug);
2033
+ }
2034
+ if (filter?.recordIds && filter.recordIds.length > 0) {
2035
+ url.searchParams.set('recordIds', filter.recordIds.join(','));
2036
+ }
1967
2037
  return url.toString();
1968
2038
  }
1969
2039
  buildCollectionEditorUrl(args) {
@@ -1976,7 +2046,11 @@ class EinblickEditRuntime {
1976
2046
  url.searchParams.set('nonce', args.nonce);
1977
2047
  url.searchParams.set('sessionToken', sessionToken);
1978
2048
  url.searchParams.set('returnTo', window.location.href);
2049
+ url.searchParams.set('sourceType', args.sourceType ?? 'cms');
1979
2050
  url.searchParams.set('collectionId', args.collectionId);
2051
+ if (args.resourceSlug) {
2052
+ url.searchParams.set('resourceSlug', args.resourceSlug);
2053
+ }
1980
2054
  url.searchParams.set('intent', args.intent);
1981
2055
  if (args.recordId) {
1982
2056
  url.searchParams.set('recordId', args.recordId);
@@ -1984,11 +2058,23 @@ class EinblickEditRuntime {
1984
2058
  return url.toString();
1985
2059
  }
1986
2060
  updateHoverButtonVisibility(element) {
1987
- if (!this.hoverCreateButton) {
2061
+ if (!this.hoverButton || !this.hoverCreateButton) {
1988
2062
  return;
1989
2063
  }
1990
2064
  const binding = element ? readBindingFromElement(element) : null;
1991
- this.hoverCreateButton.hidden = !this.shouldShowCreateButton(element, binding);
2065
+ const canCreate = this.shouldShowCreateButton(element, binding);
2066
+ const canEdit = this.shouldShowEditButton(element, binding);
2067
+ this.hoverButton.hidden = !canEdit;
2068
+ this.hoverCreateButton.hidden = !canCreate;
2069
+ if (this.hoverButtonContainer) {
2070
+ this.hoverButtonContainer.hidden = !canEdit && !canCreate;
2071
+ }
2072
+ }
2073
+ shouldShowEditButton(element, binding) {
2074
+ if (element?.dataset.einblickKind === 'collection') {
2075
+ return false;
2076
+ }
2077
+ return isRecordEditableBinding(binding);
1992
2078
  }
1993
2079
  shouldShowCreateButton(element, binding) {
1994
2080
  if (!element || !binding || !this.state.isAuthenticated) {
@@ -1997,28 +2083,8 @@ class EinblickEditRuntime {
1997
2083
  if (binding.fieldKey) {
1998
2084
  return false;
1999
2085
  }
2000
- if (binding.resourceMode === 'singleton') {
2001
- return false;
2002
- }
2003
- if (binding.resourceMode === 'collection') {
2004
- return true;
2005
- }
2006
- if (element.dataset.einblickKind === 'text') {
2007
- return false;
2008
- }
2009
- const selector = [
2010
- '[data-einblick-editable="true"]',
2011
- `[data-einblick-resource="${CSS.escape(binding.resourceSlug)}"]`,
2012
- ].join('');
2013
- const elements = document.querySelectorAll(selector);
2014
- const recordIds = new Set();
2015
- elements.forEach((candidate) => {
2016
- const recordId = candidate.dataset.einblickRecordId?.trim();
2017
- if (recordId) {
2018
- recordIds.add(recordId);
2019
- }
2020
- });
2021
- return recordIds.size > 1;
2086
+ return (element.dataset.einblickKind === 'collection' &&
2087
+ binding.resourceMode === 'collection');
2022
2088
  }
2023
2089
  findEditableTarget(target) {
2024
2090
  if (!(target instanceof Element)) {
@@ -2041,6 +2107,7 @@ class EinblickEditRuntime {
2041
2107
  const parentBinding = readBindingFromElement(parent);
2042
2108
  if (parentBinding &&
2043
2109
  !parentBinding.fieldKey &&
2110
+ (parentBinding.sourceType ?? 'cms') === (binding.sourceType ?? 'cms') &&
2044
2111
  parentBinding.resourceSlug === binding.resourceSlug &&
2045
2112
  parentBinding.recordId === binding.recordId) {
2046
2113
  return parent;
@@ -2059,8 +2126,12 @@ class EinblickEditRuntime {
2059
2126
  ? [element]
2060
2127
  : [];
2061
2128
  }
2129
+ if (!binding.recordId) {
2130
+ return [];
2131
+ }
2062
2132
  const selector = [
2063
2133
  '[data-einblick-editable="true"]',
2134
+ `[data-einblick-source-type="${CSS.escape(binding.sourceType ?? 'cms')}"]`,
2064
2135
  `[data-einblick-resource="${CSS.escape(binding.resourceSlug)}"]`,
2065
2136
  `[data-einblick-record-id="${CSS.escape(binding.recordId)}"]`,
2066
2137
  '[data-einblick-field-key]',
@@ -2081,7 +2152,6 @@ class EinblickEditRuntime {
2081
2152
  }
2082
2153
  return (!!this.hoverButtonContainer?.contains(target) ||
2083
2154
  !!this.quickFieldOverlayContainer?.contains(target) ||
2084
- !!this.activeOutline?.contains(target) ||
2085
2155
  !!this.brandBadge?.contains(target) ||
2086
2156
  !!this.brandPopover?.contains(target) ||
2087
2157
  !!this.bottomBarHost?.contains(target) ||
@@ -2090,8 +2160,11 @@ class EinblickEditRuntime {
2090
2160
  findElementForBinding(binding) {
2091
2161
  const selector = [
2092
2162
  '[data-einblick-editable="true"]',
2163
+ `[data-einblick-source-type="${CSS.escape(binding.sourceType ?? 'cms')}"]`,
2093
2164
  `[data-einblick-resource="${CSS.escape(binding.resourceSlug)}"]`,
2094
- `[data-einblick-record-id="${CSS.escape(binding.recordId)}"]`,
2165
+ binding.recordId
2166
+ ? `[data-einblick-record-id="${CSS.escape(binding.recordId)}"]`
2167
+ : '',
2095
2168
  binding.fieldKey
2096
2169
  ? `[data-einblick-field-key="${CSS.escape(binding.fieldKey)}"]`
2097
2170
  : '',
@@ -2187,9 +2260,6 @@ class EinblickEditRuntime {
2187
2260
  restoreState.cleanup.push(() => target.removeEventListener('click', handleClick, true), () => target.removeEventListener('keydown', handleKeyDown, true), () => target.removeEventListener('paste', handlePaste, true), () => target.removeEventListener('blur', handleBlur, true));
2188
2261
  this.currentInlineTarget = target;
2189
2262
  this.currentInlineBinding = binding;
2190
- this.ensureActiveOutline();
2191
- this.activeOutline?.setAttribute('data-einblick-inline-active', 'true');
2192
- this.positionOutline(this.activeOutline, target);
2193
2263
  window.setTimeout(() => {
2194
2264
  target.focus({ preventScroll: true });
2195
2265
  this.placeCaretAtEnd(target);
@@ -2215,11 +2285,6 @@ class EinblickEditRuntime {
2215
2285
  this.currentInlineTarget = null;
2216
2286
  this.currentInlineBinding = null;
2217
2287
  this.inlineSaveInFlight = null;
2218
- this.activeOutline?.removeAttribute('data-einblick-inline-active');
2219
- if (!this.activeElement) {
2220
- this.activeOutline?.remove();
2221
- this.activeOutline = null;
2222
- }
2223
2288
  }
2224
2289
  restoreInlineTargetAttributes(target, restoreState) {
2225
2290
  if (restoreState.contentEditable === null) {
@@ -2421,6 +2486,7 @@ class EinblickEditRuntime {
2421
2486
  nonce: this.bridgeNonce,
2422
2487
  requestId,
2423
2488
  payload: {
2489
+ sourceType: binding.sourceType ?? 'cms',
2424
2490
  resourceSlug: binding.resourceSlug,
2425
2491
  recordId: binding.recordId,
2426
2492
  fieldKey: binding.fieldKey,
@@ -2474,6 +2540,7 @@ class EinblickEditRuntime {
2474
2540
  nonce: this.bridgeNonce,
2475
2541
  requestId,
2476
2542
  payload: {
2543
+ sourceType: binding.sourceType ?? 'cms',
2477
2544
  resourceSlug: binding.resourceSlug,
2478
2545
  recordId: binding.recordId,
2479
2546
  fieldKey: binding.fieldKey,
@@ -2542,6 +2609,8 @@ class EinblickEditRuntime {
2542
2609
  nonce,
2543
2610
  intent: args.intent,
2544
2611
  recordId: args.recordId,
2612
+ sourceType: args.sourceType,
2613
+ resourceSlug: args.resourceSlug,
2545
2614
  });
2546
2615
  }
2547
2616
  catch (error) {
@@ -2554,6 +2623,7 @@ class EinblickEditRuntime {
2554
2623
  this.editorTarget = null;
2555
2624
  this.editorBinding = args.resourceSlug
2556
2625
  ? {
2626
+ sourceType: args.sourceType ?? 'cms',
2557
2627
  resourceSlug: args.resourceSlug,
2558
2628
  recordId: args.recordId ?? '',
2559
2629
  resourceMode: 'collection',
@@ -2625,6 +2695,7 @@ export function createEinblickEditRuntime(options) {
2625
2695
  login: (loginOptions) => runtime.login(loginOptions),
2626
2696
  openEditor: (binding, element) => runtime.openEditor(binding, element),
2627
2697
  setLocale: (locale) => runtime.setLocale(locale),
2698
+ setResourceHints: (hints) => runtime.setResourceHints(hints),
2628
2699
  };
2629
2700
  }
2630
2701
  //# sourceMappingURL=runtime.js.map