@einblick/sdk 0.6.0 → 0.6.3

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,28 +1,5 @@
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
- }
26
3
  const RUNTIME_STYLE_ID = 'einblick-edit-runtime-styles';
27
4
  const EINBLICK_ACCENT_RGB = '250, 105, 62';
28
5
  const EINBLICK_ACCENT_COLOR = `rgb(${EINBLICK_ACCENT_RGB})`;
@@ -83,6 +60,9 @@ function isTextInputEvent(event) {
83
60
  event.key === 'Backspace' ||
84
61
  event.key === 'Delete');
85
62
  }
63
+ function isRecordEditableBinding(binding) {
64
+ return Boolean(binding?.recordId);
65
+ }
86
66
  function fieldTypeAllowsMultiline(binding) {
87
67
  return binding.fieldType === 'text' || binding.fieldType === 'markdown';
88
68
  }
@@ -110,43 +90,41 @@ function injectRuntimeStyles() {
110
90
  scroll-margin-top: 96px;
111
91
  }
112
92
 
113
- [data-einblick-edit-outline],
114
- [data-einblick-edit-field-outline] {
115
- position: fixed;
116
- z-index: 2147483643;
117
- box-sizing: border-box;
118
- pointer-events: none;
119
- border-radius: var(--einblick-overlay-radius, 0.35rem);
93
+ [data-einblick-editable="true"][data-einblick-hover-active="true"] {
94
+ outline: 1px solid ${EINBLICK_ACCENT_BORDER};
95
+ outline-offset: -1px;
96
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.72);
120
97
  }
121
98
 
122
- [data-einblick-edit-outline] {
123
- border: 1px solid ${EINBLICK_ACCENT_BORDER};
124
- box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.7);
99
+ [data-einblick-editable="true"][data-einblick-field-hover-active="true"] {
100
+ outline: 1px dashed rgba(${EINBLICK_ACCENT_RGB}, 0.78);
101
+ outline-offset: -1px;
125
102
  }
126
103
 
