@behold/widget 0.5.56 → 0.5.57

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,338 @@
1
+ import { c as createElement, a as setClasses, f as forceLayout, s as setCssVars } from './index-R4lEDZFo.js';
2
+ import { B as BaseWidget, I as ImagePost, V as VideoPost, A as AlbumPost, b as baseGridStyles, g as getMedianHSL } from './base-GZO73SkY.js';
3
+
4
+ var css_248z = ".post--placeholder{background-color:#dedede;height:0;padding-bottom:calc(100%/var(--post-aspect-ratio))}";
5
+ var styles = css_248z;
6
+
7
+ /*
8
+ * Grid
9
+ */
10
+ class Grid extends BaseWidget {
11
+ label = 'Grid';
12
+ // Provided
13
+ widgetSettings;
14
+ feedMetadata;
15
+ posts;
16
+ previewLoadingColors = null;
17
+ // Internal
18
+ containerEl;
19
+ postEls;
20
+ popoverGalleryEl;
21
+ appliedBreakpoint;
22
+ medianPaletteHSL;
23
+ constructor() {
24
+ super();
25
+ // Register child components
26
+ ImagePost.register();
27
+ VideoPost.register();
28
+ AlbumPost.register();
29
+ // Bind event handlers
30
+ this._handlePostClick = this._handlePostClick.bind(this);
31
+ // Listen to focus
32
+ this.addEventListener('post-focus-next', this._handleFocusNextPost);
33
+ this.addEventListener('post-focus-previous', this._handleFocusPreviousPost);
34
+ // Define which props will trigger _handlePropChange
35
+ this.onPropChange(this._handlePropChange, ['widgetSettings', 'feedMetadata', 'posts', 'previewLoadingColors'], ['widgetSettings', 'feedMetadata', 'posts'], this.setup);
36
+ // Connect
37
+ this.onConnect(() => {
38
+ this.containerEl = createElement({
39
+ type: 'figure',
40
+ classes: 'posts',
41
+ });
42
+ this.renderWidget(this.containerEl, [baseGridStyles, styles]);
43
+ // A11y stuff
44
+ this.setAttribute('tabindex', '0');
45
+ this.setAttribute('aria-label', 'Gallery of Instagram posts. Shift + arrow keys to navigate');
46
+ // listen to resize
47
+ this.onResize(this, this, this._handleResize);
48
+ });
49
+ }
50
+ /*
51
+ * Run initial setup that requires data to be fully loaded
52
+ */
53
+ setup() {
54
+ // Calculate medianPaletteHSL
55
+ if (this.widgetSettings.loadingColor !== 'transparent') {
56
+ const palettes = this.posts
57
+ .filter((post) => post.colorPalette)
58
+ .map((post) => post.colorPalette);
59
+ this.medianPaletteHSL = getMedianHSL(palettes, this.widgetSettings.loadingColor);
60
+ }
61
+ // Find initial breakpoint
62
+ const matchingBreakpoint = this.getMatchingBreakpoint(this.offsetWidth, this.widgetSettings.breakpoints);
63
+ // Render posts with current breakpoint styles
64
+ this.renderBreakpoint(matchingBreakpoint);
65
+ // Pass settings and metadata to posts
66
+ if (this.postEls) {
67
+ this.postEls.forEach((postEl) => {
68
+ postEl.widgetSettings = this.widgetSettings;
69
+ postEl.feedMetadata = this.feedMetadata;
70
+ });
71
+ }
72
+ // Enable popoverGallery if necessary
73
+ if (this.widgetSettings.onPostClick === 'openPopupGallery') {
74
+ this.enablePopoverGallery();
75
+ }
76
+ }
77
+ /*
78
+ * Handle prop change
79
+ */
80
+ _handlePropChange({ changedProp, oldValue, newValue }) {
81
+ switch (changedProp) {
82
+ case 'posts':
83
+ this._handlePostsChange({ oldValue, newValue });
84
+ break;
85
+ case 'widgetSettings':
86
+ this._handleSettingsChange(oldValue, newValue);
87
+ break;
88
+ case 'feedMetadata':
89
+ this._handleMetadataChange();
90
+ break;
91
+ case 'previewLoadingColors':
92
+ this.postEls.forEach((postEl) => {
93
+ postEl.previewLoadingColors = this.previewLoadingColors;
94
+ });
95
+ setClasses(this, {
96
+ 'is-previewing-loading-colors': !!this.previewLoadingColors,
97
+ });
98
+ break;
99
+ }
100
+ }
101
+ /*
102
+ * Handle posts change
103
+ */
104
+ _handlePostsChange({ oldValue, newValue }) {
105
+ this.postEls = [];
106
+ this.renderBreakpoint(this.getMatchingBreakpoint(this.offsetWidth, this.widgetSettings.breakpoints), true);
107
+ if (this.popoverGalleryEl) {
108
+ this.popoverGalleryEl.posts = this.posts;
109
+ }
110
+ }
111
+ /*
112
+ * Handle settings change
113
+ */
114
+ _handleSettingsChange(oldValue, newValue) {
115
+ if (oldValue?.onPostClick !== 'openPopupGallery' &&
116
+ newValue?.onPostClick === 'openPopupGallery') {
117
+ this.enablePopoverGallery();
118
+ }
119
+ const matchingBreakpoint = this.getMatchingBreakpoint(this.offsetWidth, this.widgetSettings.breakpoints);
120
+ this.renderBreakpoint(matchingBreakpoint);
121
+ this.postEls.forEach((postEl) => {
122
+ postEl.widgetSettings = this.widgetSettings;
123
+ });
124
+ if (this.popoverGalleryEl) {
125
+ this.popoverGalleryEl.widgetSettings = this.widgetSettings;
126
+ }
127
+ }
128
+ /*
129
+ * Handle settings change
130
+ */
131
+ _handleMetadataChange() {
132
+ let description = `from @${this.feedMetadata.username}`;
133
+ if (this.feedMetadata.hashtags?.length) {
134
+ description = `from hashtag${this.feedMetadata.hashtags.length > 1 ? 's' : ''} ${this.feedMetadata.hashtags.join(', ')}`;
135
+ }
136
+ this.setAttribute('aria-label', `Gallery of Instagram posts ${description}. Shift + arrow keys to navigate`);
137
+ this.postEls.forEach((postEl) => {
138
+ postEl.feedMetadata = this.feedMetadata;
139
+ });
140
+ if (this.popoverGalleryEl) {
141
+ this.popoverGalleryEl.feedMetadata = this.feedMetadata;
142
+ }
143
+ }
144
+ /**
145
+ * Enable popup carousel
146
+ */
147
+ async enablePopoverGallery() {
148
+ if (this.popoverGalleryEl)
149
+ return;
150
+ const { default: PopoverGallery } = await import('./PopoverGallery-pssVDO_O.js');
151
+ PopoverGallery.register();
152
+ this.popoverGalleryEl = document.createElement('behold-popover-gallery');
153
+ Object.assign(this.popoverGalleryEl, {
154
+ widgetSettings: this.widgetSettings,
155
+ feedMetadata: this.feedMetadata,
156
+ posts: this.posts,
157
+ closeFocusEl: this,
158
+ onSlideChange: (slideIndex) => {
159
+ this.popoverGalleryEl.closeFocusEl = this.postEls[slideIndex];
160
+ },
161
+ });
162
+ }
163
+ /*
164
+ * Handle resize
165
+ */
166
+ _handleResize(entry) {
167
+ const widgetWidth = entry.contentBoxSize?.[0]?.inlineSize;
168
+ const breakpoints = this.widgetSettings.breakpoints;
169
+ const matchingBreakpoint = this.getMatchingBreakpoint(widgetWidth, breakpoints);
170
+ this.renderBreakpoint(matchingBreakpoint);
171
+ }
172
+ /**
173
+ * Handle prev post focus
174
+ */
175
+ _handleFocusPreviousPost() {
176
+ if (!this.postEls.length)
177
+ return;
178
+ let currentFocusIndex = [...this.containerEl.children].indexOf(this.shadow.activeElement?.parentElement);
179
+ if (currentFocusIndex > 0) {
180
+ currentFocusIndex = currentFocusIndex - 1;
181
+ }
182
+ else {
183
+ currentFocusIndex = this.postEls.length - 1;
184
+ }
185
+ this.postEls[currentFocusIndex].focus();
186
+ }
187
+ /**
188
+ * Handle next post focus
189
+ */
190
+ _handleFocusNextPost() {
191
+ if (!this.postEls.length)
192
+ return;
193
+ let currentFocusIndex = [...this.containerEl.children].indexOf(this.shadow.activeElement?.parentElement);
194
+ if (currentFocusIndex > -1 && currentFocusIndex < this.postEls.length - 1) {
195
+ currentFocusIndex = currentFocusIndex + 1;
196
+ }
197
+ else {
198
+ currentFocusIndex = 0;
199
+ }
200
+ this.postEls[currentFocusIndex].focus();
201
+ }
202
+ /**
203
+ * Handle post click
204
+ */
205
+ _handlePostClick(post) {
206
+ this.popoverGalleryEl.open(this.postEls.indexOf(post), post);
207
+ }
208
+ /*
209
+ * Update postEls
210
+ */
211
+ renderPosts(breakpoint) {
212
+ this.postEls = this.createPostEls(breakpoint);
213
+ this.raf(() => {
214
+ this.containerEl.beholdReplaceChildren(...this.postEls);
215
+ }, 'renderPosts');
216
+ }
217
+ /*
218
+ * Get breakpoint that matches a width
219
+ */
220
+ getMatchingBreakpoint(width, breakpoints) {
221
+ return Object.entries(breakpoints)
222
+ .map(([key, value]) => ({ width: key, ...value }))
223
+ .filter((bp) => bp.width !== 'default')
224
+ .sort((a, b) => parseInt(b.width) - parseInt(a.width))
225
+ .reduce((acc, curr) => {
226
+ return width <= parseInt(curr.width) ? curr : acc;
227
+ },
228
+ // numPosts doesn't exist on default breakpoint for some reason
229
+ // @todo fix that
230
+ { numPosts: this.posts.length, ...breakpoints.default });
231
+ }
232
+ /*
233
+ * Render posts with breakpoint
234
+ */
235
+ renderBreakpoint(breakpoint, forceRender = false) {
236
+ if (!this.posts || !this.containerEl)
237
+ return;
238
+ if (!this.postEls?.length)
239
+ forceRender = true;
240
+ const applied = this.appliedBreakpoint;
241
+ const { numColumns, gap, borderRadius, numPosts, postAspectRatio } = breakpoint;
242
+ // If we're reducing the # of posts, first remove them all to prevent fouc
243
+ if (applied?.numPosts > numPosts) {
244
+ this.containerEl.beholdReplaceChildren();
245
+ forceLayout();
246
+ }
247
+ // First apply container styles to prevent FOUC
248
+ switch (this.widgetSettings.alignment) {
249
+ case 'left':
250
+ this.style.justifyContent = 'flex-start';
251
+ break;
252
+ case 'right':
253
+ this.style.justifyContent = 'flex-end';
254
+ break;
255
+ default:
256
+ this.style.justifyContent = 'center';
257
+ break;
258
+ }
259
+ if (this.widgetSettings.maxWidth &&
260
+ this.widgetSettings.constrainWidth &&
261
+ this.containerEl.style.maxWidth !== `${this.widgetSettings.maxWidth}px`) {
262
+ this.containerEl.style.maxWidth = `${this.widgetSettings.maxWidth}px`;
263
+ }
264
+ if (!this.widgetSettings.constrainWidth) {
265
+ this.containerEl.style.maxWidth = '';
266
+ }
267
+ this.containerEl.style.gridTemplateColumns = `repeat(${numColumns}, 1fr)`;
268
+ this.containerEl.style.gap = `${gap.y}px ${gap.x}px`;
269
+ this.containerEl.setAttribute('data-hover-effect', this.widgetSettings.hoverEffect);
270
+ let aspectRatio = (postAspectRatio || [1, 1]).reduce((w, h) => w / h);
271
+ setCssVars(this.containerEl, {
272
+ '--post-border-radius': `${borderRadius}%`,
273
+ '--post-aspect-ratio': `${aspectRatio}`,
274
+ });
275
+ // Then render posts into updated container
276
+ if (applied?.numPosts !== numPosts || forceRender) {
277
+ this.renderPosts(breakpoint);
278
+ }
279
+ this.postEls.forEach((el, i) => {
280
+ el.hasRowGap = `${breakpoint.gap.y}` !== '0';
281
+ });
282
+ this.appliedBreakpoint = breakpoint;
283
+ }
284
+ /*
285
+ * Create post Els based on a breakpoint
286
+ */
287
+ createPostEls(breakpoint) {
288
+ const { numPosts, numColumns, gap, postAspectRatio } = breakpoint;
289
+ const filteredArray = this.posts.filter((p, i) => {
290
+ const numPosts = breakpoint?.numPosts || this.posts?.length || 200;
291
+ return i < numPosts;
292
+ });
293
+ const postEls = filteredArray.map((post, i) => {
294
+ const numRows = Math.ceil((numPosts || this.posts.length) / numColumns);
295
+ let postType = 'behold-image-post';
296
+ if (post.mediaType === 'VIDEO') {
297
+ postType = 'behold-video-post';
298
+ }
299
+ if (post.mediaType === 'CAROUSEL_ALBUM') {
300
+ postType = 'behold-album-post';
301
+ }
302
+ const postEl = createElement({
303
+ type: postType,
304
+ props: {
305
+ post,
306
+ widgetSettings: this.widgetSettings,
307
+ feedMetadata: this.feedMetadata,
308
+ medianPaletteHSL: this.medianPaletteHSL,
309
+ onClick: this._handlePostClick,
310
+ hasRowGap: `${gap.y}` !== '0',
311
+ isLastRow: Math.ceil((i + 1) / numColumns) === numRows,
312
+ index: i,
313
+ aspectRatio: postAspectRatio || [1, 1],
314
+ totalPosts: filteredArray?.length || numPosts || this.posts?.length || 0,
315
+ },
316
+ });
317
+ return postEl;
318
+ });
319
+ while (postEls.length < breakpoint.numPosts) {
320
+ const placeholderEl = createElement({
321
+ classes: 'post post--placeholder',
322
+ });
323
+ postEls.push(placeholderEl);
324
+ }
325
+ return postEls;
326
+ }
327
+ /*
328
+ * Register
329
+ */
330
+ static register(name = 'behold-grid') {
331
+ if (!customElements.get(name)) {
332
+ customElements.define(name, Grid);
333
+ }
334
+ return name;
335
+ }
336
+ }
337
+
338
+ export { Grid as default };