@behold/widget 0.5.64 → 0.5.65

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,339 @@
1
+ import { c as createElement, a as setClasses, f as forceLayout, s as setCssVars } from './index-zGq7k0wG.js';
2
+ import { B as BaseWidget, I as ImagePost, V as VideoPost, A as AlbumPost, b as baseGridStyles, g as getMedianHSL } from './base-D7ZLOWQ5.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
+ // Define which props will trigger _handlePropChange
26
+ this.onPropChange(this._handlePropChange, ['widgetSettings', 'feedMetadata', 'posts', 'previewLoadingColors'], ['widgetSettings', 'feedMetadata', 'posts'], this.setup);
27
+ // Register child components
28
+ ImagePost.register();
29
+ VideoPost.register();
30
+ AlbumPost.register();
31
+ // Bind event handlers
32
+ this._handlePostClick = this._handlePostClick.bind(this);
33
+ // Listen to focus
34
+ this.addEventListener('post-focus-next', this._handleFocusNextPost);
35
+ this.addEventListener('post-focus-previous', this._handleFocusPreviousPost);
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-oswhxqGK.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
+ const matchingBreakpoint = 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
+ return { numPosts: this.posts.length, ...breakpoints.default, ...matchingBreakpoint };
232
+ }
233
+ /*
234
+ * Render posts with breakpoint
235
+ */
236
+ renderBreakpoint(breakpoint, forceRender = false) {
237
+ if (!this.posts || !this.containerEl)
238
+ return;
239
+ if (!this.postEls?.length)
240
+ forceRender = true;
241
+ const applied = this.appliedBreakpoint;
242
+ const { numColumns, gap, borderRadius, numPosts, postAspectRatio } = breakpoint;
243
+ // If we're reducing the # of posts, first remove them all to prevent fouc
244
+ if (applied?.numPosts > numPosts) {
245
+ this.containerEl.beholdReplaceChildren();
246
+ forceLayout();
247
+ }
248
+ // First apply container styles to prevent FOUC
249
+ switch (this.widgetSettings.alignment) {
250
+ case 'left':
251
+ this.style.justifyContent = 'flex-start';
252
+ break;
253
+ case 'right':
254
+ this.style.justifyContent = 'flex-end';
255
+ break;
256
+ default:
257
+ this.style.justifyContent = 'center';
258
+ break;
259
+ }
260
+ if (this.widgetSettings.maxWidth &&
261
+ this.widgetSettings.constrainWidth &&
262
+ this.containerEl.style.maxWidth !== `${this.widgetSettings.maxWidth}px`) {
263
+ this.containerEl.style.maxWidth = `${this.widgetSettings.maxWidth}px`;
264
+ }
265
+ if (!this.widgetSettings.constrainWidth) {
266
+ this.containerEl.style.maxWidth = '';
267
+ }
268
+ this.containerEl.style.gridTemplateColumns = `repeat(${numColumns}, 1fr)`;
269
+ this.containerEl.style.gap = `${gap.y}px ${gap.x}px`;
270
+ this.containerEl.setAttribute('data-hover-effect', this.widgetSettings.hoverEffect);
271
+ let aspectRatio = (postAspectRatio || [1, 1]).reduce((w, h) => w / h);
272
+ setCssVars(this.containerEl, {
273
+ '--post-border-radius': `${borderRadius}%`,
274
+ '--post-aspect-ratio': `${aspectRatio}`,
275
+ });
276
+ // Then render posts into updated container
277
+ if (applied?.numPosts !== numPosts || forceRender) {
278
+ this.renderPosts(breakpoint);
279
+ }
280
+ this.postEls.forEach((el, i) => {
281
+ el.hasRowGap = `${breakpoint.gap.y}` !== '0';
282
+ });
283
+ this.appliedBreakpoint = breakpoint;
284
+ }
285
+ /*
286
+ * Create post Els based on a breakpoint
287
+ */
288
+ createPostEls(breakpoint) {
289
+ const { numPosts, numColumns, gap, postAspectRatio } = breakpoint;
290
+ const filteredArray = this.posts.filter((p, i) => {
291
+ const numPosts = breakpoint?.numPosts || this.posts?.length || 200;
292
+ return i < numPosts;
293
+ });
294
+ const postEls = filteredArray.map((post, i) => {
295
+ const numRows = Math.ceil((numPosts || this.posts.length) / numColumns);
296
+ let postType = 'behold-image-post';
297
+ if (post.mediaType === 'VIDEO') {
298
+ postType = 'behold-video-post';
299
+ }
300
+ if (post.mediaType === 'CAROUSEL_ALBUM') {
301
+ postType = 'behold-album-post';
302
+ }
303
+ const postEl = createElement({
304
+ type: postType,
305
+ props: {
306
+ post,
307
+ widgetSettings: this.widgetSettings,
308
+ feedMetadata: this.feedMetadata,
309
+ medianPaletteHSL: this.medianPaletteHSL,
310
+ onClick: this._handlePostClick,
311
+ hasRowGap: `${gap.y}` !== '0',
312
+ isLastRow: Math.ceil((i + 1) / numColumns) === numRows,
313
+ index: i,
314
+ aspectRatio: postAspectRatio || [1, 1],
315
+ totalPosts: filteredArray?.length || numPosts || this.posts?.length || 0,
316
+ },
317
+ });
318
+ return postEl;
319
+ });
320
+ while (postEls.length < breakpoint.numPosts) {
321
+ const placeholderEl = createElement({
322
+ classes: 'post post--placeholder',
323
+ });
324
+ postEls.push(placeholderEl);
325
+ }
326
+ return postEls;
327
+ }
328
+ /*
329
+ * Register
330
+ */
331
+ static register(name = 'behold-grid') {
332
+ if (!customElements.get(name)) {
333
+ customElements.define(name, Grid);
334
+ }
335
+ return name;
336
+ }
337
+ }
338
+
339
+ export { Grid as default };