127
- [data-einblick-edit-field-outline] {
128
- border: 1px dashed rgba(${EINBLICK_ACCENT_RGB}, 0.78);
129
- background: rgba(${EINBLICK_ACCENT_RGB}, 0.04);
104
+ [data-einblick-editable="true"][data-einblick-inline-editing="true"] {
105
+ outline: 2px solid ${EINBLICK_ACCENT_COLOR};
106
+ outline-offset: -2px;
130
107
  }
131
108
 
132
109
  [data-einblick-edit-controls] {
133
- position: fixed;
110
+ position: absolute;
134
111
  z-index: 2147483644;
135
112
  display: inline-flex;
136
113
  align-items: center;
137
- gap: 0.45rem;
114
+ gap: 0;
138
115
  }
139
116
 
140
117
  [data-einblick-edit-button],
141
118
  [data-einblick-create-button],
142
119
  [data-einblick-field-button] {
143
120
  border: 1px solid ${EINBLICK_ACCENT_COLOR};
144
- border-radius: 999px;
145
- padding: 0.3rem 0.7rem;
121
+ border-radius: 0;
122
+ min-height: 2rem;
123
+ padding: 0 0.72rem;
146
124
  font: 600 12px/1 system-ui, sans-serif;
147
- letter-spacing: 0.01em;
125
+ letter-spacing: 0;
148
126
  cursor: pointer;
149
- box-shadow: 0 8px 20px rgba(15, 23, 42, 0.16);
127
+ box-shadow: 0 8px 18px rgba(15, 23, 42, 0.16);
150
128
  }
151
129
 
152
130
  [data-einblick-edit-button],
@@ -156,7 +134,6 @@ function injectRuntimeStyles() {
156
134
  }
157
135
 
158
136
  [data-einblick-edit-button] {
159
- padding: 0.42rem 0.85rem;
160
137
  font-size: 13px;
161
138
  }
162
139
 
@@ -174,19 +151,19 @@ function injectRuntimeStyles() {
174
151
  }
175
152
 
176
153
  [data-einblick-field-overlays] {
177
- position: fixed;
178
- inset: 0;
154
+ position: absolute;
155
+ left: 0;
156
+ top: 0;
179
157
  z-index: 2147483644;
180
158
  pointer-events: none;
181
159
  }
182
160
 
183
161
  [data-einblick-field-button] {
184
- position: fixed;
162
+ position: absolute;
185
163
  z-index: 2147483644;
186
164
  pointer-events: auto;
187
- padding: 0.24rem 0.58rem;
188
165
  font-size: 11px;
189
- box-shadow: 0 8px 18px rgba(15, 23, 42, 0.12);
166
+ min-height: 1.65rem;
190
167
  }
191
168
 
192
169
  [data-einblick-brand-badge] {
@@ -417,7 +394,6 @@ class EinblickEditRuntime {
417
394
  callbacks;
418
395
  chrome;
419
396
  reserveBottomSpace;
420
- resourceHints;
421
397
  locale;
422
398
  messages;
423
399
  bridgeNonce = createRandomId();
@@ -431,7 +407,6 @@ class EinblickEditRuntime {
431
407
  bridgeFrame = null;
432
408
  bridgePingInterval = null;
433
409
  activeElement = null;
434
- activeOutline = null;
435
410
  hoverButtonContainer = null;
436
411
  hoverButton = null;
437
412
  hoverCreateButton = null;
@@ -468,7 +443,6 @@ class EinblickEditRuntime {
468
443
  navigatorFrame = null;
469
444
  navigatorNonce = null;
470
445
  navigatorOpen = false;
471
- navigatorFilter = null;
472
446
  pendingInlineSaves = new Map();
473
447
  pendingInlineMediaSaves = new Map();
474
448
  pendingLogin = null;
@@ -479,7 +453,6 @@ class EinblickEditRuntime {
479
453
  this.chrome = options.chrome ?? 'bar';
480
454
  this.reserveBottomSpace =
481
455
  options.reserveBottomSpace ?? this.chrome === 'bar';
482
- this.resourceHints = normalizeResourceHints(options.resourceHints);
483
456
  this.locale = resolveEinblickSdkLocale(options.locale);
484
457
  this.messages = getEinblickSdkMessages(this.locale);
485
458
  this.session =
@@ -502,10 +475,6 @@ class EinblickEditRuntime {
502
475
  this.messages = getEinblickSdkMessages(nextLocale);
503
476
  this.refreshLocalizedUi();
504
477
  }
505
- setResourceHints(hints) {
506
- this.resourceHints = normalizeResourceHints(hints);
507
- this.updateBottomBarResources();
508
- }
509
478
  start() {
510
479
  if (this.started ||
511
480
  typeof window === 'undefined' ||
@@ -570,8 +539,6 @@ class EinblickEditRuntime {
570
539
  this.bridgeFrame?.remove();
571
540
  this.bridgeFrame = null;
572
541
  this.setActiveElement(null);
573
- this.activeOutline?.remove();
574
- this.activeOutline = null;
575
542
  this.hoverButtonContainer?.remove();
576
543
  this.hoverButtonContainer = null;
577
544
  this.hoverButton?.remove();
@@ -720,8 +687,6 @@ class EinblickEditRuntime {
720
687
  };
721
688
  handleViewportChange = () => {
722
689
  if (this.currentInlineTarget?.isConnected) {
723
- this.ensureActiveOutline();
724
- this.positionOutline(this.activeOutline, this.currentInlineTarget);
725
690
  return;
726
691
  }
727
692
  if (this.activeElement && !this.activeElement.isConnected) {
@@ -774,7 +739,8 @@ class EinblickEditRuntime {
774
739
  return;
775
740
  }
776
741
  const binding = readBindingFromElement(this.activeElement);
777
- if (!binding) {
742
+ if (!isRecordEditableBinding(binding) ||
743
+ !this.shouldShowEditButton(this.activeElement, binding)) {
778
744
  return;
779
745
  }
780
746
  if (binding.fieldKey && isMediaEditableBinding(binding)) {
@@ -985,7 +951,6 @@ class EinblickEditRuntime {
985
951
  sourceType: message.sourceType ?? 'cms',
986
952
  resourceSlug: message.resourceSlug,
987
953
  recordId: message.recordId ?? '',
988
- resourceMode: 'collection',
989
954
  displayMode: 'drawer',
990
955
  },
991
956
  value: {
@@ -1020,16 +985,19 @@ class EinblickEditRuntime {
1020
985
  });
1021
986
  }
1022
987
  else if (this.editorBinding) {
1023
- const nextBinding = {
1024
- ...this.editorBinding,
1025
- sourceType: message.sourceType ?? this.editorBinding.sourceType ?? 'cms',
1026
- recordId: message.recordId ?? this.editorBinding.recordId,
1027
- };
1028
- void this.callbacks.onSave?.({
1029
- binding: nextBinding,
1030
- value: message.fields,
1031
- source: 'drawer',
1032
- });
988
+ const nextRecordId = message.recordId ?? this.editorBinding.recordId;
989
+ if (nextRecordId) {
990
+ const nextBinding = {
991
+ ...this.editorBinding,
992
+ sourceType: message.sourceType ?? this.editorBinding.sourceType ?? 'cms',
993
+ recordId: nextRecordId,
994
+ };
995
+ void this.callbacks.onSave?.({
996
+ binding: nextBinding,
997
+ value: message.fields,
998
+ source: 'drawer',
999
+ });
1000
+ }
1033
1001
  }
1034
1002
  this.closeDrawer();
1035
1003
  return;
@@ -1283,6 +1251,7 @@ class EinblickEditRuntime {
1283
1251
  min-width: 0;
1284
1252
  height: 2rem;
1285
1253
  margin: 0;
1254
+ border: 0;
1286
1255
  border-radius: 0.35rem;
1287
1256
  background: #ffffff;
1288
1257
  color: #fa693e;
@@ -1476,78 +1445,23 @@ class EinblickEditRuntime {
1476
1445
  ? this.messages.brand.authenticatedHint
1477
1446
  : this.messages.brand.unauthenticatedHint;
1478
1447
  }
1479
- collectBottomBarResources() {
1480
- const resources = new Map();
1481
- for (const hint of this.resourceHints) {
1482
- const key = `${hint.sourceType}:${hint.resourceSlug}`;
1483
- if (hint.showInBottomBar === false) {
1484
- resources.delete(key);
1485
- continue;
1486
- }
1487
- resources.set(key, hint);
1488
- }
1489
- if (typeof document !== 'undefined') {
1490
- const elements = document.querySelectorAll('[data-einblick-editable="true"][data-einblick-resource]');
1491
- elements.forEach((element) => {
1492
- const binding = readBindingFromElement(element);
1493
- if (!binding?.resourceSlug)
1494
- return;
1495
- const sourceType = binding.sourceType ?? 'cms';
1496
- const key = `${sourceType}:${binding.resourceSlug}`;
1497
- if (resources.has(key)) {
1498
- const current = resources.get(key);
1499
- if (binding.recordId &&
1500
- !current.recordIds?.includes(binding.recordId)) {
1501
- current.recordIds = [...(current.recordIds ?? []), binding.recordId];
1502
- }
1503
- return;
1504
- }
1505
- resources.set(key, {
1506
- sourceType,
1507
- resourceSlug: binding.resourceSlug,
1508
- label: binding.label,
1509
- mode: binding.resourceMode,
1510
- recordIds: binding.recordId ? [binding.recordId] : undefined,
1511
- });
1512
- });
1513
- }
1514
- return [...resources.values()];
1515
- }
1516
1448
  updateBottomBarResources() {
1517
1449
  if (!this.bottomBarResourceList) {
1518
1450
  return;
1519
1451
  }
1520
1452
  this.bottomBarResourceList.replaceChildren();
1521
- const resources = this.collectBottomBarResources();
1522
- if (resources.length === 0) {
1523
- const button = document.createElement('button');
1524
- button.type = 'button';
1525
- button.className = 'resource-button';
1526
- button.textContent = this.messages.actions.cmsCollections;
1527
- button.setAttribute('aria-expanded', this.isNavigatorOpen() ? 'true' : 'false');
1528
- button.addEventListener('click', (event) => {
1529
- event.preventDefault();
1530
- event.stopPropagation();
1531
- this.setBottomBarMenuOpen(false);
1532
- this.setNavigatorOpen(!this.isNavigatorOpen());
1533
- });
1534
- this.bottomBarResourceList.append(button);
1535
- return;
1536
- }
1537
- for (const resource of resources) {
1538
- const button = document.createElement('button');
1539
- button.type = 'button';
1540
- button.className = 'resource-button';
1541
- button.textContent = resource.label ?? resource.resourceSlug;
1542
- button.title = button.textContent;
1543
- button.addEventListener('click', (event) => {
1544
- event.preventDefault();
1545
- event.stopPropagation();
1546
- this.setBottomBarMenuOpen(false);
1547
- this.openNavigator(resource);
1548
- });
1549
- this.bottomBarResourceList.append(button);
1550
- }
1453
+ const button = document.createElement('button');
1454
+ button.type = 'button';
1455
+ button.className = 'resource-button';
1456
+ button.textContent = this.messages.actions.collections;
1457
+ button.setAttribute('aria-expanded', this.isNavigatorOpen() ? 'true' : 'false');
1458
+ button.addEventListener('click', (event) => {
1459
+ event.preventDefault();
1460
+ event.stopPropagation();
1461
+ this.setBottomBarMenuOpen(false);
1462
+ this.setNavigatorOpen(!this.isNavigatorOpen());
1463
+ });
1464
+ this.bottomBarResourceList.append(button);
1551
1465
  }
1552
1466
  updateBottomBar() {
1553
1467
  if (!this.bottomBarHost) {
@@ -1580,9 +1494,9 @@ class EinblickEditRuntime {
1580
1494
  if (this.bottomBarLogoutButton) {
1581
1495
  this.bottomBarLogoutButton.textContent = this.messages.actions.logout;
1582
1496
  }
1583
- this.ensureNavigatorPanel(false, this.navigatorFilter);
1497
+ this.ensureNavigatorPanel(false);
1584
1498
  if (this.navigatorFrame) {
1585
- this.navigatorFrame.title = this.messages.actions.cmsCollections;
1499
+ this.navigatorFrame.title = this.messages.actions.collections;
1586
1500
  }
1587
1501
  }
1588
1502
  applyBottomLayout(active) {
@@ -1644,7 +1558,7 @@ class EinblickEditRuntime {
1644
1558
  isNavigatorOpen() {
1645
1559
  return this.navigatorOpen && !!this.navigatorPanel;
1646
1560
  }
1647
- ensureNavigatorPanel(reportErrors = false, filter) {
1561
+ ensureNavigatorPanel(reportErrors = false) {
1648
1562
  if (this.navigatorPanel) {
1649
1563
  return true;
1650
1564
  }
@@ -1654,7 +1568,7 @@ class EinblickEditRuntime {
1654
1568
  let navigatorUrl;
1655
1569
  const nonce = createRandomId();
1656
1570
  try {
1657
- navigatorUrl = this.buildNavigatorUrl(nonce, filter);
1571
+ navigatorUrl = this.buildNavigatorUrl(nonce);
1658
1572
  }
1659
1573
  catch (error) {
1660
1574
  if (reportErrors) {
@@ -1669,7 +1583,7 @@ class EinblickEditRuntime {
1669
1583
  panel.setAttribute('aria-hidden', 'true');
1670
1584
  const iframe = document.createElement('iframe');
1671
1585
  iframe.src = navigatorUrl;
1672
- iframe.title = this.messages.actions.cmsCollections;
1586
+ iframe.title = this.messages.actions.collections;
1673
1587
  iframe.tabIndex = -1;
1674
1588
  iframe.style.display = 'block';
1675
1589
  iframe.style.width = '100%';
@@ -1681,22 +1595,18 @@ class EinblickEditRuntime {
1681
1595
  this.navigatorPanel = panel;
1682
1596
  this.navigatorFrame = iframe;
1683
1597
  this.navigatorNonce = nonce;
1684
- this.navigatorFilter = filter ?? null;
1685
1598
  return true;
1686
1599
  }
1687
- openNavigator(filter) {
1600
+ openNavigator() {
1688
1601
  if (!this.state.isAuthenticated) {
1689
1602
  this.callbacks.onError?.(this.messages.errors.signInToEditContent);
1690
1603
  return;
1691
1604
  }
1692
- if (this.isNavigatorOpen() && !filter) {
1605
+ if (this.isNavigatorOpen()) {
1693
1606
  return;
1694
1607
  }
1695
- if (this.isNavigatorOpen() && filter) {
1696
- this.removeNavigatorPanel(false);
1697
- }
1698
1608
  this.setBottomBarMenuOpen(false);
1699
- if (!this.ensureNavigatorPanel(true, filter) || !this.navigatorPanel) {
1609
+ if (!this.ensureNavigatorPanel(true) || !this.navigatorPanel) {
1700
1610
  return;
1701
1611
  }
1702
1612
  this.navigatorOpen = true;
@@ -1723,7 +1633,6 @@ class EinblickEditRuntime {
1723
1633
  this.navigatorPanel = null;
1724
1634
  this.navigatorFrame = null;
1725
1635
  this.navigatorNonce = null;
1726
- this.navigatorFilter = null;
1727
1636
  this.navigatorOpen = false;
1728
1637
  if (hadNavigator && updateBottomBar) {
1729
1638
  this.updateBottomBar();
@@ -1817,6 +1726,9 @@ class EinblickEditRuntime {
1817
1726
  this.updateHoverChrome();
1818
1727
  return;
1819
1728
  }
1729
+ if (this.activeElement) {
1730
+ delete this.activeElement.dataset.einblickHoverActive;
1731
+ }
1820
1732
  this.activeElement = element;
1821
1733
  if (!element ||
1822
1734
  !this.hoverButton ||
@@ -1825,34 +1737,27 @@ class EinblickEditRuntime {
1825
1737
  if (this.hoverButtonContainer) {
1826
1738
  this.hoverButtonContainer.hidden = true;
1827
1739
  }
1828
- this.activeOutline?.remove();
1829
- this.activeOutline = null;
1830
1740
  this.clearQuickFieldOverlays();
1741
+ if (this.hoverButton) {
1742
+ this.hoverButton.hidden = true;
1743
+ }
1831
1744
  if (this.hoverCreateButton) {
1832
1745
  this.hoverCreateButton.hidden = true;
1833
1746
  }
1834
1747
  return;
1835
1748
  }
1836
- this.ensureActiveOutline();
1749
+ element.dataset.einblickHoverActive = 'true';
1837
1750
  this.hoverButtonContainer.hidden = false;
1838
1751
  this.updateHoverButtonLabel();
1839
1752
  this.updateHoverButtonVisibility(element);
1840
1753
  this.renderQuickFieldOverlays(element);
1841
1754
  this.updateHoverChrome();
1842
1755
  }
1843
- ensureActiveOutline() {
1844
- if (this.activeOutline || typeof document === 'undefined') {
1845
- return;
1846
- }
1847
- const outline = document.createElement('div');
1848
- outline.setAttribute('data-einblick-edit-outline', 'true');
1849
- document.body.append(outline);
1850
- this.activeOutline = outline;
1851
- }
1852
1756
  updateHoverChrome() {
1853
1757
  if (!this.activeElement || !this.state.isAuthenticated) {
1854
- this.activeOutline?.remove();
1855
- this.activeOutline = null;
1758
+ if (this.activeElement) {
1759
+ delete this.activeElement.dataset.einblickHoverActive;
1760
+ }
1856
1761
  if (this.hoverButtonContainer) {
1857
1762
  this.hoverButtonContainer.hidden = true;
1858
1763
  }
@@ -1863,10 +1768,9 @@ class EinblickEditRuntime {
1863
1768
  this.setActiveElement(null);
1864
1769
  return;
1865
1770
  }
1866
- this.ensureActiveOutline();
1867
1771
  const rect = this.activeElement.getBoundingClientRect();
1868
- const isVisible = this.positionOutline(this.activeOutline, this.activeElement);
1869
- if (!isVisible || rect.width <= 0 || rect.height <= 0) {
1772
+ const isVisible = this.isElementRectVisible(rect);
1773
+ if (!isVisible) {
1870
1774
  if (this.hoverButtonContainer) {
1871
1775
  this.hoverButtonContainer.hidden = true;
1872
1776
  }
@@ -1878,38 +1782,32 @@ class EinblickEditRuntime {
1878
1782
  this.positionHoverControls(rect);
1879
1783
  this.updateQuickFieldOverlayPositions();
1880
1784
  }
1881
- positionOutline(outline, target) {
1882
- if (!outline) {
1883
- return false;
1884
- }
1885
- const rect = target.getBoundingClientRect();
1886
- if (rect.width <= 0 || rect.height <= 0) {
1887
- outline.hidden = true;
1888
- return false;
1889
- }
1890
- const style = window.getComputedStyle(target);
1891
- outline.hidden = false;
1892
- outline.style.setProperty('--einblick-overlay-radius', style.borderRadius || '0.35rem');
1893
- outline.style.left = `${Math.round(rect.left)}px`;
1894
- outline.style.top = `${Math.round(rect.top)}px`;
1895
- outline.style.width = `${Math.round(rect.width)}px`;
1896
- outline.style.height = `${Math.round(rect.height)}px`;
1897
- return true;
1785
+ isElementRectVisible(rect) {
1786
+ return (rect.width > 0 &&
1787
+ rect.height > 0 &&
1788
+ rect.bottom >= 0 &&
1789
+ rect.right >= 0 &&
1790
+ rect.top <= window.innerHeight &&
1791
+ rect.left <= window.innerWidth);
1898
1792
  }
1899
1793
  positionHoverControls(rect) {
1900
1794
  if (!this.hoverButtonContainer) {
1901
1795
  return;
1902
1796
  }
1903
1797
  this.hoverButtonContainer.hidden = false;
1904
- const margin = 8;
1905
1798
  const width = this.hoverButtonContainer.offsetWidth || 180;
1906
1799
  const height = this.hoverButtonContainer.offsetHeight || 32;
1907
- const left = clamp(rect.right - width - margin, margin, Math.max(margin, window.innerWidth - width - margin));
1908
- const top = clamp(rect.top + margin, margin, Math.max(margin, window.innerHeight - height - margin));
1800
+ const scrollX = window.scrollX || document.documentElement.scrollLeft;
1801
+ const scrollY = window.scrollY || document.documentElement.scrollTop;
1802
+ const left = clamp(rect.right + scrollX - width, scrollX, Math.max(scrollX, scrollX + window.innerWidth - width));
1803
+ const top = clamp(rect.top + scrollY, scrollY, Math.max(scrollY, scrollY + window.innerHeight - height));
1909
1804
  this.hoverButtonContainer.style.left = `${Math.round(left)}px`;
1910
1805
  this.hoverButtonContainer.style.top = `${Math.round(top)}px`;
1911
1806
  }
1912
1807
  clearQuickFieldOverlays() {
1808
+ for (const overlay of this.quickFieldOverlays) {
1809
+ delete overlay.target.dataset.einblickFieldHoverActive;
1810
+ }
1913
1811
  this.quickFieldOverlayContainer?.remove();
1914
1812
  this.quickFieldOverlayContainer = null;
1915
1813
  this.quickFieldOverlays = [];
@@ -1924,13 +1822,13 @@ class EinblickEditRuntime {
1924
1822
  container.setAttribute('data-einblick-field-overlays', 'true');
1925
1823
  this.quickFieldOverlays = targets.map((target) => {
1926
1824
  const binding = readBindingFromElement(target);
1927
- const outline = document.createElement('div');
1928
- outline.setAttribute('data-einblick-edit-field-outline', 'true');
1825
+ const isMediaBinding = isRecordEditableBinding(binding) && isMediaEditableBinding(binding);
1826
+ target.dataset.einblickFieldHoverActive = 'true';
1929
1827
  const button = document.createElement('button');
1930
1828
  button.type = 'button';
1931
- button.setAttribute('data-einblick-field-button', binding && isMediaEditableBinding(binding) ? 'media' : 'text');
1829
+ button.setAttribute('data-einblick-field-button', isMediaBinding ? 'media' : 'text');
1932
1830
  button.textContent =
1933
- binding && isMediaEditableBinding(binding)
1831
+ isMediaBinding
1934
1832
  ? getMediaActionLabel(binding, this.messages)
1935
1833
  : this.messages.actions.editText;
1936
1834
  button.addEventListener('pointerdown', (event) => {
@@ -1939,7 +1837,7 @@ class EinblickEditRuntime {
1939
1837
  button.addEventListener('click', (event) => {
1940
1838
  stopEvent(event);
1941
1839
  const nextBinding = readBindingFromElement(target);
1942
- if (!nextBinding) {
1840
+ if (!isRecordEditableBinding(nextBinding)) {
1943
1841
  return;
1944
1842
  }
1945
1843
  if (isMediaEditableBinding(nextBinding)) {
@@ -1948,8 +1846,8 @@ class EinblickEditRuntime {
1948
1846
  }
1949
1847
  this.openInlineEditor(target, nextBinding);
1950
1848
  });
1951
- container.append(outline, button);
1952
- return { target, outline, button };
1849
+ container.append(button);
1850
+ return { target, button };
1953
1851
  });
1954
1852
  document.body.append(container);
1955
1853
  this.quickFieldOverlayContainer = container;
@@ -1961,21 +1859,21 @@ class EinblickEditRuntime {
1961
1859
  }
1962
1860
  for (const overlay of this.quickFieldOverlays) {
1963
1861
  if (!overlay.target.isConnected) {
1964
- overlay.outline.hidden = true;
1965
1862
  overlay.button.hidden = true;
1966
1863
  continue;
1967
1864
  }
1968
1865
  const rect = overlay.target.getBoundingClientRect();
1969
- const isVisible = this.positionOutline(overlay.outline, overlay.target);
1866
+ const isVisible = this.isElementRectVisible(rect);
1970
1867
  overlay.button.hidden = !isVisible;
1971
1868
  if (!isVisible) {
1972
1869
  continue;
1973
1870
  }
1974
- const margin = 4;
1975
1871
  const width = overlay.button.offsetWidth || 96;
1976
1872
  const height = overlay.button.offsetHeight || 26;
1977
- const left = clamp(rect.left + margin, margin, Math.max(margin, window.innerWidth - width - margin));
1978
- const top = clamp(rect.top + margin, margin, Math.max(margin, window.innerHeight - height - margin));
1873
+ const scrollX = window.scrollX || document.documentElement.scrollLeft;
1874
+ const scrollY = window.scrollY || document.documentElement.scrollTop;
1875
+ const left = clamp(rect.right + scrollX - width, scrollX, Math.max(scrollX, scrollX + window.innerWidth - width));
1876
+ const top = clamp(rect.top + scrollY, scrollY, Math.max(scrollY, scrollY + window.innerHeight - height));
1979
1877
  overlay.button.style.left = `${Math.round(left)}px`;
1980
1878
  overlay.button.style.top = `${Math.round(top)}px`;
1981
1879
  }
@@ -2068,7 +1966,12 @@ class EinblickEditRuntime {
2068
1966
  url.searchParams.set('returnTo', window.location.href);
2069
1967
  url.searchParams.set('sourceType', binding.sourceType ?? 'cms');
2070
1968
  url.searchParams.set('resourceSlug', binding.resourceSlug);
2071
- url.searchParams.set('recordId', binding.recordId);
1969
+ if (binding.recordId) {
1970
+ url.searchParams.set('recordId', binding.recordId);
1971
+ }
1972
+ else if (intent !== 'create') {
1973
+ throw new Error(this.messages.errors.openEditorFailed);
1974
+ }
2072
1975
  if (intent === 'create') {
2073
1976
  url.searchParams.set('intent', intent);
2074
1977
  }
@@ -2077,7 +1980,7 @@ class EinblickEditRuntime {
2077
1980
  }
2078
1981
  return url.toString();
2079
1982
  }
2080
- buildNavigatorUrl(nonce, filter) {
1983
+ buildNavigatorUrl(nonce) {
2081
1984
  const sessionToken = this.getActiveSessionToken();
2082
1985
  if (!sessionToken) {
2083
1986
  throw new Error(this.messages.errors.editSessionExpired);
@@ -2086,15 +1989,6 @@ class EinblickEditRuntime {
2086
1989
  url.searchParams.set('origin', window.location.origin);
2087
1990
  url.searchParams.set('nonce', nonce);
2088
1991
  url.searchParams.set('sessionToken', sessionToken);
2089
- if (filter?.sourceType) {
2090
- url.searchParams.set('sourceType', filter.sourceType);
2091
- }
2092
- if (filter?.resourceSlug) {
2093
- url.searchParams.set('resourceSlug', filter.resourceSlug);
2094
- }
2095
- if (filter?.recordIds && filter.recordIds.length > 0) {
2096
- url.searchParams.set('recordIds', filter.recordIds.join(','));
2097
- }
2098
1992
  return url.toString();
2099
1993
  }
2100
1994
  buildCollectionEditorUrl(args) {
@@ -2119,11 +2013,23 @@ class EinblickEditRuntime {
2119
2013
  return url.toString();
2120
2014
  }
2121
2015
  updateHoverButtonVisibility(element) {
2122
- if (!this.hoverCreateButton) {
2016
+ if (!this.hoverButton || !this.hoverCreateButton) {
2123
2017
  return;
2124
2018
  }
2125
2019
  const binding = element ? readBindingFromElement(element) : null;
2126
- this.hoverCreateButton.hidden = !this.shouldShowCreateButton(element, binding);
2020
+ const canCreate = this.shouldShowCreateButton(element, binding);
2021
+ const canEdit = this.shouldShowEditButton(element, binding);
2022
+ this.hoverButton.hidden = !canEdit;
2023
+ this.hoverCreateButton.hidden = !canCreate;
2024
+ if (this.hoverButtonContainer) {
2025
+ this.hoverButtonContainer.hidden = !canEdit && !canCreate;
2026
+ }
2027
+ }
2028
+ shouldShowEditButton(element, binding) {
2029
+ if (element?.dataset.einblickKind === 'collection') {
2030
+ return false;
2031
+ }
2032
+ return isRecordEditableBinding(binding);
2127
2033
  }
2128
2034
  shouldShowCreateButton(element, binding) {
2129
2035
  if (!element || !binding || !this.state.isAuthenticated) {
@@ -2132,29 +2038,7 @@ class EinblickEditRuntime {
2132
2038
  if (binding.fieldKey) {
2133
2039
  return false;
2134
2040
  }
2135
- if (binding.resourceMode === 'singleton') {
2136
- return false;
2137
- }
2138
- if (binding.resourceMode === 'collection') {
2139
- return true;
2140
- }
2141
- if (element.dataset.einblickKind === 'text') {
2142
- return false;
2143
- }
2144
- const selector = [
2145
- '[data-einblick-editable="true"]',
2146
- `[data-einblick-source-type="${CSS.escape(binding.sourceType ?? 'cms')}"]`,
2147
- `[data-einblick-resource="${CSS.escape(binding.resourceSlug)}"]`,
2148
- ].join('');
2149
- const elements = document.querySelectorAll(selector);
2150
- const recordIds = new Set();
2151
- elements.forEach((candidate) => {
2152
- const recordId = candidate.dataset.einblickRecordId?.trim();
2153
- if (recordId) {
2154
- recordIds.add(recordId);
2155
- }
2156
- });
2157
- return recordIds.size > 1;
2041
+ return element.dataset.einblickKind === 'collection';
2158
2042
  }
2159
2043
  findEditableTarget(target) {
2160
2044
  if (!(target instanceof Element)) {
@@ -2196,6 +2080,9 @@ class EinblickEditRuntime {
2196
2080
  ? [element]
2197
2081
  : [];
2198
2082
  }
2083
+ if (!binding.recordId) {
2084
+ return [];
2085
+ }
2199
2086
  const selector = [
2200
2087
  '[data-einblick-editable="true"]',
2201
2088
  `[data-einblick-source-type="${CSS.escape(binding.sourceType ?? 'cms')}"]`,
@@ -2219,7 +2106,6 @@ class EinblickEditRuntime {
2219
2106
  }
2220
2107
  return (!!this.hoverButtonContainer?.contains(target) ||
2221
2108
  !!this.quickFieldOverlayContainer?.contains(target) ||
2222
- !!this.activeOutline?.contains(target) ||
2223
2109
  !!this.brandBadge?.contains(target) ||
2224
2110
  !!this.brandPopover?.contains(target) ||
2225
2111
  !!this.bottomBarHost?.contains(target) ||
@@ -2230,7 +2116,9 @@ class EinblickEditRuntime {
2230
2116
  '[data-einblick-editable="true"]',
2231
2117
  `[data-einblick-source-type="${CSS.escape(binding.sourceType ?? 'cms')}"]`,
2232
2118
  `[data-einblick-resource="${CSS.escape(binding.resourceSlug)}"]`,
2233
- `[data-einblick-record-id="${CSS.escape(binding.recordId)}"]`,
2119
+ binding.recordId
2120
+ ? `[data-einblick-record-id="${CSS.escape(binding.recordId)}"]`
2121
+ : '',
2234
2122
  binding.fieldKey
2235
2123
  ? `[data-einblick-field-key="${CSS.escape(binding.fieldKey)}"]`
2236
2124
  : '',
@@ -2326,9 +2214,6 @@ class EinblickEditRuntime {
2326
2214
  restoreState.cleanup.push(() => target.removeEventListener('click', handleClick, true), () => target.removeEventListener('keydown', handleKeyDown, true), () => target.removeEventListener('paste', handlePaste, true), () => target.removeEventListener('blur', handleBlur, true));
2327
2215
  this.currentInlineTarget = target;
2328
2216
  this.currentInlineBinding = binding;
2329
- this.ensureActiveOutline();
2330
- this.activeOutline?.setAttribute('data-einblick-inline-active', 'true');
2331
- this.positionOutline(this.activeOutline, target);
2332
2217
  window.setTimeout(() => {
2333
2218
  target.focus({ preventScroll: true });
2334
2219
  this.placeCaretAtEnd(target);
@@ -2354,11 +2239,6 @@ class EinblickEditRuntime {
2354
2239
  this.currentInlineTarget = null;
2355
2240
  this.currentInlineBinding = null;
2356
2241
  this.inlineSaveInFlight = null;
2357
- this.activeOutline?.removeAttribute('data-einblick-inline-active');
2358
- if (!this.activeElement) {
2359
- this.activeOutline?.remove();
2360
- this.activeOutline = null;
2361
- }
2362
2242
  }
2363
2243
  restoreInlineTargetAttributes(target, restoreState) {
2364
2244
  if (restoreState.contentEditable === null) {
@@ -2700,7 +2580,6 @@ class EinblickEditRuntime {
2700
2580
  sourceType: args.sourceType ?? 'cms',
2701
2581
  resourceSlug: args.resourceSlug,
2702
2582
  recordId: args.recordId ?? '',
2703
- resourceMode: 'collection',
2704
2583
  displayMode: 'drawer',
2705
2584
  }
2706
2585
  : null;
@@ -2769,7 +2648,6 @@ export function createEinblickEditRuntime(options) {
2769
2648
  login: (loginOptions) => runtime.login(loginOptions),
2770
2649
  openEditor: (binding, element) => runtime.openEditor(binding, element),
2771
2650
  setLocale: (locale) => runtime.setLocale(locale),
2772
- setResourceHints: (hints) => runtime.setResourceHints(hints),
2773
2651
  };
2774
2652
  }
2775
2653
  //# sourceMappingURL=runtime.js.map