@einblick/sdk 0.6.3 → 0.7.0

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,9 +1,11 @@
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
3
  const RUNTIME_STYLE_ID = 'einblick-edit-runtime-styles';
4
- const EINBLICK_ACCENT_RGB = '250, 105, 62';
5
- const EINBLICK_ACCENT_COLOR = `rgb(${EINBLICK_ACCENT_RGB})`;
6
- const EINBLICK_ACCENT_BORDER = `rgba(${EINBLICK_ACCENT_RGB}, 0.92)`;
4
+ const DEFAULT_EINBLICK_ACCENT_COLOR = 'rgb(250, 105, 62)';
5
+ const EINBLICK_ACCENT_COLOR_VAR = '--einblick-editor-accent-color';
6
+ const EINBLICK_ACCENT_COLOR = `var(${EINBLICK_ACCENT_COLOR_VAR}, ${DEFAULT_EINBLICK_ACCENT_COLOR})`;
7
+ const EINBLICK_ACCENT_TINT = `color-mix(in srgb, ${EINBLICK_ACCENT_COLOR} 12%, transparent)`;
8
+ const EINBLICK_ACCENT_HOVER_COLOR = `color-mix(in srgb, ${EINBLICK_ACCENT_COLOR} 92%, black)`;
7
9
  const CONTROL_BAR_HEIGHT = 44;
8
10
  function getBrandBadgeSvg(size) {
9
11
  return `
@@ -40,6 +42,30 @@ function getMoreActionsSvg(size) {
40
42
  </svg>
41
43
  `;
42
44
  }
