@teambit/compositions 1.0.994 → 1.0.995

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.
@@ -6,6 +6,7 @@ import type {
6
6
  CompositionsMenuSlot,
7
7
  EmptyStateSlot,
8
8
  UsePreviewSandboxSlot,
9
+ UsePreviewPropsSlot,
9
10
  } from './compositions.ui.runtime';
10
11
 
11
12
  type Options = { menuBarWidgetSlot: CompositionsMenuSlot };
@@ -18,7 +19,8 @@ export class CompositionsSection implements Section {
18
19
  private compositions: CompositionsUI,
19
20
  private options: Options,
20
21
  private emptyStateSlot: EmptyStateSlot,
21
- private usePreviewSandboxSlot: UsePreviewSandboxSlot
22
+ private usePreviewSandboxSlot: UsePreviewSandboxSlot,
23
+ private usePreviewPropsSlot: UsePreviewPropsSlot
22
24
  ) {}
23
25
 
24
26
  navigationLink = {
@@ -33,6 +35,7 @@ export class CompositionsSection implements Section {
33
35
  menuBarWidgets={this.options.menuBarWidgetSlot}
34
36
  emptyState={this.emptyStateSlot}
35
37
  usePreviewSandboxSlot={this.usePreviewSandboxSlot}
38
+ usePreviewPropsSlot={this.usePreviewPropsSlot}
36
39
  enableLiveControls
37
40
  />
38
41
  ),
@@ -146,20 +146,37 @@
146
146
  border-top: 1px solid var(--bit-border-color-lightest, #eaeaec);
147
147
  background: var(--bit-bg-color, #ffffff);
148
148
  overflow: hidden;
149
+ min-height: 0;
150
+ // Never overflow the previewArea: if the inline `height` (or a user-set
151
+ // resize) ends up taller than the parent allows (e.g. browser zoom shrinks
152
+ // available space, or there are many controls), `max-height` wins and the
153
+ // tray stays inside its container so `trayBody`'s `overflow-y: auto` keeps
154
+ // working instead of the bottom rows getting clipped off-screen.
155
+ max-height: calc(100% - 80px);
156
+ box-sizing: border-box;
149
157
  }
150
158
 
151
- .trayDragHandle {
159
+ .controlsTrayCollapsed {
160
+ // when collapsed, only show the header bar
161
+ height: auto;
162
+ }
163
+
164
+ // ─── Two-zone header: resize strip on top, click-to-collapse below ─────
165
+ // The two zones are visually separated by a divider so the boundary is
166
+ // obvious: top = "grab to resize", bottom = "click to collapse".
167
+
168
+ .trayResizeStrip {
169
+ position: relative;
152
170
  flex-shrink: 0;
153
- height: 8px;
154
- display: flex;
155
- align-items: center;
156
- justify-content: center;
171
+ height: 12px;
157
172
  cursor: ns-resize;
158
- background: transparent;
159
- transition: background 120ms ease;
173
+ user-select: none;
174
+ background: var(--surface01-color, rgba(0, 0, 0, 0.02));
175
+ border-bottom: 1px solid var(--bit-border-color-lightest, #eaeaec);
176
+ transition: background 140ms ease;
160
177
 
161
178
  &:hover {
162
- background: rgba(0, 0, 0, 0.03);
179
+ background: var(--surface-hover-color, rgba(0, 0, 0, 0.05));
163
180
  }
164
181
 
165
182
  &:hover .trayDragBar {
@@ -169,19 +186,36 @@
169
186
  }
170
187
 
171
188
  .trayDragBar {
189
+ position: absolute;
190
+ top: 50%;
191
+ left: 50%;
192
+ transform: translate(-50%, -50%);
172
193
  width: 32px;
173
194
  height: 3px;
174
195
  border-radius: 2px;
175
196
  background: var(--bit-border-color-lightest, #d0d0d3);
176
197
  transition: all 140ms ease;
198
+ pointer-events: none;
177
199
  }
178
200
 
179
- .trayHeader {
180
- flex-shrink: 0;
201
+ .trayHeaderInner {
181
202
  display: flex;
182
203
  align-items: center;
183
204
  justify-content: space-between;
184
- padding: 4px 14px 8px;
205
+ padding: 8px 14px;
206
+ cursor: pointer;
207
+ user-select: none;
208
+ outline: none;
209
+ transition: background 140ms ease;
210
+
211
+ &:hover {
212
+ background: var(--surface-hover-color, rgba(0, 0, 0, 0.03));
213
+ }
214
+ }
215
+
216
+ // give the collapsed bar a bit more breathing room
217
+ .controlsTrayCollapsed .trayHeaderInner {
218
+ padding: 10px 14px;
185
219
  }
186
220
 
187
221
  .trayTitleRow {
@@ -218,29 +252,31 @@
218
252
  line-height: 1;
219
253
  }
220
254
 
221
- .trayClose {
222
- display: flex;
255
+ .trayCollapseIcon {
256
+ display: inline-flex;
223
257
  align-items: center;
224
258
  justify-content: center;
225
- width: 24px;
226
- height: 24px;
227
- border-radius: 4px;
228
- border: none;
229
- background: transparent;
259
+ width: 20px;
260
+ height: 20px;
261
+ font-size: 10px;
230
262
  color: var(--bit-text-color-light, #8b8d98);
231
- cursor: pointer;
232
- font-size: 12px;
233
- padding: 0;
234
- transition: all 120ms ease;
263
+ transform: rotate(90deg);
264
+ transform-origin: center;
265
+ transition: transform 200ms ease-in-out;
266
+ pointer-events: none;
267
+ }
235
268
 
236
- &:hover {
237
- background: rgba(0, 0, 0, 0.06);
238
- color: var(--bit-text-color-heavy, #1c2024);
239
- }
269
+ .trayCollapseIconCollapsed {
270
+ transform: rotate(-90deg);
240
271
  }
241
272
 
242
273
  .trayBody {
243
- flex: 1;
274
+ // `flex-basis: auto` so the body contributes its natural content height to
275
+ // the tray's intrinsic size — without this, an auto-sized tray collapses to
276
+ // just the header. `min-height: 0` keeps `overflow-y: auto` working when
277
+ // the parent's `max-height` clamps the tray.
278
+ flex: 1 1 auto;
279
+ min-height: 0;
244
280
  overflow-y: auto;
245
281
  overflow-x: hidden;
246
282
  }
package/compositions.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import React, { useContext, useEffect, useState, useMemo, useRef, useCallback } from 'react';
3
3
  import { useParams, useSearchParams } from 'react-router-dom';
4
+ import classNames from 'classnames';
4
5
  import head from 'lodash.head';
5
6
  import queryString from 'query-string';
6
7
  import { ThemeContext } from '@teambit/documenter.theme.theme-context';
@@ -13,7 +14,12 @@ import { Tab, TabContainer, TabList, TabPanel } from '@teambit/panels';
13
14
  import { useDocs } from '@teambit/docs.ui.queries.get-docs';
14
15
  import { Collapser } from '@teambit/ui-foundation.ui.buttons.collapser';
15
16
  import { EmptyBox } from '@teambit/design.ui.empty-box';
16
- import { SandboxPermissionsAggregator, toPreviewUrl } from '@teambit/preview.ui.component-preview';
17
+ import type { PreviewIframeAttrs } from '@teambit/preview.ui.component-preview';
18
+ import {
19
+ PreviewPropsAggregator,
20
+ SandboxPermissionsAggregator,
21
+ toPreviewUrl,
22
+ } from '@teambit/preview.ui.component-preview';
17
23
  import { useIsMobile } from '@teambit/ui-foundation.ui.hooks.use-is-mobile';
18
24
  import { CompositionsMenuBar } from '@teambit/compositions.ui.compositions-menu-bar';
19
25
  import { CompositionContextProvider } from '@teambit/compositions.ui.hooks.use-composition';
@@ -26,8 +32,14 @@ import { OptionButton } from '@teambit/design.ui.input.option-button';
26
32
  import { StatusMessageCard } from '@teambit/design.ui.surfaces.status-message-card';
27
33
  import { Tooltip } from '@teambit/design.ui.tooltip';
28
34
  import { Icon } from '@teambit/evangelist.elements.icon';
35
+ import type { UseLiveControlsResult } from '@teambit/compositions.ui.composition-live-controls';
29
36
  import { useLiveControls } from '@teambit/compositions.ui.composition-live-controls';
30
- import type { EmptyStateSlot, CompositionsMenuSlot, UsePreviewSandboxSlot } from './compositions.ui.runtime';
37
+ import type {
38
+ EmptyStateSlot,
39
+ CompositionsMenuSlot,
40
+ UsePreviewSandboxSlot,
41
+ UsePreviewPropsSlot,
42
+ } from './compositions.ui.runtime';
31
43
  import type { Composition } from './composition';
32
44
  import styles from './compositions.module.scss';
33
45
  import { ComponentComposition } from './ui';
@@ -47,6 +59,13 @@ export type CompositionsProp = {
47
59
  menuBarWidgets?: CompositionsMenuSlot;
48
60
  emptyState?: EmptyStateSlot;
49
61
  usePreviewSandboxSlot?: UsePreviewSandboxSlot;
62
+ /**
63
+ * per-component resolvers for iframe attributes on the composition preview (`allow`,
64
+ * `referrerPolicy`, ...). Each resolver gets the current `ComponentModel`; results merge
65
+ * with later registrations winning. Default `allow` (`clipboard-write`) lives on
66
+ * `ComponentPreview` and applies when no resolver overrides it.
67
+ */
68
+ usePreviewPropsSlot?: UsePreviewPropsSlot;
50
69
  enableLiveControls?: boolean;
51
70
  };
52
71
 
@@ -54,6 +73,7 @@ export function Compositions({
54
73
  menuBarWidgets,
55
74
  emptyState,
56
75
  usePreviewSandboxSlot,
76
+ usePreviewPropsSlot,
57
77
  enableLiveControls = true,
58
78
  }: CompositionsProp) {
59
79
  const component = useContext(ComponentContext);
@@ -74,6 +94,8 @@ export function Compositions({
74
94
 
75
95
  const properties = useDocs(component.id);
76
96
  const previewSandboxHooks = usePreviewSandboxSlot?.values() ?? [];
97
+ const previewPropsHooks = usePreviewPropsSlot?.values() ?? [];
98
+ const [previewAttrs, setPreviewAttrs] = useState<PreviewIframeAttrs>({});
77
99
  const isMobile = useIsMobile();
78
100
  const showSidebar = !isMobile && component.compositions.length > 0;
79
101
  const [isSidebarOpen, setSidebarOpenness] = useState(showSidebar);
@@ -97,28 +119,50 @@ export function Compositions({
97
119
 
98
120
  const queryParams = useMemo(() => queryString.stringify(compositionParams), [compositionParams]);
99
121
 
100
- const [controlsTrayOpen, setControlsTrayOpen] = useState(false);
122
+ // Tracks compositions the user has explicitly collapsed. Anything not in
123
+ // this set defaults to expanded, which gives us "auto-open on load" as a
124
+ // pure derivation rather than an effect.
125
+ const [collapsedCompositions, setCollapsedCompositions] = useState<Set<string>>(() => new Set());
101
126
  const [isDraggingTray, setIsDraggingTray] = useState(false);
102
- const trayRef = useRef<HTMLDivElement>(null);
103
- const trayHeightRef = useRef(260);
127
+ const trayRef = useRef<HTMLDivElement | null>(null);
128
+ // `null` = auto-size to content (default). Becomes a number once the user
129
+ // drag-resizes — only then do we pin a fixed height.
130
+ const [trayHeight, setTrayHeight] = useState<number | null>(null);
104
131
  const { ready, defs, values, onChange } = useLiveControls();
105
132
 
106
- const onTrayDragStart = useCallback((e: React.MouseEvent) => {
133
+ const onResizeStripMouseDown = useCallback((e: React.MouseEvent) => {
134
+ if (e.button !== 0) return;
107
135
  e.preventDefault();
108
136
  const startY = e.clientY;
109
- const startHeight = trayHeightRef.current;
137
+ // Seed the drag from the tray's current rendered height — handles the
138
+ // initial auto-sized case (where `trayHeight` is still null).
139
+ const startHeight = trayRef.current?.offsetHeight ?? 0;
140
+ const parentEl = trayRef.current?.parentElement;
141
+ const parentHeight = parentEl?.clientHeight ?? window.innerHeight;
142
+ const maxHeight = Math.max(120, parentHeight - 80);
110
143
  setIsDraggingTray(true);
111
144
  document.body.style.cursor = 'ns-resize';
112
145
  document.body.style.userSelect = 'none';
146
+ // Mutate the tray's inline height directly and rAF-throttle, so dragging
147
+ // doesn't re-render the whole compositions tree (including the iframe)
148
+ // on every mousemove. State is committed once on mouseup.
149
+ let pendingHeight = startHeight;
150
+ let rafScheduled = false;
151
+ const applyHeight = () => {
152
+ rafScheduled = false;
153
+ if (trayRef.current) trayRef.current.style.height = `${pendingHeight}px`;
154
+ };
113
155
  const onMove = (ev: MouseEvent) => {
114
- const next = Math.max(120, Math.min(600, startHeight + (startY - ev.clientY)));
115
- trayHeightRef.current = next;
116
- if (trayRef.current) {
117
- trayRef.current.style.maxHeight = `${next}px`;
156
+ const delta = startY - ev.clientY;
157
+ pendingHeight = Math.max(120, Math.min(maxHeight, startHeight + delta));
158
+ if (!rafScheduled) {
159
+ rafScheduled = true;
160
+ requestAnimationFrame(applyHeight);
118
161
  }
119
162
  };
120
163
  const onUp = () => {
121
164
  setIsDraggingTray(false);
165
+ setTrayHeight(pendingHeight);
122
166
  document.body.style.cursor = '';
123
167
  document.body.style.userSelect = '';
124
168
  document.removeEventListener('mousemove', onMove);
@@ -148,7 +192,21 @@ export function Compositions({
148
192
  }, [enableLiveControls]);
149
193
 
150
194
  const currentCompositionHasControls = ready && defs.length > 0;
151
- const showControlsTray = currentCompositionHasControls && controlsTrayOpen;
195
+ const currentCompositionIdentifier = currentComposition?.identifier;
196
+ const isTrayCollapsedForCurrent =
197
+ !!currentCompositionIdentifier && collapsedCompositions.has(currentCompositionIdentifier);
198
+ const showControlsTray = currentCompositionHasControls;
199
+ const isTrayCollapsed = showControlsTray && isTrayCollapsedForCurrent;
200
+
201
+ const toggleTrayCollapsed = useCallback(() => {
202
+ if (!currentCompositionIdentifier) return;
203
+ setCollapsedCompositions((prev) => {
204
+ const next = new Set(prev);
205
+ if (next.has(currentCompositionIdentifier)) next.delete(currentCompositionIdentifier);
206
+ else next.add(currentCompositionIdentifier);
207
+ return next;
208
+ });
209
+ }, [currentCompositionIdentifier]);
152
210
 
153
211
  return (
154
212
  <CompositionContextProvider queryParams={compositionParams} setQueryParams={setCompositionParams}>
@@ -160,25 +218,21 @@ export function Compositions({
160
218
  <OptionButton icon="open-tab" />
161
219
  </Link>
162
220
  </Tooltip>
163
- {currentCompositionHasControls && (
164
- <Tooltip content={controlsTrayOpen ? 'Hide controls' : 'Show controls'} placement="bottom">
165
- <button
166
- className={`${styles.toolbarButton} ${controlsTrayOpen ? styles.toolbarButtonActive : ''}`}
167
- onClick={() => setControlsTrayOpen((o) => !o)}
168
- >
169
- <Icon of="settings" />
170
- </button>
171
- </Tooltip>
172
- )}
173
221
  </CompositionsMenuBar>
174
222
  <SandboxPermissionsAggregator
175
223
  hooks={previewSandboxHooks}
176
224
  onSandboxChange={setSandboxValue}
177
225
  component={component}
178
226
  />
227
+ <PreviewPropsAggregator
228
+ hooks={previewPropsHooks}
229
+ onPreviewPropsChange={setPreviewAttrs}
230
+ component={component}
231
+ />
179
232
  <div className={styles.previewArea}>
180
233
  {isDraggingTray && <div className={styles.dragOverlay} />}
181
234
  <CompositionContent
235
+ {...previewAttrs}
182
236
  className={styles.compositionPanel}
183
237
  emptyState={emptyState}
184
238
  component={component}
@@ -187,28 +241,17 @@ export function Compositions({
187
241
  sandbox={sandboxValue}
188
242
  />
189
243
  {showControlsTray && (
190
- <div ref={trayRef} className={styles.controlsTray} style={{ maxHeight: trayHeightRef.current }}>
191
- <div className={styles.trayDragHandle} onMouseDown={onTrayDragStart}>
192
- <div className={styles.trayDragBar} />
193
- </div>
194
- <div className={styles.trayHeader}>
195
- <div className={styles.trayTitleRow}>
196
- <Icon of="settings" className={styles.trayIcon} />
197
- <span className={styles.trayTitle}>Live Controls</span>
198
- {ready && defs.length > 0 && <span className={styles.trayBadge}>{defs.length}</span>}
199
- </div>
200
- <button className={styles.trayClose} onClick={() => setControlsTrayOpen(false)}>
201
- <Icon of="x" />
202
- </button>
203
- </div>
204
- <div className={styles.trayBody}>
205
- {ready ? (
206
- <LiveControls defs={defs} values={values} onChange={onChange} />
207
- ) : (
208
- <div className={styles.trayEmpty}>No live controls available for this composition</div>
209
- )}
210
- </div>
211
- </div>
244
+ <LiveControlsTray
245
+ trayRef={trayRef}
246
+ collapsed={isTrayCollapsed}
247
+ height={trayHeight}
248
+ ready={ready}
249
+ defs={defs}
250
+ values={values}
251
+ onChange={onChange}
252
+ onResizeStripMouseDown={onResizeStripMouseDown}
253
+ onToggleExpanded={toggleTrayCollapsed}
254
+ />
212
255
  )}
213
256
  </div>
214
257
  </Pane>
@@ -234,6 +277,9 @@ export function Compositions({
234
277
  isScaling={isScaling}
235
278
  useNameParam={useNameParam}
236
279
  includesEnvTemplate={component.preview?.includesEnvTemplate}
280
+ hasLiveControls={currentCompositionHasControls}
281
+ liveControlsActive={currentCompositionHasControls && !isTrayCollapsedForCurrent}
282
+ onToggleLiveControls={toggleTrayCollapsed}
237
283
  onSelectComposition={(composition) => {
238
284
  if (!currentComposition || !location) return;
239
285
  const selectedCompositionFromUrl = params['*'];
@@ -252,9 +298,6 @@ export function Compositions({
252
298
  }
253
299
  const newPath = pathSegments.join('/');
254
300
  navigate(`/${newPath}?${urlParams.toString()}`);
255
- // open the tray only on explicit user selection, so the
256
- // tray stays closed on navigation/tab return.
257
- setControlsTrayOpen(true);
258
301
  }}
259
302
  url={compositionUrl}
260
303
  compositions={component.compositions}
@@ -272,6 +315,89 @@ export function Compositions({
272
315
  );
273
316
  }
274
317
 
318
+ type LiveControlsTrayProps = {
319
+ trayRef: React.RefObject<HTMLDivElement | null>;
320
+ collapsed: boolean;
321
+ height: number | null;
322
+ ready: boolean;
323
+ defs: UseLiveControlsResult['defs'];
324
+ values: UseLiveControlsResult['values'];
325
+ onChange: UseLiveControlsResult['onChange'];
326
+ onResizeStripMouseDown: (e: React.MouseEvent) => void;
327
+ onToggleExpanded: () => void;
328
+ };
329
+
330
+ function LiveControlsTray({
331
+ trayRef,
332
+ collapsed,
333
+ height,
334
+ ready,
335
+ defs,
336
+ values,
337
+ onChange,
338
+ onResizeStripMouseDown,
339
+ onToggleExpanded,
340
+ }: LiveControlsTrayProps) {
341
+ // height = null → let CSS size the tray to its content (with the max-height
342
+ // clamp doing the upper bound); height = number → user has drag-resized,
343
+ // pin to that.
344
+ const trayStyle = collapsed || height === null ? undefined : { height };
345
+ return (
346
+ <div
347
+ ref={trayRef}
348
+ className={classNames(styles.controlsTray, collapsed && styles.controlsTrayCollapsed)}
349
+ style={trayStyle}
350
+ >
351
+ {!collapsed && (
352
+ <div
353
+ className={styles.trayResizeStrip}
354
+ onMouseDown={onResizeStripMouseDown}
355
+ role="separator"
356
+ aria-orientation="horizontal"
357
+ aria-label="Resize live controls"
358
+ title="Drag to resize"
359
+ >
360
+ <div className={styles.trayDragBar} />
361
+ </div>
362
+ )}
363
+ <div
364
+ className={styles.trayHeaderInner}
365
+ onClick={onToggleExpanded}
366
+ role="button"
367
+ tabIndex={0}
368
+ onKeyDown={(e) => {
369
+ if (e.key === 'Enter' || e.key === ' ') {
370
+ e.preventDefault();
371
+ onToggleExpanded();
372
+ }
373
+ }}
374
+ title={collapsed ? 'Click to expand' : 'Click to collapse'}
375
+ >
376
+ <div className={styles.trayTitleRow}>
377
+ <Icon of="settings" className={styles.trayIcon} />
378
+ <span className={styles.trayTitle}>Live Controls</span>
379
+ {ready && defs.length > 0 && <span className={styles.trayBadge}>{defs.length}</span>}
380
+ </div>
381
+ <span
382
+ className={classNames(styles.trayCollapseIcon, collapsed && styles.trayCollapseIconCollapsed)}
383
+ aria-hidden
384
+ >
385
+ <Icon of="right-rounded-corners" />
386
+ </span>
387
+ </div>
388
+ {!collapsed && (
389
+ <div className={styles.trayBody}>
390
+ {ready ? (
391
+ <LiveControls defs={defs} values={values} onChange={onChange} />
392
+ ) : (
393
+ <div className={styles.trayEmpty}>No live controls available for this composition</div>
394
+ )}
395
+ </div>
396
+ )}
397
+ </div>
398
+ );
399
+ }
400
+
275
401
  export type CompositionContentProps = {
276
402
  component: ComponentModel;
277
403
  selected?: Composition;
@@ -9,7 +9,7 @@ import { CompositionCompareSection } from '@teambit/compositions.ui.composition-
9
9
  import { CompositionCompare } from '@teambit/compositions.ui.composition-compare';
10
10
  import type { ComponentCompareUI } from '@teambit/component-compare';
11
11
  import { ComponentCompareAspect } from '@teambit/component-compare';
12
- import type { UseSandboxPermission } from '@teambit/preview.ui.component-preview';
12
+ import type { UsePreviewProps, UseSandboxPermission } from '@teambit/preview.ui.component-preview';
13
13
  import { CompositionsSection } from './composition.section';
14
14
  import { CompositionsAspect } from './compositions.aspect';
15
15
  import type { MenuBarWidget } from './compositions';
@@ -18,12 +18,14 @@ import { CompositionContent } from './compositions';
18
18
  export type CompositionsMenuSlot = SlotRegistry<MenuBarWidget[]>;
19
19
  export type EmptyStateSlot = SlotRegistry<ComponentType>;
20
20
  export type UsePreviewSandboxSlot = SlotRegistry<UseSandboxPermission>;
21
+ export type UsePreviewPropsSlot = SlotRegistry<UsePreviewProps>;
21
22
 
22
23
  export class CompositionsUI {
23
24
  constructor(
24
25
  private menuBarWidgetSlot: CompositionsMenuSlot,
25
26
  private emptyStateSlot: EmptyStateSlot,
26
- private usePreviewSandboxSlot: UsePreviewSandboxSlot
27
+ private usePreviewSandboxSlot: UsePreviewSandboxSlot,
28
+ private usePreviewPropsSlot: UsePreviewPropsSlot
27
29
  ) {}
28
30
  /**
29
31
  * register a new tester empty state. this allows to register a different empty state from each environment for example.
@@ -41,6 +43,18 @@ export class CompositionsUI {
41
43
  this.usePreviewSandboxSlot.register(useSandboxPermission);
42
44
  }
43
45
 
46
+ /**
47
+ * register a per-component resolver for iframe attributes on the composition preview
48
+ * (`allow`, `referrerPolicy`, ...). The resolver runs at render time with the current
49
+ * `ComponentModel`; results from multiple resolvers merge with later keys winning. Use
50
+ * this to grant a specific subset of components extra Permissions Policy capabilities
51
+ * (e.g. `fullscreen` for chart/video components) without loosening the default for all
52
+ * components.
53
+ */
54
+ registerPreviewProps(usePreviewProps: UsePreviewProps) {
55
+ this.usePreviewPropsSlot.register(usePreviewProps);
56
+ }
57
+
44
58
  getCompositionsCompare = () => {
45
59
  return (
46
60
  <CompositionCompare
@@ -54,23 +68,35 @@ export class CompositionsUI {
54
68
 
55
69
  static dependencies = [ComponentAspect, ComponentCompareAspect];
56
70
  static runtime = UIRuntime;
57
- static slots = [Slot.withType<ReactNode>(), Slot.withType<ComponentType>(), Slot.withType<UseSandboxPermission>()];
71
+ static slots = [
72
+ Slot.withType<ReactNode>(),
73
+ Slot.withType<ComponentType>(),
74
+ Slot.withType<UseSandboxPermission>(),
75
+ Slot.withType<UsePreviewProps>(),
76
+ ];
58
77
 
59
78
  static async provider(
60
79
  [component, componentCompare]: [ComponentUI, ComponentCompareUI],
61
80
  config: {},
62
- [compositionMenuSlot, emptyStateSlot, usePreviewSandboxSlot]: [
81
+ [compositionMenuSlot, emptyStateSlot, usePreviewSandboxSlot, usePreviewPropsSlot]: [
63
82
  CompositionsMenuSlot,
64
83
  EmptyStateSlot,
65
84
  UsePreviewSandboxSlot,
85
+ UsePreviewPropsSlot,
66
86
  ]
67
87
  ) {
68
- const compositions = new CompositionsUI(compositionMenuSlot, emptyStateSlot, usePreviewSandboxSlot);
88
+ const compositions = new CompositionsUI(
89
+ compositionMenuSlot,
90
+ emptyStateSlot,
91
+ usePreviewSandboxSlot,
92
+ usePreviewPropsSlot
93
+ );
69
94
  const section = new CompositionsSection(
70
95
  compositions,
71
96
  { menuBarWidgetSlot: compositions.menuBarWidgetSlot },
72
97
  emptyStateSlot,
73
- usePreviewSandboxSlot
98
+ usePreviewSandboxSlot,
99
+ usePreviewPropsSlot
74
100
  );
75
101
  const compositionCompare = new CompositionCompareSection(compositions);
76
102
  component.registerRoute(section.route);
@@ -83,6 +109,12 @@ export class CompositionsUI {
83
109
  manager.add('allow-same-origin');
84
110
  }
85
111
  });
112
+ // Default Permissions Policy: allow clipboard writes so copy-to-clipboard buttons in
113
+ // compositions work. Clipboard-read, camera, mic, geolocation, etc. remain denied. Other
114
+ // aspects may layer on (e.g. `fullscreen` for chart/video components).
115
+ compositions.registerPreviewProps((manager) => {
116
+ manager.set('allow', 'clipboard-write');
117
+ });
86
118
  return compositions;
87
119
  }
88
120
  }
@@ -1,5 +1,5 @@
1
1
  import type { Section } from '@teambit/component';
2
- import type { CompositionsUI, CompositionsMenuSlot, EmptyStateSlot, UsePreviewSandboxSlot } from './compositions.ui.runtime';
2
+ import type { CompositionsUI, CompositionsMenuSlot, EmptyStateSlot, UsePreviewSandboxSlot, UsePreviewPropsSlot } from './compositions.ui.runtime';
3
3
  type Options = {
4
4
  menuBarWidgetSlot: CompositionsMenuSlot;
5
5
  };
@@ -11,11 +11,12 @@ export declare class CompositionsSection implements Section {
11
11
  private options;
12
12
  private emptyStateSlot;
13
13
  private usePreviewSandboxSlot;
14
+ private usePreviewPropsSlot;
14
15
  constructor(
15
16
  /**
16
17
  * simulations ui extension.
17
18
  */
18
- compositions: CompositionsUI, options: Options, emptyStateSlot: EmptyStateSlot, usePreviewSandboxSlot: UsePreviewSandboxSlot);
19
+ compositions: CompositionsUI, options: Options, emptyStateSlot: EmptyStateSlot, usePreviewSandboxSlot: UsePreviewSandboxSlot, usePreviewPropsSlot: UsePreviewPropsSlot);
19
20
  navigationLink: {
20
21
  href: string;
21
22
  children: string;
@@ -27,11 +27,12 @@ class CompositionsSection {
27
27
  /**
28
28
  * simulations ui extension.
29
29
  */
30
- compositions, options, emptyStateSlot, usePreviewSandboxSlot) {
30
+ compositions, options, emptyStateSlot, usePreviewSandboxSlot, usePreviewPropsSlot) {
31
31
  this.compositions = compositions;
32
32
  this.options = options;
33
33
  this.emptyStateSlot = emptyStateSlot;
34
34
  this.usePreviewSandboxSlot = usePreviewSandboxSlot;
35
+ this.usePreviewPropsSlot = usePreviewPropsSlot;
35
36
  _defineProperty(this, "navigationLink", {
36
37
  href: '~compositions',
37
38
  children: 'Preview'
@@ -42,6 +43,7 @@ class CompositionsSection {
42
43
  menuBarWidgets: this.options.menuBarWidgetSlot,
43
44
  emptyState: this.emptyStateSlot,
44
45
  usePreviewSandboxSlot: this.usePreviewSandboxSlot,
46
+ usePreviewPropsSlot: this.usePreviewPropsSlot,
45
47
  enableLiveControls: true
46
48
  })
47
49
  });
@@ -1 +1 @@
1
- {"version":3,"names":["_react","data","_interopRequireDefault","require","_compositions","e","__esModule","default","_defineProperty","r","t","_toPropertyKey","Object","defineProperty","value","enumerable","configurable","writable","i","_toPrimitive","Symbol","toPrimitive","call","TypeError","String","Number","CompositionsSection","constructor","compositions","options","emptyStateSlot","usePreviewSandboxSlot","href","children","path","element","createElement","Compositions","menuBarWidgets","menuBarWidgetSlot","emptyState","enableLiveControls","exports"],"sources":["composition.section.tsx"],"sourcesContent":["import type { Section } from '@teambit/component';\nimport React from 'react';\nimport { Compositions } from './compositions';\nimport type {\n CompositionsUI,\n CompositionsMenuSlot,\n EmptyStateSlot,\n UsePreviewSandboxSlot,\n} from './compositions.ui.runtime';\n\ntype Options = { menuBarWidgetSlot: CompositionsMenuSlot };\n\nexport class CompositionsSection implements Section {\n constructor(\n /**\n * simulations ui extension.\n */\n private compositions: CompositionsUI,\n private options: Options,\n private emptyStateSlot: EmptyStateSlot,\n private usePreviewSandboxSlot: UsePreviewSandboxSlot\n ) {}\n\n navigationLink = {\n href: '~compositions',\n children: 'Preview',\n };\n\n route = {\n path: '~compositions/*',\n element: (\n <Compositions\n menuBarWidgets={this.options.menuBarWidgetSlot}\n emptyState={this.emptyStateSlot}\n usePreviewSandboxSlot={this.usePreviewSandboxSlot}\n enableLiveControls\n />\n ),\n };\n\n order = 20;\n}\n"],"mappings":";;;;;;AACA,SAAAA,OAAA;EAAA,MAAAC,IAAA,GAAAC,sBAAA,CAAAC,OAAA;EAAAH,MAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AACA,SAAAG,cAAA;EAAA,MAAAH,IAAA,GAAAE,OAAA;EAAAC,aAAA,YAAAA,CAAA;IAAA,OAAAH,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAA8C,SAAAC,uBAAAG,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAAA,SAAAG,gBAAAH,CAAA,EAAAI,CAAA,EAAAC,CAAA,YAAAD,CAAA,GAAAE,cAAA,CAAAF,CAAA,MAAAJ,CAAA,GAAAO,MAAA,CAAAC,cAAA,CAAAR,CAAA,EAAAI,CAAA,IAAAK,KAAA,EAAAJ,CAAA,EAAAK,UAAA,MAAAC,YAAA,MAAAC,QAAA,UAAAZ,CAAA,CAAAI,CAAA,IAAAC,CAAA,EAAAL,CAAA;AAAA,SAAAM,eAAAD,CAAA,QAAAQ,CAAA,GAAAC,YAAA,CAAAT,CAAA,uCAAAQ,CAAA,GAAAA,CAAA,GAAAA,CAAA;AAAA,SAAAC,aAAAT,CAAA,EAAAD,CAAA,2BAAAC,CAAA,KAAAA,CAAA,SAAAA,CAAA,MAAAL,CAAA,GAAAK,CAAA,CAAAU,MAAA,CAAAC,WAAA,kBAAAhB,CAAA,QAAAa,CAAA,GAAAb,CAAA,CAAAiB,IAAA,CAAAZ,CAAA,EAAAD,CAAA,uCAAAS,CAAA,SAAAA,CAAA,YAAAK,SAAA,yEAAAd,CAAA,GAAAe,MAAA,GAAAC,MAAA,EAAAf,CAAA;AAUvC,MAAMgB,mBAAmB,CAAoB;EAClDC,WAAWA;EACT;AACJ;AACA;EACYC,YAA4B,EAC5BC,OAAgB,EAChBC,cAA8B,EAC9BC,qBAA4C,EACpD;IAAA,KAJQH,YAA4B,GAA5BA,YAA4B;IAAA,KAC5BC,OAAgB,GAAhBA,OAAgB;IAAA,KAChBC,cAA8B,GAA9BA,cAA8B;IAAA,KAC9BC,qBAA4C,GAA5CA,qBAA4C;IAAAvB,eAAA,yBAGrC;MACfwB,IAAI,EAAE,eAAe;MACrBC,QAAQ,EAAE;IACZ,CAAC;IAAAzB,eAAA,gBAEO;MACN0B,IAAI,EAAE,iBAAiB;MACvBC,OAAO,eACLnC,MAAA,GAAAO,OAAA,CAAA6B,aAAA,CAAChC,aAAA,GAAAiC,YAAY;QACXC,cAAc,EAAE,IAAI,CAACT,OAAO,CAACU,iBAAkB;QAC/CC,UAAU,EAAE,IAAI,CAACV,cAAe;QAChCC,qBAAqB,EAAE,IAAI,CAACA,qBAAsB;QAClDU,kBAAkB;MAAA,CACnB;IAEL,CAAC;IAAAjC,eAAA,gBAEO,EAAE;EAnBP;AAoBL;AAACkC,OAAA,CAAAhB,mBAAA,GAAAA,mBAAA","ignoreList":[]}
1
+ {"version":3,"names":["_react","data","_interopRequireDefault","require","_compositions","e","__esModule","default","_defineProperty","r","t","_toPropertyKey","Object","defineProperty","value","enumerable","configurable","writable","i","_toPrimitive","Symbol","toPrimitive","call","TypeError","String","Number","CompositionsSection","constructor","compositions","options","emptyStateSlot","usePreviewSandboxSlot","usePreviewPropsSlot","href","children","path","element","createElement","Compositions","menuBarWidgets","menuBarWidgetSlot","emptyState","enableLiveControls","exports"],"sources":["composition.section.tsx"],"sourcesContent":["import type { Section } from '@teambit/component';\nimport React from 'react';\nimport { Compositions } from './compositions';\nimport type {\n CompositionsUI,\n CompositionsMenuSlot,\n EmptyStateSlot,\n UsePreviewSandboxSlot,\n UsePreviewPropsSlot,\n} from './compositions.ui.runtime';\n\ntype Options = { menuBarWidgetSlot: CompositionsMenuSlot };\n\nexport class CompositionsSection implements Section {\n constructor(\n /**\n * simulations ui extension.\n */\n private compositions: CompositionsUI,\n private options: Options,\n private emptyStateSlot: EmptyStateSlot,\n private usePreviewSandboxSlot: UsePreviewSandboxSlot,\n private usePreviewPropsSlot: UsePreviewPropsSlot\n ) {}\n\n navigationLink = {\n href: '~compositions',\n children: 'Preview',\n };\n\n route = {\n path: '~compositions/*',\n element: (\n <Compositions\n menuBarWidgets={this.options.menuBarWidgetSlot}\n emptyState={this.emptyStateSlot}\n usePreviewSandboxSlot={this.usePreviewSandboxSlot}\n usePreviewPropsSlot={this.usePreviewPropsSlot}\n enableLiveControls\n />\n ),\n };\n\n order = 20;\n}\n"],"mappings":";;;;;;AACA,SAAAA,OAAA;EAAA,MAAAC,IAAA,GAAAC,sBAAA,CAAAC,OAAA;EAAAH,MAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AACA,SAAAG,cAAA;EAAA,MAAAH,IAAA,GAAAE,OAAA;EAAAC,aAAA,YAAAA,CAAA;IAAA,OAAAH,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAA8C,SAAAC,uBAAAG,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAAA,SAAAG,gBAAAH,CAAA,EAAAI,CAAA,EAAAC,CAAA,YAAAD,CAAA,GAAAE,cAAA,CAAAF,CAAA,MAAAJ,CAAA,GAAAO,MAAA,CAAAC,cAAA,CAAAR,CAAA,EAAAI,CAAA,IAAAK,KAAA,EAAAJ,CAAA,EAAAK,UAAA,MAAAC,YAAA,MAAAC,QAAA,UAAAZ,CAAA,CAAAI,CAAA,IAAAC,CAAA,EAAAL,CAAA;AAAA,SAAAM,eAAAD,CAAA,QAAAQ,CAAA,GAAAC,YAAA,CAAAT,CAAA,uCAAAQ,CAAA,GAAAA,CAAA,GAAAA,CAAA;AAAA,SAAAC,aAAAT,CAAA,EAAAD,CAAA,2BAAAC,CAAA,KAAAA,CAAA,SAAAA,CAAA,MAAAL,CAAA,GAAAK,CAAA,CAAAU,MAAA,CAAAC,WAAA,kBAAAhB,CAAA,QAAAa,CAAA,GAAAb,CAAA,CAAAiB,IAAA,CAAAZ,CAAA,EAAAD,CAAA,uCAAAS,CAAA,SAAAA,CAAA,YAAAK,SAAA,yEAAAd,CAAA,GAAAe,MAAA,GAAAC,MAAA,EAAAf,CAAA;AAWvC,MAAMgB,mBAAmB,CAAoB;EAClDC,WAAWA;EACT;AACJ;AACA;EACYC,YAA4B,EAC5BC,OAAgB,EAChBC,cAA8B,EAC9BC,qBAA4C,EAC5CC,mBAAwC,EAChD;IAAA,KALQJ,YAA4B,GAA5BA,YAA4B;IAAA,KAC5BC,OAAgB,GAAhBA,OAAgB;IAAA,KAChBC,cAA8B,GAA9BA,cAA8B;IAAA,KAC9BC,qBAA4C,GAA5CA,qBAA4C;IAAA,KAC5CC,mBAAwC,GAAxCA,mBAAwC;IAAAxB,eAAA,yBAGjC;MACfyB,IAAI,EAAE,eAAe;MACrBC,QAAQ,EAAE;IACZ,CAAC;IAAA1B,eAAA,gBAEO;MACN2B,IAAI,EAAE,iBAAiB;MACvBC,OAAO,eACLpC,MAAA,GAAAO,OAAA,CAAA8B,aAAA,CAACjC,aAAA,GAAAkC,YAAY;QACXC,cAAc,EAAE,IAAI,CAACV,OAAO,CAACW,iBAAkB;QAC/CC,UAAU,EAAE,IAAI,CAACX,cAAe;QAChCC,qBAAqB,EAAE,IAAI,CAACA,qBAAsB;QAClDC,mBAAmB,EAAE,IAAI,CAACA,mBAAoB;QAC9CU,kBAAkB;MAAA,CACnB;IAEL,CAAC;IAAAlC,eAAA,gBAEO,EAAE;EApBP;AAqBL;AAACmC,OAAA,CAAAjB,mBAAA,GAAAA,mBAAA","ignoreList":[]}