45
+ function createDatabaseIconElement(size) {
46
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
47
+ svg.setAttribute('viewBox', '0 0 24 24');
48
+ svg.setAttribute('width', String(size));
49
+ svg.setAttribute('height', String(size));
50
+ svg.setAttribute('fill', 'none');
51
+ svg.setAttribute('stroke', 'currentColor');
52
+ svg.setAttribute('stroke-width', '2');
53
+ svg.setAttribute('stroke-linecap', 'round');
54
+ svg.setAttribute('stroke-linejoin', 'round');
55
+ svg.setAttribute('aria-hidden', 'true');
56
+ svg.setAttribute('focusable', 'false');
57
+ const ellipse = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');
58
+ ellipse.setAttribute('cx', '12');
59
+ ellipse.setAttribute('cy', '5');
60
+ ellipse.setAttribute('rx', '9');
61
+ ellipse.setAttribute('ry', '3');
62
+ const sidePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
63
+ sidePath.setAttribute('d', 'M3 5V19A9 3 0 0 0 21 19V5');
64
+ const middlePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
65
+ middlePath.setAttribute('d', 'M3 12A9 3 0 0 0 21 12');
66
+ svg.append(ellipse, sidePath, middlePath);
67
+ return svg;
68
+ }
43
69
  function createRandomId() {
44
70
  if (typeof crypto !== 'undefined' &&
45
71
  typeof crypto.randomUUID === 'function') {
@@ -54,6 +80,24 @@ function stopEvent(event) {
54
80
  function clamp(value, min, max) {
55
81
  return Math.min(Math.max(value, min), max);
56
82
  }
83
+ function rectsOverlap(first, second, padding = 0) {
84
+ return (first.left < second.right + padding &&
85
+ first.right > second.left - padding &&
86
+ first.top < second.bottom + padding &&
87
+ first.bottom > second.top - padding);
88
+ }
89
+ function normalizeAccentColor(accentColor) {
90
+ const normalized = accentColor?.trim();
91
+ if (!normalized) {
92
+ return null;
93
+ }
94
+ if (typeof CSS !== 'undefined' &&
95
+ typeof CSS.supports === 'function' &&
96
+ !CSS.supports('color', normalized)) {
97
+ return null;
98
+ }
99
+ return normalized;
100
+ }
57
101
  function isTextInputEvent(event) {
58
102
  return (event.key.length === 1 ||
59
103
  event.key === 'Enter' ||
@@ -67,15 +111,37 @@ function fieldTypeAllowsMultiline(binding) {
67
111
  return binding.fieldType === 'text' || binding.fieldType === 'markdown';
68
112
  }
69
113
  function getMediaAcceptValue(binding) {
70
- return binding.fieldType === 'image' || binding.fieldType === 'images'
71
- ? 'image/*'
72
- : '';
114
+ return binding.fieldType === 'image' ? 'image/*' : '';
73
115
  }
74
116
  function getMediaActionLabel(binding, messages) {
75
- return binding.fieldType === 'image' || binding.fieldType === 'images'
117
+ return binding.fieldType === 'image'
76
118
  ? messages.actions.replaceImage
77
119
  : messages.actions.replaceFile;
78
120
  }
121
+ function isMultiMediaBinding(binding) {
122
+ return binding?.fieldType === 'images' || binding?.fieldType === 'files';
123
+ }
124
+ function setOverlayButtonContent(button, label, options) {
125
+ const hasDatabaseIcon = options?.databaseIcon === true;
126
+ if (button.dataset.einblickOverlayButtonLabel === label &&
127
+ button.dataset.einblickOverlayButtonDatabaseIcon ===
128
+ (hasDatabaseIcon ? 'true' : 'false')) {
129
+ return;
130
+ }
131
+ const text = document.createElement('span');
132
+ text.setAttribute('data-einblick-overlay-button-label', 'true');
133
+ text.textContent = label;
134
+ button.replaceChildren();
135
+ if (hasDatabaseIcon) {
136
+ button.append(createDatabaseIconElement(14));
137
+ }
138
+ button.append(text);
139
+ button.title = label;
140
+ button.dataset.einblickOverlayButtonLabel = label;
141
+ button.dataset.einblickOverlayButtonDatabaseIcon = hasDatabaseIcon
142
+ ? 'true'
143
+ : 'false';
144
+ }
79
145
  function injectRuntimeStyles() {
80
146
  if (typeof document === 'undefined') {
81
147
  return;
@@ -91,13 +157,12 @@ function injectRuntimeStyles() {
91
157
  }
92
158
 
93
159
  [data-einblick-editable="true"][data-einblick-hover-active="true"] {
94
- outline: 1px solid ${EINBLICK_ACCENT_BORDER};
160
+ outline: 1px solid ${EINBLICK_ACCENT_COLOR};
95
161
  outline-offset: -1px;
96
- box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.72);
97
162
  }
98
163
 
99
164
  [data-einblick-editable="true"][data-einblick-field-hover-active="true"] {
100
- outline: 1px dashed rgba(${EINBLICK_ACCENT_RGB}, 0.78);
165
+ outline: 1px dashed ${EINBLICK_ACCENT_COLOR};
101
166
  outline-offset: -1px;
102
167
  }
103
168
 
@@ -111,43 +176,46 @@ function injectRuntimeStyles() {
111
176
  z-index: 2147483644;
112
177
  display: inline-flex;
113
178
  align-items: center;
114
- gap: 0;
179
+ gap: 0.25rem;
115
180
  }
116
181
 
117
182
  [data-einblick-edit-button],
118
- [data-einblick-create-button],
183
+ [data-einblick-collection-create-button],
119
184
  [data-einblick-field-button] {
185
+ display: inline-flex;
186
+ align-items: center;
187
+ justify-content: center;
188
+ gap: 0.35rem;
120
189
  border: 1px solid ${EINBLICK_ACCENT_COLOR};
121
190
  border-radius: 0;
122
- min-height: 2rem;
123
- padding: 0 0.72rem;
124
- font: 600 12px/1 system-ui, sans-serif;
125
- letter-spacing: 0;
126
- cursor: pointer;
127
- box-shadow: 0 8px 18px rgba(15, 23, 42, 0.16);
128
- }
129
-
130
- [data-einblick-edit-button],
131
- [data-einblick-field-button="media"] {
191
+ min-width: max-content;
192
+ min-height: 2.05rem;
193
+ max-width: calc(100vw - 0.5rem);
194
+ padding: 0 0.74rem;
195
+ overflow: hidden;
132
196
  background: ${EINBLICK_ACCENT_COLOR};
133
197
  color: rgb(255, 255, 255);
198
+ font: 700 13px/1 system-ui, sans-serif;
199
+ letter-spacing: 0;
200
+ white-space: nowrap;
201
+ text-overflow: ellipsis;
202
+ cursor: pointer;
134
203
  }
135
204
 
136
- [data-einblick-edit-button] {
137
- font-size: 13px;
138
- }
139
-
140
- [data-einblick-create-button],
141
- [data-einblick-field-button="text"] {
142
- background: rgb(255, 255, 255);
143
- color: ${EINBLICK_ACCENT_COLOR};
205
+ [data-einblick-edit-button] svg,
206
+ [data-einblick-collection-create-button] svg,
207
+ [data-einblick-field-button] svg {
208
+ flex: none;
209
+ width: 0.95rem;
210
+ height: 0.95rem;
144
211
  }
145
212
 
146
213
  [data-einblick-edit-button]:hover,
147
- [data-einblick-create-button]:hover,
214
+ [data-einblick-collection-create-button]:hover,
148
215
  [data-einblick-field-button]:hover {
149
- background: rgb(255, 255, 255);
150
- color: ${EINBLICK_ACCENT_COLOR};
216
+ background: ${EINBLICK_ACCENT_COLOR};
217
+ background: ${EINBLICK_ACCENT_HOVER_COLOR};
218
+ color: rgb(255, 255, 255);
151
219
  }
152
220
 
153
221
  [data-einblick-field-overlays] {
@@ -162,8 +230,6 @@ function injectRuntimeStyles() {
162
230
  position: absolute;
163
231
  z-index: 2147483644;
164
232
  pointer-events: auto;
165
- font-size: 11px;
166
- min-height: 1.65rem;
167
233
  }
168
234
 
169
235
  [data-einblick-brand-badge] {
@@ -182,17 +248,15 @@ function injectRuntimeStyles() {
182
248
  background: transparent;
183
249
  color: ${EINBLICK_ACCENT_COLOR};
184
250
  cursor: pointer;
185
- filter: drop-shadow(0 10px 24px rgba(15, 23, 42, 0.22));
186
- transition: transform 120ms ease, filter 120ms ease;
251
+ transition: transform 120ms ease;
187
252
  }
188
253
 
189
254
  [data-einblick-brand-badge]:hover {
190
255
  transform: translateY(-1px) scale(1.04);
191
- filter: drop-shadow(0 12px 28px rgba(15, 23, 42, 0.28));
192
256
  }
193
257
 
194
258
  [data-einblick-brand-badge]:focus-visible {
195
- outline: 2px solid ${EINBLICK_ACCENT_BORDER};
259
+ outline: 2px solid ${EINBLICK_ACCENT_COLOR};
196
260
  outline-offset: 4px;
197
261
  border-radius: 999px;
198
262
  }
@@ -212,7 +276,6 @@ function injectRuntimeStyles() {
212
276
  color: rgb(15, 23, 42);
213
277
  border-radius: 1.25rem;
214
278
  padding: 1rem;
215
- box-shadow: 0 22px 56px rgba(15, 23, 42, 0.2);
216
279
  backdrop-filter: blur(18px);
217
280
  box-sizing: border-box;
218
281
  }
@@ -262,7 +325,8 @@ function injectRuntimeStyles() {
262
325
  display: inline-flex;
263
326
  align-items: center;
264
327
  border-radius: 999px;
265
- background: rgba(${EINBLICK_ACCENT_RGB}, 0.12);
328
+ background: transparent;
329
+ background: ${EINBLICK_ACCENT_TINT};
266
330
  color: ${EINBLICK_ACCENT_COLOR};
267
331
  padding: 0.32rem 0.58rem;
268
332
  font: 600 0.74rem/1 system-ui, sans-serif;
@@ -311,7 +375,8 @@ function injectRuntimeStyles() {
311
375
 
312
376
  [data-einblick-brand-popover-action="primary"]:hover {
313
377
  border-color: ${EINBLICK_ACCENT_COLOR};
314
- background: rgb(237, 92, 49);
378
+ background: ${EINBLICK_ACCENT_COLOR};
379
+ background: ${EINBLICK_ACCENT_HOVER_COLOR};
315
380
  }
316
381
 
317
382
  [data-einblick-inline-editing="true"] {
@@ -337,7 +402,6 @@ function injectRuntimeStyles() {
337
402
  width: min(46rem, 100vw);
338
403
  background: rgb(255, 255, 255);
339
404
  border-left: 1px solid rgba(148, 163, 184, 0.35);
340
- box-shadow: 0 24px 64px rgba(15, 23, 42, 0.26);
341
405
  overflow: hidden;
342
406
  }
343
407
 
@@ -359,7 +423,6 @@ function injectRuntimeStyles() {
359
423
  border-top: 1px solid rgba(148, 163, 184, 0.36);
360
424
  overflow: hidden;
361
425
  background: rgb(255, 255, 255);
362
- box-shadow: 0 -12px 28px rgba(15, 23, 42, 0.12);
363
426
  pointer-events: none;
364
427
  transform: translateY(100%);
365
428
  transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
@@ -396,6 +459,8 @@ class EinblickEditRuntime {
396
459
  reserveBottomSpace;
397
460
  locale;
398
461
  messages;
462
+ accentColor;
463
+ originalRootAccentColor;
399
464
  bridgeNonce = createRandomId();
400
465
  session;
401
466
  state = {
@@ -409,7 +474,6 @@ class EinblickEditRuntime {
409
474
  activeElement = null;
410
475
  hoverButtonContainer = null;
411
476
  hoverButton = null;
412
- hoverCreateButton = null;
413
477
  quickFieldOverlayContainer = null;
414
478
  quickFieldOverlays = [];
415
479
  brandBadge = null;
@@ -455,6 +519,7 @@ class EinblickEditRuntime {
455
519
  options.reserveBottomSpace ?? this.chrome === 'bar';
456
520
  this.locale = resolveEinblickSdkLocale(options.locale);
457
521
  this.messages = getEinblickSdkMessages(this.locale);
522
+ this.accentColor = normalizeAccentColor(options.accentColor);
458
523
  this.session =
459
524
  options.initialSession && !isEditSessionExpired(options.initialSession)
460
525
  ? options.initialSession
@@ -475,6 +540,43 @@ class EinblickEditRuntime {
475
540
  this.messages = getEinblickSdkMessages(nextLocale);
476
541
  this.refreshLocalizedUi();
477
542
  }
543
+ setAccentColor(accentColor) {
544
+ const nextAccentColor = normalizeAccentColor(accentColor);
545
+ if (nextAccentColor === this.accentColor) {
546
+ return;
547
+ }
548
+ this.accentColor = nextAccentColor;
549
+ this.applyAccentColor();
550
+ }
551
+ applyAccentColor() {
552
+ if (typeof document === 'undefined') {
553
+ return;
554
+ }
555
+ if (!this.accentColor) {
556
+ this.restoreAccentColor();
557
+ return;
558
+ }
559
+ const root = document.documentElement;
560
+ if (this.originalRootAccentColor === undefined) {
561
+ this.originalRootAccentColor =
562
+ root.style.getPropertyValue(EINBLICK_ACCENT_COLOR_VAR) || null;
563
+ }
564
+ root.style.setProperty(EINBLICK_ACCENT_COLOR_VAR, this.accentColor);
565
+ }
566
+ restoreAccentColor() {
567
+ if (typeof document === 'undefined' ||
568
+ this.originalRootAccentColor === undefined) {
569
+ return;
570
+ }
571
+ const root = document.documentElement;
572
+ if (this.originalRootAccentColor) {
573
+ root.style.setProperty(EINBLICK_ACCENT_COLOR_VAR, this.originalRootAccentColor);
574
+ }
575
+ else {
576
+ root.style.removeProperty(EINBLICK_ACCENT_COLOR_VAR);
577
+ }
578
+ this.originalRootAccentColor = undefined;
579
+ }
478
580
  start() {
479
581
  if (this.started ||
480
582
  typeof window === 'undefined' ||
@@ -482,6 +584,7 @@ class EinblickEditRuntime {
482
584
  return;
483
585
  }
484
586
  this.started = true;
587
+ this.applyAccentColor();
485
588
  injectRuntimeStyles();
486
589
  this.ensureHoverButton();
487
590
  if (this.chrome === 'badge') {
@@ -493,6 +596,7 @@ class EinblickEditRuntime {
493
596
  window.addEventListener('message', this.handleMessage);
494
597
  window.addEventListener('pointerdown', this.handleGlobalPointerDown, true);
495
598
  window.addEventListener('pointermove', this.handlePointerMove, true);
599
+ window.addEventListener('click', this.handleCollectionCreateButtonClick, true);
496
600
  window.addEventListener('scroll', this.handleViewportChange, true);
497
601
  window.addEventListener('resize', this.handleViewportChange);
498
602
  window.addEventListener('blur', this.handleWindowBlur);
@@ -508,12 +612,14 @@ class EinblickEditRuntime {
508
612
  this.closeInlineEditor();
509
613
  }
510
614
  this.updateBottomBarResources();
615
+ this.updateCollectionCreateButtons();
511
616
  });
512
617
  this.mutationObserver.observe(document.body, {
513
618
  childList: true,
514
619
  subtree: true,
515
620
  });
516
621
  this.reloadBridge();
622
+ this.updateCollectionCreateButtons();
517
623
  }
518
624
  destroy() {
519
625
  if (this.destroyed) {
@@ -524,6 +630,7 @@ class EinblickEditRuntime {
524
630
  window.removeEventListener('message', this.handleMessage);
525
631
  window.removeEventListener('pointerdown', this.handleGlobalPointerDown, true);
526
632
  window.removeEventListener('pointermove', this.handlePointerMove, true);
633
+ window.removeEventListener('click', this.handleCollectionCreateButtonClick, true);
527
634
  window.removeEventListener('scroll', this.handleViewportChange, true);
528
635
  window.removeEventListener('resize', this.handleViewportChange);
529
636
  window.removeEventListener('blur', this.handleWindowBlur);
@@ -543,8 +650,6 @@ class EinblickEditRuntime {
543
650
  this.hoverButtonContainer = null;
544
651
  this.hoverButton?.remove();
545
652
  this.hoverButton = null;
546
- this.hoverCreateButton?.remove();
547
- this.hoverCreateButton = null;
548
653
  this.clearQuickFieldOverlays();
549
654
  this.brandBadge?.remove();
550
655
  this.brandBadge = null;
@@ -567,6 +672,7 @@ class EinblickEditRuntime {
567
672
  this.closeInlineEditor();
568
673
  this.removeNavigatorPanel(false);
569
674
  this.closeDrawer();
675
+ this.restoreAccentColor();
570
676
  if (this.pendingLogin) {
571
677
  if (this.pendingLogin.closePollId !== null) {
572
678
  window.clearInterval(this.pendingLogin.closePollId);
@@ -597,9 +703,6 @@ class EinblickEditRuntime {
597
703
  if (this.hoverButton) {
598
704
  this.updateHoverButtonLabel();
599
705
  }
600
- if (this.hoverCreateButton) {
601
- this.hoverCreateButton.textContent = this.messages.actions.addRecord;
602
- }
603
706
  if (this.brandBadge) {
604
707
  this.brandBadge.setAttribute('aria-label', this.messages.brand.quickActionsLabel);
605
708
  this.brandBadge.title = this.messages.brand.quickActionsLabel;
@@ -616,6 +719,7 @@ class EinblickEditRuntime {
616
719
  }
617
720
  this.updateBrandPopover();
618
721
  this.updateBottomBar();
722
+ this.updateCollectionCreateButtons();
619
723
  this.updateHoverChrome();
620
724
  }
621
725
  async login(options) {
@@ -743,22 +847,31 @@ class EinblickEditRuntime {
743
847
  !this.shouldShowEditButton(this.activeElement, binding)) {
744
848
  return;
745
849
  }
850
+ if (binding.fieldKey && isMultiMediaBinding(binding)) {
851
+ this.openDrawer(this.activeElement, binding);
852
+ return;
853
+ }
746
854
  if (binding.fieldKey && isMediaEditableBinding(binding)) {
747
855
  this.openMediaPicker(this.activeElement, binding, this.hoverButton);
748
856
  return;
749
857
  }
750
858
  this.openEditor(binding, this.activeElement);
751
859
  };
752
- handleCreateButtonClick = (event) => {
860
+ handleCollectionCreateButtonClick = (event) => {
861
+ const button = this.findCollectionCreateButton(event.target);
862
+ if (!button) {
863
+ return;
864
+ }
753
865
  stopEvent(event);
754
- if (!this.activeElement) {
866
+ const collection = this.findCollectionForCreateButton(button);
867
+ if (!collection) {
755
868
  return;
756
869
  }
757
- const binding = readBindingFromElement(this.activeElement);
758
- if (!binding || !this.shouldShowCreateButton(this.activeElement, binding)) {
870
+ const binding = readBindingFromElement(collection);
871
+ if (!binding || !this.shouldShowCreateButton(collection, binding)) {
759
872
  return;
760
873
  }
761
- this.openDrawer(this.activeElement, binding, 'create');
874
+ this.openDrawer(collection, binding, 'create');
762
875
  };
763
876
  handleGlobalPointerDown = (event) => {
764
877
  const target = event.target;
@@ -1044,6 +1157,7 @@ class EinblickEditRuntime {
1044
1157
  this.updateBrandBadge();
1045
1158
  this.updateBrandPopover();
1046
1159
  this.updateBottomBar();
1160
+ this.updateCollectionCreateButtons();
1047
1161
  if (!nextState.isAuthenticated) {
1048
1162
  this.setActiveElement(null);
1049
1163
  this.removeNavigatorPanel();
@@ -1058,26 +1172,18 @@ class EinblickEditRuntime {
1058
1172
  container.hidden = true;
1059
1173
  const editButton = document.createElement('button');
1060
1174
  editButton.type = 'button';
1061
- editButton.textContent = this.messages.actions.editElement;
1175
+ setOverlayButtonContent(editButton, this.messages.actions.editElement, {
1176
+ databaseIcon: true,
1177
+ });
1062
1178
  editButton.setAttribute('data-einblick-edit-button', 'true');
1063
1179
  editButton.addEventListener('pointerdown', (event) => {
1064
1180
  event.stopPropagation();
1065
1181
  });
1066
1182
  editButton.addEventListener('click', this.handleHoverButtonClick);
1067
- const createButton = document.createElement('button');
1068
- createButton.type = 'button';
1069
- createButton.textContent = this.messages.actions.addRecord;
1070
- createButton.hidden = true;
1071
- createButton.setAttribute('data-einblick-create-button', 'true');
1072
- createButton.addEventListener('pointerdown', (event) => {
1073
- event.stopPropagation();
1074
- });
1075
- createButton.addEventListener('click', this.handleCreateButtonClick);
1076
- container.append(editButton, createButton);
1183
+ container.append(editButton);
1077
1184
  document.body.append(container);
1078
1185
  this.hoverButtonContainer = container;
1079
1186
  this.hoverButton = editButton;
1080
- this.hoverCreateButton = createButton;
1081
1187
  }
1082
1188
  ensureBrandBadge() {
1083
1189
  if ((this.brandBadge && this.brandPopover) ||
@@ -1254,7 +1360,7 @@ class EinblickEditRuntime {
1254
1360
  border: 0;
1255
1361
  border-radius: 0.35rem;
1256
1362
  background: #ffffff;
1257
- color: #fa693e;
1363
+ color: ${EINBLICK_ACCENT_COLOR};
1258
1364
  padding: 0 0.7rem;
1259
1365
  font: 700 0.78rem/1 system-ui, sans-serif;
1260
1366
  letter-spacing: 0;
@@ -1281,11 +1387,6 @@ class EinblickEditRuntime {
1281
1387
  outline-offset: 2px;
1282
1388
  }
1283
1389
 
1284
- .secondary {
1285
- border-color: transparent;
1286
- background: transparent;
1287
- }
1288
-
1289
1390
  .menu {
1290
1391
  position: absolute;
1291
1392
  right: 0.5rem;
@@ -1299,7 +1400,6 @@ class EinblickEditRuntime {
1299
1400
  border-radius: 0.75rem;
1300
1401
  background: rgb(255, 255, 255);
1301
1402
  color: rgb(15, 23, 42);
1302
- box-shadow: 0 14px 32px rgba(15, 23, 42, 0.16);
1303
1403
  overflow: hidden;
1304
1404
  }
1305
1405
 
@@ -1367,7 +1467,7 @@ class EinblickEditRuntime {
1367
1467
  resourceList.className = 'resources';
1368
1468
  const moreButton = document.createElement('button');
1369
1469
  moreButton.type = 'button';
1370
- moreButton.className = 'secondary icon-button';
1470
+ moreButton.className = 'icon-button';
1371
1471
  moreButton.setAttribute('aria-haspopup', 'menu');
1372
1472
  moreButton.title = this.messages.actions.moreActions;
1373
1473
  moreButton.innerHTML = getMoreActionsSvg(20);
@@ -1741,9 +1841,6 @@ class EinblickEditRuntime {
1741
1841
  if (this.hoverButton) {
1742
1842
  this.hoverButton.hidden = true;
1743
1843
  }
1744
- if (this.hoverCreateButton) {
1745
- this.hoverCreateButton.hidden = true;
1746
- }
1747
1844
  return;
1748
1845
  }
1749
1846
  element.dataset.einblickHoverActive = 'true';
@@ -1779,6 +1876,10 @@ class EinblickEditRuntime {
1779
1876
  }
1780
1877
  this.updateHoverButtonLabel();
1781
1878
  this.updateHoverButtonVisibility(this.activeElement);
1879
+ if (this.hoverButtonContainer?.hidden) {
1880
+ this.updateQuickFieldOverlayPositions();
1881
+ return;
1882
+ }
1782
1883
  this.positionHoverControls(rect);
1783
1884
  this.updateQuickFieldOverlayPositions();
1784
1885
  }
@@ -1827,10 +1928,9 @@ class EinblickEditRuntime {
1827
1928
  const button = document.createElement('button');
1828
1929
  button.type = 'button';
1829
1930
  button.setAttribute('data-einblick-field-button', isMediaBinding ? 'media' : 'text');
1830
- button.textContent =
1831
- isMediaBinding
1832
- ? getMediaActionLabel(binding, this.messages)
1833
- : this.messages.actions.editText;
1931
+ setOverlayButtonContent(button, isMediaBinding
1932
+ ? getMediaActionLabel(binding, this.messages)
1933
+ : this.messages.actions.editText);
1834
1934
  button.addEventListener('pointerdown', (event) => {
1835
1935
  event.stopPropagation();
1836
1936
  });
@@ -1872,8 +1972,37 @@ class EinblickEditRuntime {
1872
1972
  const height = overlay.button.offsetHeight || 26;
1873
1973
  const scrollX = window.scrollX || document.documentElement.scrollLeft;
1874
1974
  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));
1975
+ let left = clamp(rect.right + scrollX - width, scrollX, Math.max(scrollX, scrollX + window.innerWidth - width));
1976
+ let top = clamp(rect.top + scrollY, scrollY, Math.max(scrollY, scrollY + window.innerHeight - height));
1977
+ const hoverControlsRect = this.hoverButtonContainer && !this.hoverButtonContainer.hidden
1978
+ ? this.hoverButtonContainer.getBoundingClientRect()
1979
+ : null;
1980
+ if (hoverControlsRect) {
1981
+ const candidateRect = {
1982
+ left: left - scrollX,
1983
+ top: top - scrollY,
1984
+ right: left - scrollX + width,
1985
+ bottom: top - scrollY + height,
1986
+ };
1987
+ if (rectsOverlap(candidateRect, hoverControlsRect, 6)) {
1988
+ const minTop = scrollY;
1989
+ const maxTop = Math.max(scrollY, scrollY + window.innerHeight - height);
1990
+ const belowTop = hoverControlsRect.bottom + scrollY + 6;
1991
+ const aboveTop = hoverControlsRect.top + scrollY - height - 6;
1992
+ if (belowTop <= maxTop) {
1993
+ top = clamp(belowTop, minTop, maxTop);
1994
+ }
1995
+ else if (aboveTop >= minTop) {
1996
+ top = clamp(aboveTop, minTop, maxTop);
1997
+ }
1998
+ else {
1999
+ const maxLeft = Math.max(scrollX, scrollX + window.innerWidth - width);
2000
+ const leftOfControls = hoverControlsRect.left + scrollX - width - 6;
2001
+ const rightOfControls = hoverControlsRect.right + scrollX + 6;
2002
+ left = clamp(leftOfControls >= scrollX ? leftOfControls : rightOfControls, scrollX, maxLeft);
2003
+ }
2004
+ }
2005
+ }
1877
2006
  overlay.button.style.left = `${Math.round(left)}px`;
1878
2007
  overlay.button.style.top = `${Math.round(top)}px`;
1879
2008
  }
@@ -1884,14 +2013,19 @@ class EinblickEditRuntime {
1884
2013
  }
1885
2014
  const binding = readBindingFromElement(this.activeElement);
1886
2015
  if (!binding?.fieldKey) {
1887
- this.hoverButton.textContent = this.messages.actions.editElement;
2016
+ setOverlayButtonContent(this.hoverButton, this.messages.actions.editElement, { databaseIcon: true });
1888
2017
  return;
1889
2018
  }
1890
- this.hoverButton.textContent = isMediaEditableBinding(binding)
2019
+ const opensDrawer = isMultiMediaBinding(binding) ||
2020
+ (!isMediaEditableBinding(binding) && !shouldUseInlineEditor(binding));
2021
+ const label = isMediaEditableBinding(binding)
1891
2022
  ? getMediaActionLabel(binding, this.messages)
1892
2023
  : shouldUseInlineEditor(binding)
1893
2024
  ? this.messages.actions.editText
1894
- : this.messages.actions.edit;
2025
+ : this.messages.actions.editElement;
2026
+ setOverlayButtonContent(this.hoverButton, label, {
2027
+ databaseIcon: opensDrawer,
2028
+ });
1895
2029
  }
1896
2030
  reloadBridge() {
1897
2031
  this.clearBridgePing();
@@ -2013,16 +2147,51 @@ class EinblickEditRuntime {
2013
2147
  return url.toString();
2014
2148
  }
2015
2149
  updateHoverButtonVisibility(element) {
2016
- if (!this.hoverButton || !this.hoverCreateButton) {
2150
+ if (!this.hoverButton) {
2017
2151
  return;
2018
2152
  }
2019
2153
  const binding = element ? readBindingFromElement(element) : null;
2020
- const canCreate = this.shouldShowCreateButton(element, binding);
2021
2154
  const canEdit = this.shouldShowEditButton(element, binding);
2022
2155
  this.hoverButton.hidden = !canEdit;
2023
- this.hoverCreateButton.hidden = !canCreate;
2024
2156
  if (this.hoverButtonContainer) {
2025
- this.hoverButtonContainer.hidden = !canEdit && !canCreate;
2157
+ this.hoverButtonContainer.hidden = !canEdit;
2158
+ }
2159
+ }
2160
+ findCollectionCreateButton(target) {
2161
+ const element = target instanceof Element
2162
+ ? target
2163
+ : target instanceof Node
2164
+ ? target.parentElement
2165
+ : null;
2166
+ if (!element) {
2167
+ return null;
2168
+ }
2169
+ return element.closest('[data-einblick-collection-create-button="true"]');
2170
+ }
2171
+ findCollectionForCreateButton(button) {
2172
+ return button.closest('[data-einblick-editable="true"][data-einblick-kind="collection"]');
2173
+ }
2174
+ updateCollectionCreateButtons() {
2175
+ if (typeof document === 'undefined') {
2176
+ return;
2177
+ }
2178
+ const buttons = Array.from(document.querySelectorAll('[data-einblick-collection-create-button="true"]'));
2179
+ for (const button of buttons) {
2180
+ const label = button.dataset.einblickCollectionCreateLabel?.trim() ||
2181
+ this.messages.actions.addRecord;
2182
+ setOverlayButtonContent(button, label, { databaseIcon: true });
2183
+ button.type = 'button';
2184
+ const collection = this.findCollectionForCreateButton(button);
2185
+ const binding = collection ? readBindingFromElement(collection) : null;
2186
+ const isVisible = !!collection &&
2187
+ !!binding &&
2188
+ this.shouldShowCreateButton(collection, binding);
2189
+ button.hidden = !isVisible;
2190
+ button.disabled = !isVisible;
2191
+ const wrapper = button.closest('[data-einblick-collection-create-wrapper="true"]');
2192
+ if (wrapper) {
2193
+ wrapper.hidden = !isVisible;
2194
+ }
2026
2195
  }
2027
2196
  }
2028
2197
  shouldShowEditButton(element, binding) {
@@ -2038,7 +2207,10 @@ class EinblickEditRuntime {
2038
2207
  if (binding.fieldKey) {
2039
2208
  return false;
2040
2209
  }
2041
- return element.dataset.einblickKind === 'collection';
2210
+ if (element.dataset.einblickKind !== 'collection') {
2211
+ return false;
2212
+ }
2213
+ return true;
2042
2214
  }
2043
2215
  findEditableTarget(target) {
2044
2216
  if (!(target instanceof Element)) {
@@ -2076,9 +2248,7 @@ class EinblickEditRuntime {
2076
2248
  return [];
2077
2249
  }
2078
2250
  if (binding.fieldKey) {
2079
- return shouldUseInlineEditor(binding) || isMediaEditableBinding(binding)
2080
- ? [element]
2081
- : [];
2251
+ return [];
2082
2252
  }
2083
2253
  if (!binding.recordId) {
2084
2254
  return [];
@@ -2109,7 +2279,8 @@ class EinblickEditRuntime {
2109
2279
  !!this.brandBadge?.contains(target) ||
2110
2280
  !!this.brandPopover?.contains(target) ||
2111
2281
  !!this.bottomBarHost?.contains(target) ||
2112
- !!this.bottomBarMoreMenu?.contains(target));
2282
+ !!this.bottomBarMoreMenu?.contains(target) ||
2283
+ !!this.findCollectionCreateButton(target));
2113
2284
  }
2114
2285
  findElementForBinding(binding) {
2115
2286
  const selector = [
@@ -2361,18 +2532,21 @@ class EinblickEditRuntime {
2361
2532
  this.callbacks.onError?.(this.messages.errors.fieldBindingRequired);
2362
2533
  return;
2363
2534
  }
2535
+ if (isMultiMediaBinding(binding)) {
2536
+ this.openDrawer(target, binding);
2537
+ return;
2538
+ }
2364
2539
  const input = document.createElement('input');
2365
2540
  input.type = 'file';
2366
2541
  input.accept = getMediaAcceptValue(binding);
2367
2542
  input.style.position = 'fixed';
2368
2543
  input.style.left = '-9999px';
2369
2544
  input.style.top = '0';
2370
- const previousText = button?.textContent ?? '';
2545
+ const buttonLabel = getMediaActionLabel(binding, this.messages);
2371
2546
  const resetButton = () => {
2372
2547
  if (button) {
2373
2548
  button.disabled = false;
2374
- button.textContent =
2375
- previousText || getMediaActionLabel(binding, this.messages);
2549
+ setOverlayButtonContent(button, buttonLabel);
2376
2550
  }
2377
2551
  };
2378
2552
  input.addEventListener('change', () => {
@@ -2383,7 +2557,7 @@ class EinblickEditRuntime {
2383
2557
  }
2384
2558
  if (button) {
2385
2559
  button.disabled = true;
2386
- button.textContent = this.messages.actions.uploading;
2560
+ setOverlayButtonContent(button, this.messages.actions.uploading);
2387
2561
  }
2388
2562
  void (async () => {
2389
2563
  try {
@@ -2648,6 +2822,7 @@ export function createEinblickEditRuntime(options) {
2648
2822
  login: (loginOptions) => runtime.login(loginOptions),
2649
2823
  openEditor: (binding, element) => runtime.openEditor(binding, element),
2650
2824
  setLocale: (locale) => runtime.setLocale(locale),
2825
+ setAccentColor: (accentColor) => runtime.setAccentColor(accentColor),
2651
2826
  };
2652
2827
  }
2653
2828
  //# sourceMappingURL=runtime.js.map