blacklight-gallery 6.0.0 → 6.1.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.env +2 -0
  3. data/.github/workflows/ruby.yml +10 -26
  4. data/Gemfile +5 -12
  5. data/README.md +3 -5
  6. data/Rakefile +53 -32
  7. data/app/assets/config/blacklight_gallery/manifest.js +0 -1
  8. data/app/assets/javascripts/blacklight_gallery/blacklight-gallery.esm.js +959 -484
  9. data/app/assets/javascripts/blacklight_gallery/blacklight-gallery.esm.js.map +1 -1
  10. data/app/assets/javascripts/blacklight_gallery/blacklight-gallery.js +959 -484
  11. data/app/assets/javascripts/blacklight_gallery/blacklight-gallery.js.map +1 -1
  12. data/app/assets/stylesheets/blacklight_gallery/masonry.css +9 -3
  13. data/app/assets/stylesheets/blacklight_gallery/slideshow.css +2 -13
  14. data/app/components/blacklight/gallery/document_component.html.erb +1 -5
  15. data/app/components/blacklight/gallery/document_component.rb +0 -4
  16. data/app/components/blacklight/gallery/openseadragon_embed_component.rb +4 -5
  17. data/app/components/blacklight/gallery/slideshow_component.html.erb +1 -1
  18. data/app/components/blacklight/gallery/slideshow_component.rb +3 -6
  19. data/app/{views/catalog/_slideshow_modal.html.erb → components/blacklight/gallery/slideshow_modal_component.html.erb} +2 -3
  20. data/app/components/blacklight/gallery/slideshow_modal_component.rb +6 -0
  21. data/app/components/blacklight/gallery/slideshow_presenter_component.html.erb +14 -0
  22. data/app/components/blacklight/gallery/slideshow_presenter_component.rb +17 -0
  23. data/app/components/blacklight/gallery/slideshow_preview_component.html.erb +1 -5
  24. data/app/components/blacklight/gallery/slideshow_preview_component.rb +0 -13
  25. data/app/javascript/blacklight-gallery/grid-lanes-polyfill.js +1067 -0
  26. data/app/javascript/blacklight-gallery/index.js +18 -4
  27. data/app/views/catalog/_document_gallery.html.erb +1 -1
  28. data/app/views/catalog/_document_masonry.html.erb +1 -1
  29. data/app/views/catalog/_document_slideshow.html.erb +5 -25
  30. data/app/views/catalog/_openseadragon_default.html.erb +1 -1
  31. data/blacklight-gallery.gemspec +19 -20
  32. data/compose.yaml +18 -0
  33. data/config/importmap.rb +0 -1
  34. data/config/locales/blacklight-gallery.ar.yml +0 -3
  35. data/config/locales/blacklight-gallery.de.yml +3 -0
  36. data/config/locales/blacklight-gallery.en.yml +0 -3
  37. data/config/locales/blacklight-gallery.es.yml +3 -3
  38. data/config/locales/blacklight-gallery.fr.yml +3 -3
  39. data/config/locales/blacklight-gallery.it.yml +3 -3
  40. data/config/locales/blacklight-gallery.pt-BR.yml +3 -3
  41. data/config/locales/blacklight-gallery.zh.yml +3 -3
  42. data/lib/blacklight/gallery/version.rb +1 -1
  43. data/lib/generators/blacklight_gallery/install_generator.rb +15 -29
  44. data/package.json +2 -4
  45. data/rollup.config.js +13 -16
  46. data/spec/components/blacklight/gallery/document_component_spec.rb +2 -10
  47. data/spec/components/blacklight/gallery/openseadragon_embed_component_spec.rb +1 -6
  48. data/spec/components/blacklight/gallery/slideshow_component_spec.rb +28 -25
  49. data/spec/components/blacklight/gallery/slideshow_preview_component_spec.rb +3 -12
  50. data/spec/features/slideshow_spec.rb +23 -1
  51. data/spec/views/catalog/_document_slideshow.html.erb_spec.rb +6 -10
  52. metadata +49 -53
  53. data/app/components/blacklight/gallery/icons/pause_slideshow_component.rb +0 -18
  54. data/app/components/blacklight/gallery/icons/start_slideshow_component.rb +0 -18
  55. data/app/javascript/blacklight-gallery/masonry.js +0 -12
  56. data/app/javascript/blacklight-gallery/slideshow.js +0 -122
  57. data/vendor/assets/javascripts/imagesloaded.pkgd.js +0 -441
  58. data/vendor/assets/javascripts/masonry.min.js +0 -9
@@ -1,587 +1,1062 @@
1
- /*!
2
- * imagesLoaded PACKAGED v5.0.0
3
- * JavaScript is all like "You images are done yet or what?"
4
- * MIT License
5
- */
6
-
7
1
  /**
8
- * EvEmitter v2.1.1
9
- * Lil' event emitter
10
- * MIT License
2
+ * CSS Grid Lanes Polyfill
3
+ *
4
+ * Polyfills the new `display: grid-lanes` CSS feature for browsers
5
+ * that don't support it natively. Based on the WebKit implementation
6
+ * described at: https://webkit.org/blog/17660/introducing-css-grid-lanes/
7
+ *
8
+ * Features supported:
9
+ * - display: grid-lanes
10
+ * - grid-template-columns / grid-template-rows for lane definition
11
+ * - gap, column-gap, row-gap
12
+ * - --flow-tolerance for placement sensitivity
13
+ * - Spanning items (grid-column: span N)
14
+ * - Explicit placement (grid-column: N / M)
15
+ * - Responsive auto-fill/auto-fit with minmax()
16
+ * - Both waterfall (columns) and brick (rows) layouts
17
+ *
18
+ * Features that do not work:
19
+ * - fr units with grid-template-rows
20
+ *
21
+ * @version 1.0.0
22
+ * @author Simon Willison
23
+ * @author ninjamar
24
+ * @license MIT
11
25
  */
12
26
 
13
- ( function( global, factory ) {
14
- // universal module definition
15
- if ( typeof module == 'object' && module.exports ) {
16
- // CommonJS - Browserify, Webpack
17
- module.exports = factory();
18
- } else {
19
- // Browser globals
20
- global.EvEmitter = factory();
21
- }
27
+ // ============================================================================
28
+ // CONSTANTS & STATE
29
+ // ============================================================================
22
30
 
23
- }( typeof window != 'undefined' ? window : undefined, function() {
31
+ const POLYFILL_NAME = "GridLanesPolyfill";
32
+ const POLYFILL_ATTR = "data-grid-lanes-polyfilled";
33
+ const DEFAULT_TOLERANCE = 16; // ~1em in pixels
24
34
 
25
- function EvEmitter() {}
35
+ // Cache computed styles to reduce redundant calls
36
+ let styleMap = new WeakMap(); // Not const because map has no clear meaning it has to be initialized later
26
37
 
27
- let proto = EvEmitter.prototype;
38
+ // ============================================================================
39
+ // FEATURE DETECTION
40
+ // ============================================================================
28
41
 
29
- proto.on = function( eventName, listener ) {
30
- if ( !eventName || !listener ) return this;
31
-
32
- // set events hash
33
- let events = this._events = this._events || {};
34
- // set listeners array
35
- let listeners = events[ eventName ] = events[ eventName ] || [];
36
- // only add once
37
- if ( !listeners.includes( listener ) ) {
38
- listeners.push( listener );
42
+ /**
43
+ * Check if the browser natively supports display: grid-lanes
44
+ */
45
+ function supportsGridLanes() {
46
+ if (typeof CSS === "undefined" || !CSS.supports) {
47
+ return false
39
48
  }
49
+ return CSS.supports("display", "grid-lanes")
50
+ }
40
51
 
41
- return this;
42
- };
43
-
44
- proto.once = function( eventName, listener ) {
45
- if ( !eventName || !listener ) return this;
46
-
47
- // add event
48
- this.on( eventName, listener );
49
- // set once flag
50
- // set onceEvents hash
51
- let onceEvents = this._onceEvents = this._onceEvents || {};
52
- // set onceListeners object
53
- let onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
54
- // set flag
55
- onceListeners[ listener ] = true;
56
-
57
- return this;
58
- };
52
+ // ============================================================================
53
+ // PARSING UTILITIES
54
+ // ============================================================================
59
55
 
60
- proto.off = function( eventName, listener ) {
61
- let listeners = this._events && this._events[ eventName ];
62
- if ( !listeners || !listeners.length ) return this;
56
+ /**
57
+ * Parse a CSS length value to pixels
58
+ */
59
+ function parseLengthToPixels(
60
+ value,
61
+ containerSize,
62
+ fontSize = 16,
63
+ rootFontSize = 16
64
+ ) {
65
+ if (!value || value === "auto" || value === "none") return null
66
+
67
+ const num = parseFloat(value);
68
+ if (isNaN(num)) return null
69
+
70
+ if (value.endsWith("px")) return num
71
+ if (value.endsWith("rem")) return num * rootFontSize
72
+ if (value.endsWith("em")) return num * fontSize
73
+ if (value.endsWith("ch")) return num * fontSize * 0.5 // Approximate
74
+ if (value.endsWith("lh")) return num * fontSize * 1.2 // Approximate line-height
75
+ if (value.endsWith("%")) return (num / 100) * containerSize
76
+ if (value.endsWith("vw")) return (num / 100) * window.innerWidth
77
+ if (value.endsWith("vh")) return (num / 100) * window.innerHeight
78
+ if (value.endsWith("vmin"))
79
+ return (num / 100) * Math.min(window.innerWidth, window.innerHeight)
80
+ if (value.endsWith("vmax"))
81
+ return (num / 100) * Math.max(window.innerWidth, window.innerHeight)
82
+ if (value.endsWith("fr")) return null // Handled separately
83
+
84
+ // Unitless number treated as pixels
85
+ if (!isNaN(num) && value === String(num)) return num
86
+
87
+ return null
88
+ }
63
89
 
64
- let index = listeners.indexOf( listener );
65
- if ( index != -1 ) {
66
- listeners.splice( index, 1 );
90
+ /**
91
+ * Parse minmax() function
92
+ */
93
+ function parseMinMax(value) {
94
+ const match = value.match(/minmax\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/);
95
+ if (!match) return null
96
+ return {
97
+ min: match[1].trim(),
98
+ max: match[2].trim()
67
99
  }
100
+ }
68
101
 
69
- return this;
70
- };
71
-
72
- proto.emitEvent = function( eventName, args ) {
73
- let listeners = this._events && this._events[ eventName ];
74
- if ( !listeners || !listeners.length ) return this;
75
-
76
- // copy over to avoid interference if .off() in listener
77
- listeners = listeners.slice( 0 );
78
- args = args || [];
79
- // once stuff
80
- let onceListeners = this._onceEvents && this._onceEvents[ eventName ];
81
-
82
- for ( let listener of listeners ) {
83
- let isOnce = onceListeners && onceListeners[ listener ];
84
- if ( isOnce ) {
85
- // remove listener
86
- // remove before trigger to prevent recursion
87
- this.off( eventName, listener );
88
- // unset once flag
89
- delete onceListeners[ listener ];
90
- }
91
- // trigger listener
92
- listener.apply( this, args );
102
+ /**
103
+ * Parse repeat() function
104
+ */
105
+ function parseRepeat(value) {
106
+ const match = value.match(/repeat\(\s*([^,]+)\s*,\s*(.+)\s*\)/);
107
+ if (!match) return null
108
+ return {
109
+ count: match[1].trim(),
110
+ pattern: match[2].trim()
93
111
  }
112
+ }
94
113
 
95
- return this;
96
- };
97
-
98
- proto.allOff = function() {
99
- delete this._events;
100
- delete this._onceEvents;
101
- return this;
102
- };
103
-
104
- return EvEmitter;
105
-
106
- } ) );
107
- /*!
108
- * imagesLoaded v5.0.0
109
- * JavaScript is all like "You images are done yet or what?"
110
- * MIT License
114
+ /**
115
+ * Tokenize a grid template string
111
116
  */
112
-
113
- ( function( window, factory ) {
114
- // universal module definition
115
- if ( typeof module == 'object' && module.exports ) {
116
- // CommonJS
117
- module.exports = factory( window, require('ev-emitter') );
118
- } else {
119
- // browser global
120
- window.imagesLoaded = factory( window, window.EvEmitter );
117
+ function tokenizeTemplate(template) {
118
+ const tokens = [];
119
+ let current = "";
120
+ let parenDepth = 0;
121
+
122
+ for (let i = 0; i < template.length; i++) {
123
+ const char = template[i];
124
+
125
+ if (char === "(") {
126
+ parenDepth++;
127
+ current += char;
128
+ } else if (char === ")") {
129
+ parenDepth--;
130
+ current += char;
131
+ } else if (char === " " && parenDepth === 0) {
132
+ if (current.trim()) {
133
+ tokens.push(current.trim());
134
+ }
135
+ current = "";
136
+ } else {
137
+ current += char;
138
+ }
121
139
  }
122
140
 
123
- } )( typeof window !== 'undefined' ? window : undefined,
124
- function factory( window, EvEmitter ) {
125
-
126
- let $ = window.jQuery;
127
- let console = window.console;
128
-
129
- // -------------------------- helpers -------------------------- //
130
-
131
- // turn element or nodeList into an array
132
- function makeArray( obj ) {
133
- // use object if already an array
134
- if ( Array.isArray( obj ) ) return obj;
135
-
136
- let isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
137
- // convert nodeList to array
138
- if ( isArrayLike ) return [ ...obj ];
141
+ if (current.trim()) {
142
+ tokens.push(current.trim());
143
+ }
139
144
 
140
- // array of single index
141
- return [ obj ];
145
+ return tokens
142
146
  }
143
147
 
144
- // -------------------------- imagesLoaded -------------------------- //
145
-
146
148
  /**
147
- * @param {[Array, Element, NodeList, String]} elem
148
- * @param {[Object, Function]} options - if function, use as callback
149
- * @param {Function} onAlways - callback function
150
- * @returns {ImagesLoaded}
149
+ * Calculate lane sizes from grid-template-columns/rows
151
150
  */
152
- function ImagesLoaded( elem, options, onAlways ) {
153
- // coerce ImagesLoaded() without new, to be new ImagesLoaded()
154
- if ( !( this instanceof ImagesLoaded ) ) {
155
- return new ImagesLoaded( elem, options, onAlways );
156
- }
157
- // use elem as selector string
158
- let queryElem = elem;
159
- if ( typeof elem == 'string' ) {
160
- queryElem = document.querySelectorAll( elem );
161
- }
162
- // bail if bad element
163
- if ( !queryElem ) {
164
- console.error(`Bad element for imagesLoaded ${queryElem || elem}`);
165
- return;
151
+ function calculateLaneSizes(
152
+ template,
153
+ containerSize,
154
+ gap,
155
+ fontSize,
156
+ rootFontSize
157
+ ) {
158
+ if (!template || template === "none" || template === "auto") {
159
+ return null
166
160
  }
167
161
 
168
- this.elements = makeArray( queryElem );
169
- this.options = {};
170
- // shift arguments if no options set
171
- if ( typeof options == 'function' ) {
172
- onAlways = options;
173
- } else {
174
- Object.assign( this.options, options );
175
- }
162
+ const availableSpace = containerSize;
163
+ let lanes = [];
164
+ let totalFr = 0;
165
+ let fixedSpace = 0;
166
+
167
+ // Parse the template
168
+ const tokens = tokenizeTemplate(template);
169
+
170
+ for (const token of tokens) {
171
+ // Handle repeat()
172
+ const repeatInfo = parseRepeat(token);
173
+ if (repeatInfo) {
174
+ const { count, pattern } = repeatInfo;
175
+ const patternTokens = tokenizeTemplate(pattern);
176
+
177
+ if (count === "auto-fill" || count === "auto-fit") {
178
+ // Calculate how many repetitions fit
179
+ let minSize = 0;
180
+
181
+ for (const pt of patternTokens) {
182
+ const minmax = parseMinMax(pt);
183
+ if (minmax) {
184
+ const minVal = parseLengthToPixels(
185
+ minmax.min,
186
+ containerSize,
187
+ fontSize,
188
+ rootFontSize
189
+ );
190
+ if (minmax.min === "max-content" || minmax.min === "min-content") {
191
+ minSize += 100; // Fallback estimate
192
+ } else if (minVal !== null) {
193
+ minSize += minVal;
194
+ }
195
+ } else {
196
+ const size = parseLengthToPixels(
197
+ pt,
198
+ containerSize,
199
+ fontSize,
200
+ rootFontSize
201
+ );
202
+ if (size !== null) {
203
+ minSize += size;
204
+ } else if (pt.endsWith("fr")) {
205
+ minSize += 100; // Minimum fallback for fr units
206
+ }
207
+ }
208
+ }
209
+
210
+ // Calculate repetitions
211
+ const patternCount = patternTokens.length;
212
+ const gapCount = patternCount - 1;
213
+ const minPatternSize = minSize + gapCount * gap;
214
+
215
+ let reps = Math.max(
216
+ 1,
217
+ Math.floor((availableSpace + gap) / (minPatternSize + gap))
218
+ );
219
+
220
+ // Expand pattern
221
+ for (let i = 0; i < reps; i++) {
222
+ for (const pt of patternTokens) {
223
+ const minmax = parseMinMax(pt);
224
+ if (minmax) {
225
+ const minVal = parseLengthToPixels(
226
+ minmax.min,
227
+ containerSize,
228
+ fontSize,
229
+ rootFontSize
230
+ );
231
+ const maxVal = minmax.max.endsWith("fr")
232
+ ? { fr: parseFloat(minmax.max) }
233
+ : parseLengthToPixels(
234
+ minmax.max,
235
+ containerSize,
236
+ fontSize,
237
+ rootFontSize
238
+ );
239
+
240
+ lanes.push({
241
+ min: minVal || 0,
242
+ max: maxVal,
243
+ size: 0
244
+ });
245
+
246
+ if (typeof maxVal === "object" && maxVal.fr) {
247
+ totalFr += maxVal.fr;
248
+ } else {
249
+ // Only add to fixedSpace if max is NOT fr (non-flexible tracks)
250
+ fixedSpace += minVal || 0;
251
+ }
252
+ } else if (pt.endsWith("fr")) {
253
+ const fr = parseFloat(pt);
254
+ lanes.push({ min: 0, max: { fr }, size: 0 });
255
+ totalFr += fr;
256
+ } else {
257
+ const size =
258
+ parseLengthToPixels(
259
+ pt,
260
+ containerSize,
261
+ fontSize,
262
+ rootFontSize
263
+ ) || 0;
264
+ lanes.push({ min: size, max: size, size });
265
+ fixedSpace += size;
266
+ }
267
+ }
268
+ }
269
+ } else {
270
+ // Fixed repeat count
271
+ const reps = parseInt(count, 10);
272
+ for (let i = 0; i < reps; i++) {
273
+ for (const pt of patternTokens) {
274
+ const size = parseLengthToPixels(
275
+ pt,
276
+ containerSize,
277
+ fontSize,
278
+ rootFontSize
279
+ );
280
+ if (pt.endsWith("fr")) {
281
+ const fr = parseFloat(pt);
282
+ lanes.push({ min: 0, max: { fr }, size: 0 });
283
+ totalFr += fr;
284
+ } else if (size !== null) {
285
+ lanes.push({ min: size, max: size, size });
286
+ fixedSpace += size;
287
+ }
288
+ }
289
+ }
290
+ }
291
+ continue
292
+ }
176
293
 
177
- if ( onAlways ) this.on( 'always', onAlways );
294
+ // Handle minmax()
295
+ const minmax = parseMinMax(token);
296
+ if (minmax) {
297
+ const minVal = parseLengthToPixels(
298
+ minmax.min,
299
+ containerSize,
300
+ fontSize,
301
+ rootFontSize
302
+ );
303
+ const maxVal = minmax.max.endsWith("fr")
304
+ ? { fr: parseFloat(minmax.max) }
305
+ : parseLengthToPixels(minmax.max, containerSize, fontSize, rootFontSize);
306
+
307
+ lanes.push({ min: minVal || 0, max: maxVal, size: 0 });
308
+ if (typeof maxVal === "object" && maxVal.fr) {
309
+ totalFr += maxVal.fr;
310
+ } else {
311
+ // Only add to fixedSpace if max is NOT fr (non-flexible tracks)
312
+ fixedSpace += minVal || 0;
313
+ }
314
+ continue
315
+ }
178
316
 
179
- this.getImages();
180
- // add jQuery Deferred object
181
- if ( $ ) this.jqDeferred = new $.Deferred();
317
+ // Handle fr units
318
+ if (token.endsWith("fr")) {
319
+ const fr = parseFloat(token);
320
+ lanes.push({ min: 0, max: { fr }, size: 0 });
321
+ totalFr += fr;
322
+ continue
323
+ }
182
324
 
183
- // HACK check async to allow time to bind listeners
184
- setTimeout( this.check.bind( this ) );
185
- }
325
+ // Handle fixed sizes
326
+ const size = parseLengthToPixels(
327
+ token,
328
+ containerSize,
329
+ fontSize,
330
+ rootFontSize
331
+ );
332
+ if (size !== null) {
333
+ lanes.push({ min: size, max: size, size });
334
+ fixedSpace += size;
335
+ }
336
+ }
186
337
 
187
- ImagesLoaded.prototype = Object.create( EvEmitter.prototype );
338
+ // Calculate final sizes
339
+ const totalGaps = Math.max(0, lanes.length - 1) * gap;
340
+ const flexSpace = Math.max(0, availableSpace - fixedSpace - totalGaps);
341
+ const frUnit = totalFr > 0 ? flexSpace / totalFr : 0;
188
342
 
189
- ImagesLoaded.prototype.getImages = function() {
190
- this.images = [];
343
+ for (const lane of lanes) {
344
+ if (typeof lane.max === "object" && lane.max.fr) {
345
+ lane.size = lane.min + frUnit * lane.max.fr;
346
+ } else {
347
+ lane.size = lane.min;
348
+ }
349
+ }
191
350
 
192
- // filter & find items if we have an item selector
193
- this.elements.forEach( this.addElementImages, this );
194
- };
351
+ return lanes.map(l => l.size)
352
+ }
195
353
 
196
- const elementNodeTypes = [ 1, 9, 11 ];
354
+ // ============================================================================
355
+ // STYLE & ITEM UTILITIES
356
+ // ============================================================================
197
357
 
198
358
  /**
199
- * @param {Node} elem
359
+ * Get computed styles for grid-lanes properties
200
360
  */
201
- ImagesLoaded.prototype.addElementImages = function( elem ) {
202
- // filter siblings
203
- if ( elem.nodeName === 'IMG' ) {
204
- this.addImage( elem );
205
- }
206
- // get background image on element
207
- if ( this.options.background === true ) {
208
- this.addElementBackgroundImages( elem );
361
+ function getGridLanesStyles(element) {
362
+ const computed = window.getComputedStyle(element);
363
+ const fontSize = parseFloat(computed.getPropertyValue("font-size")) || 16;
364
+ const rootFontSize =
365
+ parseFloat(
366
+ window
367
+ .getComputedStyle(document.documentElement)
368
+ .getPropertyValue("font-size")
369
+ ) || 16;
370
+
371
+ // Get gap values
372
+ let gap =
373
+ computed.getPropertyValue("gap") ||
374
+ computed.getPropertyValue("grid-gap") ||
375
+ "0px";
376
+ let columnGap =
377
+ computed.getPropertyValue("column-gap") ||
378
+ computed.getPropertyValue("grid-column-gap") ||
379
+ gap;
380
+ let rowGap =
381
+ computed.getPropertyValue("row-gap") ||
382
+ computed.getPropertyValue("grid-row-gap") ||
383
+ gap;
384
+
385
+ // Handle combined gap shorthand like "24px 16px"
386
+ if (gap.includes(" ")) {
387
+ const [rg, cg] = gap.split(/\s+/);
388
+ rowGap = rg;
389
+ columnGap = cg;
209
390
  }
210
391
 
211
- // find children
212
- // no non-element nodes, #143
213
- let { nodeType } = elem;
214
- if ( !nodeType || !elementNodeTypes.includes( nodeType ) ) return;
392
+ // Parse flow-tolerance
393
+ let tolerance = DEFAULT_TOLERANCE;
394
+ const toleranceValue = computed.getPropertyValue("--flow-tolerance").trim();
395
+
396
+ if (toleranceValue) {
397
+ const parsed = parseLengthToPixels(
398
+ toleranceValue,
399
+ 0,
400
+ fontSize,
401
+ rootFontSize
402
+ );
403
+ if (parsed !== null) tolerance = parsed;
404
+ }
215
405
 
216
- let childImgs = elem.querySelectorAll('img');
217
- // concat childElems to filterFound array
218
- for ( let img of childImgs ) {
219
- this.addImage( img );
406
+ return {
407
+ gridTemplateColumns: computed.getPropertyValue("grid-template-columns"),
408
+ gridTemplateRows: computed.getPropertyValue("grid-template-rows"),
409
+ columnGap:
410
+ parseLengthToPixels(
411
+ String(columnGap).split(" ")[0],
412
+ 0,
413
+ fontSize,
414
+ rootFontSize
415
+ ) || 0,
416
+ rowGap:
417
+ parseLengthToPixels(
418
+ String(rowGap).split(" ")[0],
419
+ 0,
420
+ fontSize,
421
+ rootFontSize
422
+ ) || 0,
423
+ fontSize,
424
+ rootFontSize,
425
+ tolerance
220
426
  }
427
+ }
221
428
 
222
- // get child background images
223
- if ( typeof this.options.background == 'string' ) {
224
- let children = elem.querySelectorAll( this.options.background );
225
- for ( let child of children ) {
226
- this.addElementBackgroundImages( child );
429
+ /**
430
+ * Get item placement properties
431
+ */
432
+ function getItemStyles(element) {
433
+ const computed = window.getComputedStyle(element);
434
+ const gridColumn = computed.gridColumn || computed.gridColumnStart;
435
+ const gridRow = computed.gridRow || computed.gridRowStart;
436
+
437
+ let columnSpan = 1;
438
+ let columnStart = null;
439
+ let columnEnd = null;
440
+ let rowSpan = 1;
441
+ let rowStart = null;
442
+ let rowEnd = null;
443
+
444
+ // Parse grid-column
445
+ if (gridColumn && gridColumn !== "auto") {
446
+ const spanMatch = gridColumn.match(/span\s+(\d+)/);
447
+ if (spanMatch) {
448
+ columnSpan = parseInt(spanMatch[1], 10);
449
+ } else if (gridColumn.includes("/")) {
450
+ const [start, end] = gridColumn.split("/").map(s => s.trim());
451
+ columnStart = parseInt(start, 10);
452
+ columnEnd = parseInt(end, 10);
453
+ if (!isNaN(columnStart) && !isNaN(columnEnd)) {
454
+ columnSpan = Math.abs(columnEnd - columnStart);
455
+ }
456
+ } else {
457
+ const num = parseInt(gridColumn, 10);
458
+ if (!isNaN(num)) {
459
+ columnStart = num;
460
+ }
227
461
  }
228
462
  }
229
- };
230
-
231
- const reURL = /url\((['"])?(.*?)\1\)/gi;
232
463
 
233
- ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
234
- let style = getComputedStyle( elem );
235
- // Firefox returns null if in a hidden iframe https://bugzil.la/548397
236
- if ( !style ) return;
237
-
238
- // get url inside url("...")
239
- let matches = reURL.exec( style.backgroundImage );
240
- while ( matches !== null ) {
241
- let url = matches && matches[2];
242
- if ( url ) {
243
- this.addBackground( url, elem );
464
+ // Parse grid-row
465
+ if (gridRow && gridRow !== "auto") {
466
+ const spanMatch = gridRow.match(/span\s+(\d+)/);
467
+ if (spanMatch) {
468
+ rowSpan = parseInt(spanMatch[1], 10);
469
+ } else if (gridRow.includes("/")) {
470
+ const [start, end] = gridRow.split("/").map(s => s.trim());
471
+ rowStart = parseInt(start, 10);
472
+ rowEnd = parseInt(end, 10);
473
+ if (!isNaN(rowStart) && !isNaN(rowEnd)) {
474
+ rowSpan = Math.abs(rowEnd - rowStart);
475
+ }
476
+ } else {
477
+ const num = parseInt(gridRow, 10);
478
+ if (!isNaN(num)) {
479
+ rowStart = num;
480
+ }
244
481
  }
245
- matches = reURL.exec( style.backgroundImage );
246
482
  }
247
- };
248
483
 
249
- /**
250
- * @param {Image} img
251
- */
252
- ImagesLoaded.prototype.addImage = function( img ) {
253
- let loadingImage = new LoadingImage( img );
254
- this.images.push( loadingImage );
255
- };
256
-
257
- ImagesLoaded.prototype.addBackground = function( url, elem ) {
258
- let background = new Background( url, elem );
259
- this.images.push( background );
260
- };
261
-
262
- ImagesLoaded.prototype.check = function() {
263
- this.progressedCount = 0;
264
- this.hasAnyBroken = false;
265
- // complete if no images
266
- if ( !this.images.length ) {
267
- this.complete();
268
- return;
484
+ return {
485
+ columnSpan,
486
+ columnStart,
487
+ columnEnd,
488
+ rowSpan,
489
+ rowStart,
490
+ rowEnd
269
491
  }
492
+ }
270
493
 
271
- /* eslint-disable-next-line func-style */
272
- let onProgress = ( image, elem, message ) => {
273
- // HACK - Chrome triggers event before object properties have changed. #83
274
- setTimeout( () => {
275
- this.progress( image, elem, message );
276
- } );
277
- };
494
+ // ============================================================================
495
+ // STYLE CACHE MANAGEMENT
496
+ // ============================================================================
278
497
 
279
- this.images.forEach( function( loadingImage ) {
280
- loadingImage.once( 'progress', onProgress );
281
- loadingImage.check();
282
- } );
283
- };
284
-
285
- ImagesLoaded.prototype.progress = function( image, elem, message ) {
286
- this.progressedCount++;
287
- this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
288
- // progress event
289
- this.emitEvent( 'progress', [ this, image, elem ] );
290
- if ( this.jqDeferred && this.jqDeferred.notify ) {
291
- this.jqDeferred.notify( this, image );
292
- }
293
- // check if completed
294
- if ( this.progressedCount === this.images.length ) {
295
- this.complete();
498
+ /**
499
+ * Get computed style with caching to reduce redundant calls
500
+ */
501
+ function catchedGetComputedStyle(elem, useCatch = true) {
502
+ if (useCatch && styleMap.has(elem)) {
503
+ return styleMap.get(elem)
296
504
  }
505
+ const style = window.getComputedStyle(elem);
506
+ styleMap.set(elem, style);
507
+ return style
508
+ }
297
509
 
298
- if ( this.options.debug && console ) {
299
- console.log( `progress: ${message}`, image, elem );
300
- }
301
- };
302
-
303
- ImagesLoaded.prototype.complete = function() {
304
- let eventName = this.hasAnyBroken ? 'fail' : 'done';
305
- this.isComplete = true;
306
- this.emitEvent( eventName, [ this ] );
307
- this.emitEvent( 'always', [ this ] );
308
- if ( this.jqDeferred ) {
309
- let jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
310
- this.jqDeferred[ jqMethod ]( this );
311
- }
312
- };
510
+ /**
511
+ * Clear cached style for an element
512
+ */
513
+ function removeCachedStyle(elem) {
514
+ styleMap.delete(elem);
515
+ }
313
516
 
314
- // -------------------------- -------------------------- //
517
+ // ============================================================================
518
+ // DOM UTILITIES
519
+ // ============================================================================
315
520
 
316
- function LoadingImage( img ) {
317
- this.img = img;
521
+ /**
522
+ * Check if element has the grid-lanes polyfill marker
523
+ */
524
+ function hasGridLanesProperty(elem) {
525
+ return (
526
+ catchedGetComputedStyle(elem)
527
+ .getPropertyValue("--grid-lanes-polyfill")
528
+ .trim() === "1"
529
+ )
318
530
  }
319
531
 
320
- LoadingImage.prototype = Object.create( EvEmitter.prototype );
321
-
322
- LoadingImage.prototype.check = function() {
323
- // If complete is true and browser supports natural sizes,
324
- // try to check for image status manually.
325
- let isComplete = this.getIsImageComplete();
326
- if ( isComplete ) {
327
- // report based on naturalWidth
328
- this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
329
- return;
532
+ /**
533
+ * Find all elements with --grid-lanes-polyfill down to the granular level.
534
+ * Prefers children over parent elements. Note: nested grid-lanes are not
535
+ * supported because the custom property cascades to all descendants.
536
+ *
537
+ * Uses TreeWalker for efficient traversal and routes through hasGridLanesProperty()
538
+ * to leverage the catchedGetComputedStyle cache. Correctly handles subtree skipping.
539
+ */
540
+ function findElements(root = document.body) {
541
+ const results = [];
542
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
543
+
544
+ let node = walker.nextNode(); // start from first child, skip root itself
545
+ while (node) {
546
+ if (hasGridLanesProperty(node)) {
547
+ results.push(node);
548
+ // Skip the entire subtree of this match by finding the next
549
+ // node outside of it: try nextSibling, then bubble up ancestors.
550
+ let next = null;
551
+ let ancestor = node;
552
+ while (ancestor && ancestor !== root) {
553
+ walker.currentNode = ancestor;
554
+ next = walker.nextSibling();
555
+ if (next) break
556
+ ancestor = ancestor.parentElement;
557
+ }
558
+ node = next; // null means no more nodes in tree
559
+ } else {
560
+ node = walker.nextNode();
561
+ }
330
562
  }
563
+ return results
564
+ }
331
565
 
332
- // If none of the checks above matched, simulate loading on detached element.
333
- this.proxyImage = new Image();
334
- // add crossOrigin attribute. #204
335
- if ( this.img.crossOrigin ) {
336
- this.proxyImage.crossOrigin = this.img.crossOrigin;
337
- }
338
- this.proxyImage.addEventListener( 'load', this );
339
- this.proxyImage.addEventListener( 'error', this );
340
- // bind to image as well for Firefox. #191
341
- this.img.addEventListener( 'load', this );
342
- this.img.addEventListener( 'error', this );
343
- this.proxyImage.src = this.img.currentSrc || this.img.src;
344
- };
345
-
346
- LoadingImage.prototype.getIsImageComplete = function() {
347
- // check for non-zero, non-undefined naturalWidth
348
- // fixes Safari+InfiniteScroll+Masonry bug infinite-scroll#671
349
- return this.img.complete && this.img.naturalWidth;
350
- };
351
-
352
- LoadingImage.prototype.confirm = function( isLoaded, message ) {
353
- this.isLoaded = isLoaded;
354
- let { parentNode } = this.img;
355
- // emit progress with parent <picture> or self <img>
356
- let elem = parentNode.nodeName === 'PICTURE' ? parentNode : this.img;
357
- this.emitEvent( 'progress', [ this, elem, message ] );
358
- };
359
-
360
- // ----- events ----- //
361
-
362
- // trigger specified handler for event type
363
- LoadingImage.prototype.handleEvent = function( event ) {
364
- let method = 'on' + event.type;
365
- if ( this[ method ] ) {
366
- this[ method ]( event );
566
+ /**
567
+ * Process a subtree for grid-lanes containers
568
+ */
569
+ function processSubtree(root, instances, options) {
570
+ const containers = findElements(root);
571
+ for (const container of containers) {
572
+ if (!instances.has(container) && !container.hasAttribute(POLYFILL_ATTR)) {
573
+ instances.set(container, new GridLanesLayout(container, options));
574
+ }
367
575
  }
368
- };
369
-
370
- LoadingImage.prototype.onload = function() {
371
- this.confirm( true, 'onload' );
372
- this.unbindEvents();
373
- };
374
-
375
- LoadingImage.prototype.onerror = function() {
376
- this.confirm( false, 'onerror' );
377
- this.unbindEvents();
378
- };
379
-
380
- LoadingImage.prototype.unbindEvents = function() {
381
- this.proxyImage.removeEventListener( 'load', this );
382
- this.proxyImage.removeEventListener( 'error', this );
383
- this.img.removeEventListener( 'load', this );
384
- this.img.removeEventListener( 'error', this );
385
- };
386
-
387
- // -------------------------- Background -------------------------- //
388
-
389
- function Background( url, element ) {
390
- this.url = url;
391
- this.element = element;
392
- this.img = new Image();
393
576
  }
394
577
 
395
- // inherit LoadingImage prototype
396
- Background.prototype = Object.create( LoadingImage.prototype );
397
-
398
- Background.prototype.check = function() {
399
- this.img.addEventListener( 'load', this );
400
- this.img.addEventListener( 'error', this );
401
- this.img.src = this.url;
402
- // check if image is already complete
403
- let isComplete = this.getIsImageComplete();
404
- if ( isComplete ) {
405
- this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
406
- this.unbindEvents();
407
- }
408
- };
409
-
410
- Background.prototype.unbindEvents = function() {
411
- this.img.removeEventListener( 'load', this );
412
- this.img.removeEventListener( 'error', this );
413
- };
414
-
415
- Background.prototype.confirm = function( isLoaded, message ) {
416
- this.isLoaded = isLoaded;
417
- this.emitEvent( 'progress', [ this, this.element, message ] );
418
- };
419
-
420
- // -------------------------- jQuery -------------------------- //
421
-
422
- ImagesLoaded.makeJQueryPlugin = function( jQuery ) {
423
- jQuery = jQuery || window.jQuery;
424
- if ( !jQuery ) return;
425
-
426
- // set local variable
427
- $ = jQuery;
428
- // $().imagesLoaded()
429
- $.fn.imagesLoaded = function( options, onAlways ) {
430
- let instance = new ImagesLoaded( this, options, onAlways );
431
- return instance.jqDeferred.promise( $( this ) );
432
- };
433
- };
434
- // try making plugin
435
- ImagesLoaded.makeJQueryPlugin();
578
+ // ============================================================================
579
+ // GRID LANES LAYOUT CLASS
580
+ // ============================================================================
436
581
 
437
- // -------------------------- -------------------------- //
582
+ /**
583
+ * Main Grid Lanes layout class
584
+ */
585
+ class GridLanesLayout {
586
+ constructor(container, options = {}) {
587
+ this.container = container;
588
+ this.options = options;
589
+ this.isVertical = true; // true = waterfall (columns), false = brick (rows)
590
+ this.lanes = [];
591
+ this.laneHeights = [];
592
+ this.resizeObserver = null;
593
+ this.mutationObserver = null;
438
594
 
439
- return ImagesLoaded;
595
+ this.init();
596
+ }
440
597
 
441
- } );
598
+ init() {
599
+ // Mark as polyfilled
600
+ this.container.setAttribute(POLYFILL_ATTR, "true");
442
601
 
443
- /*!
444
- * Masonry PACKAGED v4.2.2
445
- * Cascading grid layout library
446
- * https://masonry.desandro.com
447
- * MIT License
448
- * by David DeSandro
449
- */
602
+ // Set up container styles
603
+ this.container.style.position = "relative";
604
+ this.container.style.display = "block";
450
605
 
451
- !function(t,e){"function"==typeof define&&define.amd?define("jquery-bridget/jquery-bridget",["jquery"],function(i){return e(t,i)}):"object"==typeof module&&module.exports?module.exports=e(t,require("jquery")):t.jQueryBridget=e(t,t.jQuery);}(window,function(t,e){function i(i,r,a){function h(t,e,n){var o,r="$()."+i+'("'+e+'")';return t.each(function(t,h){var u=a.data(h,i);if(!u)return void s(i+" not initialized. Cannot call methods, i.e. "+r);var d=u[e];if(!d||"_"==e.charAt(0))return void s(r+" is not a valid method");var l=d.apply(u,n);o=void 0===o?l:o;}),void 0!==o?o:t}function u(t,e){t.each(function(t,n){var o=a.data(n,i);o?(o.option(e),o._init()):(o=new r(n,e),a.data(n,i,o));});}a=a||e||t.jQuery,a&&(r.prototype.option||(r.prototype.option=function(t){a.isPlainObject(t)&&(this.options=a.extend(true,this.options,t));}),a.fn[i]=function(t){if("string"==typeof t){var e=o.call(arguments,1);return h(this,t,e)}return u(this,t),this},n(a));}function n(t){!t||t&&t.bridget||(t.bridget=i);}var o=Array.prototype.slice,r=t.console,s="undefined"==typeof r?function(){}:function(t){r.error(t);};return n(e||t.jQuery),i}),function(t,e){"function"==typeof define&&define.amd?define("ev-emitter/ev-emitter",e):"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e();}("undefined"!=typeof window?window:undefined,function(){function t(){}var e=t.prototype;return e.on=function(t,e){if(t&&e){var i=this._events=this._events||{},n=i[t]=i[t]||[];return -1==n.indexOf(e)&&n.push(e),this}},e.once=function(t,e){if(t&&e){this.on(t,e);var i=this._onceEvents=this._onceEvents||{},n=i[t]=i[t]||{};return n[e]=true,this}},e.off=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var n=i.indexOf(e);return -1!=n&&i.splice(n,1),this}},e.emitEvent=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){i=i.slice(0),e=e||[];for(var n=this._onceEvents&&this._onceEvents[t],o=0;o<i.length;o++){var r=i[o],s=n&&n[r];s&&(this.off(t,r),delete n[r]),r.apply(this,e);}return this}},e.allOff=function(){delete this._events,delete this._onceEvents;},t}),function(t,e){"function"==typeof define&&define.amd?define("get-size/get-size",e):"object"==typeof module&&module.exports?module.exports=e():t.getSize=e();}(window,function(){function t(t){var e=parseFloat(t),i=-1==t.indexOf("%")&&!isNaN(e);return i&&e}function e(){}function i(){for(var t={width:0,height:0,innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0},e=0;u>e;e++){var i=h[e];t[i]=0;}return t}function n(t){var e=getComputedStyle(t);return e||a("Style returned "+e+". Are you running this code in a hidden iframe on Firefox? See https://bit.ly/getsizebug1"),e}function o(){if(!d){d=true;var e=document.createElement("div");e.style.width="200px",e.style.padding="1px 2px 3px 4px",e.style.borderStyle="solid",e.style.borderWidth="1px 2px 3px 4px",e.style.boxSizing="border-box";var i=document.body||document.documentElement;i.appendChild(e);var o=n(e);s=200==Math.round(t(o.width)),r.isBoxSizeOuter=s,i.removeChild(e);}}function r(e){if(o(),"string"==typeof e&&(e=document.querySelector(e)),e&&"object"==typeof e&&e.nodeType){var r=n(e);if("none"==r.display)return i();var a={};a.width=e.offsetWidth,a.height=e.offsetHeight;for(var d=a.isBorderBox="border-box"==r.boxSizing,l=0;u>l;l++){var c=h[l],f=r[c],m=parseFloat(f);a[c]=isNaN(m)?0:m;}var p=a.paddingLeft+a.paddingRight,g=a.paddingTop+a.paddingBottom,y=a.marginLeft+a.marginRight,v=a.marginTop+a.marginBottom,_=a.borderLeftWidth+a.borderRightWidth,z=a.borderTopWidth+a.borderBottomWidth,E=d&&s,b=t(r.width);b!==false&&(a.width=b+(E?0:p+_));var x=t(r.height);return x!==false&&(a.height=x+(E?0:g+z)),a.innerWidth=a.width-(p+_),a.innerHeight=a.height-(g+z),a.outerWidth=a.width+y,a.outerHeight=a.height+v,a}}var s,a="undefined"==typeof console?e:function(t){console.error(t);},h=["paddingLeft","paddingRight","paddingTop","paddingBottom","marginLeft","marginRight","marginTop","marginBottom","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth"],u=h.length,d=false;return r}),function(t,e){"function"==typeof define&&define.amd?define("desandro-matches-selector/matches-selector",e):"object"==typeof module&&module.exports?module.exports=e():t.matchesSelector=e();}(window,function(){var t=function(){var t=window.Element.prototype;if(t.matches)return "matches";if(t.matchesSelector)return "matchesSelector";for(var e=["webkit","moz","ms","o"],i=0;i<e.length;i++){var n=e[i],o=n+"MatchesSelector";if(t[o])return o}}();return function(e,i){return e[t](i)}}),function(t,e){"function"==typeof define&&define.amd?define("fizzy-ui-utils/utils",["desandro-matches-selector/matches-selector"],function(i){return e(t,i)}):"object"==typeof module&&module.exports?module.exports=e(t,require("desandro-matches-selector")):t.fizzyUIUtils=e(t,t.matchesSelector);}(window,function(t,e){var i={};i.extend=function(t,e){for(var i in e)t[i]=e[i];return t},i.modulo=function(t,e){return (t%e+e)%e};var n=Array.prototype.slice;i.makeArray=function(t){if(Array.isArray(t))return t;if(null===t||void 0===t)return [];var e="object"==typeof t&&"number"==typeof t.length;return e?n.call(t):[t]},i.removeFrom=function(t,e){var i=t.indexOf(e);-1!=i&&t.splice(i,1);},i.getParent=function(t,i){for(;t.parentNode&&t!=document.body;)if(t=t.parentNode,e(t,i))return t},i.getQueryElement=function(t){return "string"==typeof t?document.querySelector(t):t},i.handleEvent=function(t){var e="on"+t.type;this[e]&&this[e](t);},i.filterFindElements=function(t,n){t=i.makeArray(t);var o=[];return t.forEach(function(t){if(t instanceof HTMLElement){if(!n)return void o.push(t);e(t,n)&&o.push(t);for(var i=t.querySelectorAll(n),r=0;r<i.length;r++)o.push(i[r]);}}),o},i.debounceMethod=function(t,e,i){i=i||100;var n=t.prototype[e],o=e+"Timeout";t.prototype[e]=function(){var t=this[o];clearTimeout(t);var e=arguments,r=this;this[o]=setTimeout(function(){n.apply(r,e),delete r[o];},i);};},i.docReady=function(t){var e=document.readyState;"complete"==e||"interactive"==e?setTimeout(t):document.addEventListener("DOMContentLoaded",t);},i.toDashed=function(t){return t.replace(/(.)([A-Z])/g,function(t,e,i){return e+"-"+i}).toLowerCase()};var o=t.console;return i.htmlInit=function(e,n){i.docReady(function(){var r=i.toDashed(n),s="data-"+r,a=document.querySelectorAll("["+s+"]"),h=document.querySelectorAll(".js-"+r),u=i.makeArray(a).concat(i.makeArray(h)),d=s+"-options",l=t.jQuery;u.forEach(function(t){var i,r=t.getAttribute(s)||t.getAttribute(d);try{i=r&&JSON.parse(r);}catch(a){return void(o&&o.error("Error parsing "+s+" on "+t.className+": "+a))}var h=new e(t,i);l&&l.data(t,n,h);});});},i}),function(t,e){"function"==typeof define&&define.amd?define("outlayer/item",["ev-emitter/ev-emitter","get-size/get-size"],e):"object"==typeof module&&module.exports?module.exports=e(require("ev-emitter"),require("get-size")):(t.Outlayer={},t.Outlayer.Item=e(t.EvEmitter,t.getSize));}(window,function(t,e){function i(t){for(var e in t)return false;return e=null,true}function n(t,e){t&&(this.element=t,this.layout=e,this.position={x:0,y:0},this._create());}function o(t){return t.replace(/([A-Z])/g,function(t){return "-"+t.toLowerCase()})}var r=document.documentElement.style,s="string"==typeof r.transition?"transition":"WebkitTransition",a="string"==typeof r.transform?"transform":"WebkitTransform",h={WebkitTransition:"webkitTransitionEnd",transition:"transitionend"}[s],u={transform:a,transition:s,transitionDuration:s+"Duration",transitionProperty:s+"Property",transitionDelay:s+"Delay"},d=n.prototype=Object.create(t.prototype);d.constructor=n,d._create=function(){this._transn={ingProperties:{},clean:{},onEnd:{}},this.css({position:"absolute"});},d.handleEvent=function(t){var e="on"+t.type;this[e]&&this[e](t);},d.getSize=function(){this.size=e(this.element);},d.css=function(t){var e=this.element.style;for(var i in t){var n=u[i]||i;e[n]=t[i];}},d.getPosition=function(){var t=getComputedStyle(this.element),e=this.layout._getOption("originLeft"),i=this.layout._getOption("originTop"),n=t[e?"left":"right"],o=t[i?"top":"bottom"],r=parseFloat(n),s=parseFloat(o),a=this.layout.size;-1!=n.indexOf("%")&&(r=r/100*a.width),-1!=o.indexOf("%")&&(s=s/100*a.height),r=isNaN(r)?0:r,s=isNaN(s)?0:s,r-=e?a.paddingLeft:a.paddingRight,s-=i?a.paddingTop:a.paddingBottom,this.position.x=r,this.position.y=s;},d.layoutPosition=function(){var t=this.layout.size,e={},i=this.layout._getOption("originLeft"),n=this.layout._getOption("originTop"),o=i?"paddingLeft":"paddingRight",r=i?"left":"right",s=i?"right":"left",a=this.position.x+t[o];e[r]=this.getXValue(a),e[s]="";var h=n?"paddingTop":"paddingBottom",u=n?"top":"bottom",d=n?"bottom":"top",l=this.position.y+t[h];e[u]=this.getYValue(l),e[d]="",this.css(e),this.emitEvent("layout",[this]);},d.getXValue=function(t){var e=this.layout._getOption("horizontal");return this.layout.options.percentPosition&&!e?t/this.layout.size.width*100+"%":t+"px"},d.getYValue=function(t){var e=this.layout._getOption("horizontal");return this.layout.options.percentPosition&&e?t/this.layout.size.height*100+"%":t+"px"},d._transitionTo=function(t,e){this.getPosition();var i=this.position.x,n=this.position.y,o=t==this.position.x&&e==this.position.y;if(this.setPosition(t,e),o&&!this.isTransitioning)return void this.layoutPosition();var r=t-i,s=e-n,a={};a.transform=this.getTranslate(r,s),this.transition({to:a,onTransitionEnd:{transform:this.layoutPosition},isCleaning:true});},d.getTranslate=function(t,e){var i=this.layout._getOption("originLeft"),n=this.layout._getOption("originTop");return t=i?t:-t,e=n?e:-e,"translate3d("+t+"px, "+e+"px, 0)"},d.goTo=function(t,e){this.setPosition(t,e),this.layoutPosition();},d.moveTo=d._transitionTo,d.setPosition=function(t,e){this.position.x=parseFloat(t),this.position.y=parseFloat(e);},d._nonTransition=function(t){this.css(t.to),t.isCleaning&&this._removeStyles(t.to);for(var e in t.onTransitionEnd)t.onTransitionEnd[e].call(this);},d.transition=function(t){if(!parseFloat(this.layout.options.transitionDuration))return void this._nonTransition(t);var e=this._transn;for(var i in t.onTransitionEnd)e.onEnd[i]=t.onTransitionEnd[i];for(i in t.to)e.ingProperties[i]=true,t.isCleaning&&(e.clean[i]=true);if(t.from){this.css(t.from);this.element.offsetHeight;}this.enableTransition(t.to),this.css(t.to),this.isTransitioning=true;};var l="opacity,"+o(a);d.enableTransition=function(){if(!this.isTransitioning){var t=this.layout.options.transitionDuration;t="number"==typeof t?t+"ms":t,this.css({transitionProperty:l,transitionDuration:t,transitionDelay:this.staggerDelay||0}),this.element.addEventListener(h,this,false);}},d.onwebkitTransitionEnd=function(t){this.ontransitionend(t);},d.onotransitionend=function(t){this.ontransitionend(t);};var c={"-webkit-transform":"transform"};d.ontransitionend=function(t){if(t.target===this.element){var e=this._transn,n=c[t.propertyName]||t.propertyName;if(delete e.ingProperties[n],i(e.ingProperties)&&this.disableTransition(),n in e.clean&&(this.element.style[t.propertyName]="",delete e.clean[n]),n in e.onEnd){var o=e.onEnd[n];o.call(this),delete e.onEnd[n];}this.emitEvent("transitionEnd",[this]);}},d.disableTransition=function(){this.removeTransitionStyles(),this.element.removeEventListener(h,this,false),this.isTransitioning=false;},d._removeStyles=function(t){var e={};for(var i in t)e[i]="";this.css(e);};var f={transitionProperty:"",transitionDuration:"",transitionDelay:""};return d.removeTransitionStyles=function(){this.css(f);},d.stagger=function(t){t=isNaN(t)?0:t,this.staggerDelay=t+"ms";},d.removeElem=function(){this.element.parentNode.removeChild(this.element),this.css({display:""}),this.emitEvent("remove",[this]);},d.remove=function(){return s&&parseFloat(this.layout.options.transitionDuration)?(this.once("transitionEnd",function(){this.removeElem();}),void this.hide()):void this.removeElem()},d.reveal=function(){delete this.isHidden,this.css({display:""});var t=this.layout.options,e={},i=this.getHideRevealTransitionEndProperty("visibleStyle");e[i]=this.onRevealTransitionEnd,this.transition({from:t.hiddenStyle,to:t.visibleStyle,isCleaning:true,onTransitionEnd:e});},d.onRevealTransitionEnd=function(){this.isHidden||this.emitEvent("reveal");},d.getHideRevealTransitionEndProperty=function(t){var e=this.layout.options[t];if(e.opacity)return "opacity";for(var i in e)return i},d.hide=function(){this.isHidden=true,this.css({display:""});var t=this.layout.options,e={},i=this.getHideRevealTransitionEndProperty("hiddenStyle");e[i]=this.onHideTransitionEnd,this.transition({from:t.visibleStyle,to:t.hiddenStyle,isCleaning:true,onTransitionEnd:e});},d.onHideTransitionEnd=function(){this.isHidden&&(this.css({display:"none"}),this.emitEvent("hide"));},d.destroy=function(){this.css({position:"",left:"",right:"",top:"",bottom:"",transition:"",transform:""});},n}),function(t,e){"function"==typeof define&&define.amd?define("outlayer/outlayer",["ev-emitter/ev-emitter","get-size/get-size","fizzy-ui-utils/utils","./item"],function(i,n,o,r){return e(t,i,n,o,r)}):"object"==typeof module&&module.exports?module.exports=e(t,require("ev-emitter"),require("get-size"),require("fizzy-ui-utils"),require("./item")):t.Outlayer=e(t,t.EvEmitter,t.getSize,t.fizzyUIUtils,t.Outlayer.Item);}(window,function(t,e,i,n,o){function r(t,e){var i=n.getQueryElement(t);if(!i)return void(h&&h.error("Bad element for "+this.constructor.namespace+": "+(i||t)));this.element=i,u&&(this.$element=u(this.element)),this.options=n.extend({},this.constructor.defaults),this.option(e);var o=++l;this.element.outlayerGUID=o,c[o]=this,this._create();var r=this._getOption("initLayout");r&&this.layout();}function s(t){function e(){t.apply(this,arguments);}return e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e}function a(t){if("number"==typeof t)return t;var e=t.match(/(^\d*\.?\d*)(\w*)/),i=e&&e[1],n=e&&e[2];if(!i.length)return 0;i=parseFloat(i);var o=m[n]||1;return i*o}var h=t.console,u=t.jQuery,d=function(){},l=0,c={};r.namespace="outlayer",r.Item=o,r.defaults={containerStyle:{position:"relative"},initLayout:true,originLeft:true,originTop:true,resize:true,resizeContainer:true,transitionDuration:"0.4s",hiddenStyle:{opacity:0,transform:"scale(0.001)"},visibleStyle:{opacity:1,transform:"scale(1)"}};var f=r.prototype;n.extend(f,e.prototype),f.option=function(t){n.extend(this.options,t);},f._getOption=function(t){var e=this.constructor.compatOptions[t];return e&&void 0!==this.options[e]?this.options[e]:this.options[t]},r.compatOptions={initLayout:"isInitLayout",horizontal:"isHorizontal",layoutInstant:"isLayoutInstant",originLeft:"isOriginLeft",originTop:"isOriginTop",resize:"isResizeBound",resizeContainer:"isResizingContainer"},f._create=function(){this.reloadItems(),this.stamps=[],this.stamp(this.options.stamp),n.extend(this.element.style,this.options.containerStyle);var t=this._getOption("resize");t&&this.bindResize();},f.reloadItems=function(){this.items=this._itemize(this.element.children);},f._itemize=function(t){for(var e=this._filterFindItemElements(t),i=this.constructor.Item,n=[],o=0;o<e.length;o++){var r=e[o],s=new i(r,this);n.push(s);}return n},f._filterFindItemElements=function(t){return n.filterFindElements(t,this.options.itemSelector)},f.getItemElements=function(){return this.items.map(function(t){return t.element})},f.layout=function(){this._resetLayout(),this._manageStamps();var t=this._getOption("layoutInstant"),e=void 0!==t?t:!this._isLayoutInited;this.layoutItems(this.items,e),this._isLayoutInited=true;},f._init=f.layout,f._resetLayout=function(){this.getSize();},f.getSize=function(){this.size=i(this.element);},f._getMeasurement=function(t,e){var n,o=this.options[t];o?("string"==typeof o?n=this.element.querySelector(o):o instanceof HTMLElement&&(n=o),this[t]=n?i(n)[e]:o):this[t]=0;},f.layoutItems=function(t,e){t=this._getItemsForLayout(t),this._layoutItems(t,e),this._postLayout();},f._getItemsForLayout=function(t){return t.filter(function(t){return !t.isIgnored})},f._layoutItems=function(t,e){if(this._emitCompleteOnItems("layout",t),t&&t.length){var i=[];t.forEach(function(t){var n=this._getItemLayoutPosition(t);n.item=t,n.isInstant=e||t.isLayoutInstant,i.push(n);},this),this._processLayoutQueue(i);}},f._getItemLayoutPosition=function(){return {x:0,y:0}},f._processLayoutQueue=function(t){this.updateStagger(),t.forEach(function(t,e){this._positionItem(t.item,t.x,t.y,t.isInstant,e);},this);},f.updateStagger=function(){var t=this.options.stagger;return null===t||void 0===t?void(this.stagger=0):(this.stagger=a(t),this.stagger)},f._positionItem=function(t,e,i,n,o){n?t.goTo(e,i):(t.stagger(o*this.stagger),t.moveTo(e,i));},f._postLayout=function(){this.resizeContainer();},f.resizeContainer=function(){var t=this._getOption("resizeContainer");if(t){var e=this._getContainerSize();e&&(this._setContainerMeasure(e.width,true),this._setContainerMeasure(e.height,false));}},f._getContainerSize=d,f._setContainerMeasure=function(t,e){if(void 0!==t){var i=this.size;i.isBorderBox&&(t+=e?i.paddingLeft+i.paddingRight+i.borderLeftWidth+i.borderRightWidth:i.paddingBottom+i.paddingTop+i.borderTopWidth+i.borderBottomWidth),t=Math.max(t,0),this.element.style[e?"width":"height"]=t+"px";}},f._emitCompleteOnItems=function(t,e){function i(){o.dispatchEvent(t+"Complete",null,[e]);}function n(){s++,s==r&&i();}var o=this,r=e.length;if(!e||!r)return void i();var s=0;e.forEach(function(e){e.once(t,n);});},f.dispatchEvent=function(t,e,i){var n=e?[e].concat(i):i;if(this.emitEvent(t,n),u)if(this.$element=this.$element||u(this.element),e){var o=u.Event(e);o.type=t,this.$element.trigger(o,i);}else this.$element.trigger(t,i);},f.ignore=function(t){var e=this.getItem(t);e&&(e.isIgnored=true);},f.unignore=function(t){var e=this.getItem(t);e&&delete e.isIgnored;},f.stamp=function(t){t=this._find(t),t&&(this.stamps=this.stamps.concat(t),t.forEach(this.ignore,this));},f.unstamp=function(t){t=this._find(t),t&&t.forEach(function(t){n.removeFrom(this.stamps,t),this.unignore(t);},this);},f._find=function(t){return t?("string"==typeof t&&(t=this.element.querySelectorAll(t)),t=n.makeArray(t)):void 0},f._manageStamps=function(){this.stamps&&this.stamps.length&&(this._getBoundingRect(),this.stamps.forEach(this._manageStamp,this));},f._getBoundingRect=function(){var t=this.element.getBoundingClientRect(),e=this.size;this._boundingRect={left:t.left+e.paddingLeft+e.borderLeftWidth,top:t.top+e.paddingTop+e.borderTopWidth,right:t.right-(e.paddingRight+e.borderRightWidth),bottom:t.bottom-(e.paddingBottom+e.borderBottomWidth)};},f._manageStamp=d,f._getElementOffset=function(t){var e=t.getBoundingClientRect(),n=this._boundingRect,o=i(t),r={left:e.left-n.left-o.marginLeft,top:e.top-n.top-o.marginTop,right:n.right-e.right-o.marginRight,bottom:n.bottom-e.bottom-o.marginBottom};return r},f.handleEvent=n.handleEvent,f.bindResize=function(){t.addEventListener("resize",this),this.isResizeBound=true;},f.unbindResize=function(){t.removeEventListener("resize",this),this.isResizeBound=false;},f.onresize=function(){this.resize();},n.debounceMethod(r,"onresize",100),f.resize=function(){this.isResizeBound&&this.needsResizeLayout()&&this.layout();},f.needsResizeLayout=function(){var t=i(this.element),e=this.size&&t;return e&&t.innerWidth!==this.size.innerWidth},f.addItems=function(t){var e=this._itemize(t);return e.length&&(this.items=this.items.concat(e)),e},f.appended=function(t){var e=this.addItems(t);e.length&&(this.layoutItems(e,true),this.reveal(e));},f.prepended=function(t){var e=this._itemize(t);if(e.length){var i=this.items.slice(0);this.items=e.concat(i),this._resetLayout(),this._manageStamps(),this.layoutItems(e,true),this.reveal(e),this.layoutItems(i);}},f.reveal=function(t){if(this._emitCompleteOnItems("reveal",t),t&&t.length){var e=this.updateStagger();t.forEach(function(t,i){t.stagger(i*e),t.reveal();});}},f.hide=function(t){if(this._emitCompleteOnItems("hide",t),t&&t.length){var e=this.updateStagger();t.forEach(function(t,i){t.stagger(i*e),t.hide();});}},f.revealItemElements=function(t){var e=this.getItems(t);this.reveal(e);},f.hideItemElements=function(t){var e=this.getItems(t);this.hide(e);},f.getItem=function(t){for(var e=0;e<this.items.length;e++){var i=this.items[e];if(i.element==t)return i}},f.getItems=function(t){t=n.makeArray(t);var e=[];return t.forEach(function(t){var i=this.getItem(t);i&&e.push(i);},this),e},f.remove=function(t){var e=this.getItems(t);this._emitCompleteOnItems("remove",e),e&&e.length&&e.forEach(function(t){t.remove(),n.removeFrom(this.items,t);},this);},f.destroy=function(){var t=this.element.style;t.height="",t.position="",t.width="",this.items.forEach(function(t){t.destroy();}),this.unbindResize();var e=this.element.outlayerGUID;delete c[e],delete this.element.outlayerGUID,u&&u.removeData(this.element,this.constructor.namespace);},r.data=function(t){t=n.getQueryElement(t);var e=t&&t.outlayerGUID;return e&&c[e]},r.create=function(t,e){var i=s(r);return i.defaults=n.extend({},r.defaults),n.extend(i.defaults,e),i.compatOptions=n.extend({},r.compatOptions),i.namespace=t,i.data=r.data,i.Item=s(o),n.htmlInit(i,t),u&&u.bridget&&u.bridget(t,i),i};var m={ms:1,s:1e3};return r.Item=o,r}),function(t,e){"function"==typeof define&&define.amd?define(["outlayer/outlayer","get-size/get-size"],e):"object"==typeof module&&module.exports?module.exports=e(require("outlayer"),require("get-size")):t.Masonry=e(t.Outlayer,t.getSize);}(window,function(t,e){var i=t.create("masonry");i.compatOptions.fitWidth="isFitWidth";var n=i.prototype;return n._resetLayout=function(){this.getSize(),this._getMeasurement("columnWidth","outerWidth"),this._getMeasurement("gutter","outerWidth"),this.measureColumns(),this.colYs=[];for(var t=0;t<this.cols;t++)this.colYs.push(0);this.maxY=0,this.horizontalColIndex=0;},n.measureColumns=function(){if(this.getContainerWidth(),!this.columnWidth){var t=this.items[0],i=t&&t.element;this.columnWidth=i&&e(i).outerWidth||this.containerWidth;}var n=this.columnWidth+=this.gutter,o=this.containerWidth+this.gutter,r=o/n,s=n-o%n,a=s&&1>s?"round":"floor";r=Math[a](r),this.cols=Math.max(r,1);},n.getContainerWidth=function(){var t=this._getOption("fitWidth"),i=t?this.element.parentNode:this.element,n=e(i);this.containerWidth=n&&n.innerWidth;},n._getItemLayoutPosition=function(t){t.getSize();var e=t.size.outerWidth%this.columnWidth,i=e&&1>e?"round":"ceil",n=Math[i](t.size.outerWidth/this.columnWidth);n=Math.min(n,this.cols);for(var o=this.options.horizontalOrder?"_getHorizontalColPosition":"_getTopColPosition",r=this[o](n,t),s={x:this.columnWidth*r.col,y:r.y},a=r.y+t.size.outerHeight,h=n+r.col,u=r.col;h>u;u++)this.colYs[u]=a;return s},n._getTopColPosition=function(t){var e=this._getTopColGroup(t),i=Math.min.apply(Math,e);return {col:e.indexOf(i),y:i}},n._getTopColGroup=function(t){if(2>t)return this.colYs;for(var e=[],i=this.cols+1-t,n=0;i>n;n++)e[n]=this._getColGroupY(n,t);return e},n._getColGroupY=function(t,e){if(2>e)return this.colYs[t];var i=this.colYs.slice(t,t+e);return Math.max.apply(Math,i)},n._getHorizontalColPosition=function(t,e){var i=this.horizontalColIndex%this.cols,n=t>1&&i+t>this.cols;i=n?0:i;var o=e.size.outerWidth&&e.size.outerHeight;return this.horizontalColIndex=o?i+t:this.horizontalColIndex,{col:i,y:this._getColGroupY(i,t)}},n._manageStamp=function(t){var i=e(t),n=this._getElementOffset(t),o=this._getOption("originLeft"),r=o?n.left:n.right,s=r+i.outerWidth,a=Math.floor(r/this.columnWidth);a=Math.max(0,a);var h=Math.floor(s/this.columnWidth);h-=s%this.columnWidth?0:1,h=Math.min(this.cols-1,h);for(var u=this._getOption("originTop"),d=(u?n.top:n.bottom)+i.outerHeight,l=a;h>=l;l++)this.colYs[l]=Math.max(d,this.colYs[l]);},n._getContainerSize=function(){this.maxY=Math.max.apply(Math,this.colYs);var t={height:this.maxY};return this._getOption("fitWidth")&&(t.width=this._getContainerFitWidth()),t},n._getContainerFitWidth=function(){for(var t=0,e=this.cols;--e&&0===this.colYs[e];)t++;return (this.cols-t)*this.columnWidth-this.gutter},n.needsResizeLayout=function(){var t=this.containerWidth;return this.getContainerWidth(),t!=this.containerWidth},i});
606
+ // Initial layout
607
+ this.layout();
452
608
 
453
- (function($){
454
- var Slideshow = function (element, options) {
455
- this.$element = $(element);
456
- this.options = options;
457
- this.paused = false;
458
- this.activeIndex = 0;
609
+ // Set up observers
610
+ this.setupObservers();
611
+ }
459
612
 
460
- this.init = function() {
461
- this.$items = this.$element.find('.item');
613
+ setupObservers() {
614
+ // Debounce layout calls to avoid excessive recalculations
615
+ let layoutTimeout = null;
616
+ const debouncedLayout = () => {
617
+ if (layoutTimeout) clearTimeout(layoutTimeout);
618
+ layoutTimeout = setTimeout(() => this.layout(), 16);
462
619
  };
463
620
 
464
- this.attachEvents();
465
- this.init();
466
- };
467
-
468
-
469
- Slideshow.prototype = {
621
+ // Resize observer for responsive layouts AND child size changes
622
+ this.resizeObserver = new ResizeObserver(entries => {
623
+ // Check if it's the container or a child that resized
624
+ for (const entry of entries) {
625
+ if (
626
+ entry.target === this.container ||
627
+ entry.target.parentElement === this.container
628
+ ) {
629
+ debouncedLayout();
630
+ break
631
+ }
632
+ }
633
+ });
634
+ this.resizeObserver.observe(this.container);
635
+
636
+ // Observe all direct children for size changes
637
+ for (const child of this.container.children) {
638
+ if (child.nodeType === Node.ELEMENT_NODE) {
639
+ this.resizeObserver.observe(child);
640
+ }
641
+ }
470
642
 
471
- slide: function(item) {
472
- var $item = $(item),
473
- $frame = $item.find('.frame');
643
+ // Mutation observer for dynamic content
644
+ this.mutationObserver = new MutationObserver(mutations => {
645
+ let shouldRelayout = false;
646
+ for (const mutation of mutations) {
647
+ if (mutation.type === "childList") {
648
+ // Observe new children
649
+ for (const node of mutation.addedNodes) {
650
+ if (node.nodeType === Node.ELEMENT_NODE) {
651
+ this.resizeObserver.observe(node);
652
+ // Also watch for images in new nodes
653
+ this.observeImages(node);
654
+ }
655
+ }
656
+ shouldRelayout = true;
657
+ } else if (
658
+ mutation.type === "attributes" &&
659
+ mutation.attributeName === "style"
660
+ ) {
661
+ shouldRelayout = true;
662
+ }
663
+ }
664
+ if (shouldRelayout) {
665
+ debouncedLayout();
666
+ }
667
+ });
668
+ this.mutationObserver.observe(this.container, {
669
+ childList: true,
670
+ subtree: false,
671
+ attributes: true,
672
+ attributeFilter: ["style", "class"]
673
+ });
674
+
675
+ // Observe all images for load events
676
+ this.observeImages(this.container);
677
+ }
474
678
 
475
- this.$items.hide();
476
- $item.show();
679
+ observeImages(root) {
680
+ const images = root.querySelectorAll("img");
681
+ for (const img of images) {
682
+ if (!img.complete) {
683
+ img.addEventListener("load", () => this.layout(), { once: true });
684
+ img.addEventListener("error", () => this.layout(), { once: true });
685
+ }
686
+ }
687
+ }
477
688
 
478
- Math.round($item.height() - $frame.height())/2;
479
- this.activeIndex = this.$items.index(item);
689
+ layout() {
690
+ const styles = getGridLanesStyles(this.container);
691
+ const containerRect = this.container.getBoundingClientRect();
692
+
693
+ // Determine direction based on which template is defined
694
+ const hasColumns =
695
+ styles.gridTemplateColumns &&
696
+ styles.gridTemplateColumns !== "none" &&
697
+ !styles.gridTemplateColumns.startsWith("auto");
698
+ const hasRows =
699
+ styles.gridTemplateRows &&
700
+ styles.gridTemplateRows !== "none" &&
701
+ !styles.gridTemplateRows.startsWith("auto");
702
+
703
+ this.isVertical = hasColumns || !hasRows;
704
+
705
+ // Calculate lane sizes
706
+ if (this.isVertical) {
707
+ this.lanes = calculateLaneSizes(
708
+ styles.gridTemplateColumns,
709
+ containerRect.width,
710
+ styles.columnGap,
711
+ styles.fontSize,
712
+ styles.rootFontSize
713
+ ) || [containerRect.width];
714
+ } else {
715
+ this.lanes = calculateLaneSizes(
716
+ styles.gridTemplateRows,
717
+ containerRect.height,
718
+ styles.rowGap,
719
+ styles.fontSize,
720
+ styles.rootFontSize
721
+ ) || [containerRect.height];
722
+ }
480
723
 
481
- if (this.options.autoPlay && !this.paused) this.play();
724
+ // Initialize lane positions (heights for vertical, widths for horizontal)
725
+ this.laneHeights = new Array(this.lanes.length).fill(0);
726
+
727
+ // Get all direct children
728
+ const items = Array.from(this.container.children).filter(
729
+ el =>
730
+ el.nodeType === Node.ELEMENT_NODE &&
731
+ window.getComputedStyle(el).display !== "none"
732
+ );
733
+
734
+ // Separate explicitly placed items from auto-placed items
735
+ const explicitItems = [];
736
+ const autoItems = [];
737
+
738
+ for (const item of items) {
739
+ const itemStyles = getItemStyles(item);
740
+ if (this.isVertical && itemStyles.columnStart !== null) {
741
+ explicitItems.push({ element: item, styles: itemStyles });
742
+ } else if (!this.isVertical && itemStyles.rowStart !== null) {
743
+ explicitItems.push({ element: item, styles: itemStyles });
744
+ } else {
745
+ autoItems.push({ element: item, styles: itemStyles });
746
+ }
747
+ }
482
748
 
483
- return this;
484
- },
749
+ // Place explicitly positioned items first
750
+ for (const { element, styles: itemStyles } of explicitItems) {
751
+ this.placeExplicitItem(element, itemStyles, styles);
752
+ }
485
753
 
486
- play: function() {
487
- this.paused = false;
754
+ // Place auto-positioned items
755
+ for (const { element, styles: itemStyles } of autoItems) {
756
+ this.placeAutoItem(element, itemStyles, styles);
757
+ }
488
758
 
489
- if (this.interval) clearInterval(this.interval);
490
- this.interval = setInterval($.proxy(this.next, this), this.options.interval);
491
- },
759
+ // Set container height
760
+ const containerHeight = Math.max(...this.laneHeights);
761
+ this.container.style.minHeight = `${containerHeight}px`;
762
+ }
492
763
 
493
- pause: function() {
494
- this.paused = true;
495
- this.interval = clearInterval(this.interval);
764
+ placeExplicitItem(element, itemStyles, containerStyles) {
765
+ const gap = this.isVertical
766
+ ? containerStyles.columnGap
767
+ : containerStyles.rowGap;
768
+ const crossGap = this.isVertical
769
+ ? containerStyles.rowGap
770
+ : containerStyles.columnGap;
771
+
772
+ let laneIndex;
773
+ let span;
774
+
775
+ if (this.isVertical) {
776
+ // Handle negative indices
777
+ laneIndex = itemStyles.columnStart;
778
+ if (laneIndex < 0) {
779
+ laneIndex = this.lanes.length + laneIndex + 1;
780
+ }
781
+ laneIndex = Math.max(0, Math.min(laneIndex - 1, this.lanes.length - 1));
782
+ span = itemStyles.columnSpan;
783
+ } else {
784
+ laneIndex = itemStyles.rowStart;
785
+ if (laneIndex < 0) {
786
+ laneIndex = this.lanes.length + laneIndex + 1;
787
+ }
788
+ laneIndex = Math.max(0, Math.min(laneIndex - 1, this.lanes.length - 1));
789
+ span = itemStyles.rowSpan;
790
+ }
496
791
 
497
- return this;
498
- },
792
+ // Calculate position
793
+ let position = 0;
794
+ for (let i = 0; i < laneIndex; i++) {
795
+ position += this.lanes[i] + gap;
796
+ }
499
797
 
500
- startAt: function(pos) {
501
- this.to(pos);
502
- },
798
+ // Calculate width (for spanning)
799
+ let size = 0;
800
+ const endLane = Math.min(laneIndex + span, this.lanes.length);
801
+ for (let i = laneIndex; i < endLane; i++) {
802
+ size += this.lanes[i];
803
+ if (i < endLane - 1) size += gap;
804
+ }
503
805
 
504
- next: function() {
505
- return this.to('next');
506
- },
806
+ // Get the tallest lane in the span
807
+ let maxHeight = 0;
808
+ for (let i = laneIndex; i < endLane; i++) {
809
+ maxHeight = Math.max(maxHeight, this.laneHeights[i]);
810
+ }
507
811
 
508
- to: function(pos) {
509
- if (pos === 'next') pos = this.activeIndex + 1;
510
- if (pos === 'prev') pos = this.activeIndex - 1;
812
+ // Position the element
813
+ element.style.position = "absolute";
814
+
815
+ if (this.isVertical) {
816
+ element.style.left = `${position}px`;
817
+ element.style.top = `${maxHeight > 0 ? maxHeight + crossGap : 0}px`;
818
+ element.style.width = `${size}px`;
819
+ } else {
820
+ element.style.top = `${position}px`;
821
+ element.style.left = `${maxHeight > 0 ? maxHeight + crossGap : 0}px`;
822
+ element.style.height = `${size}px`;
823
+ element.style.width = "";
824
+ }
511
825
 
512
- return this.slide(this.$items[this.getValidIndex(pos)]);
513
- },
826
+ // Update lane heights
827
+ const itemRect = element.getBoundingClientRect();
828
+ const itemSize = this.isVertical ? itemRect.height : itemRect.width;
829
+ const newHeight = maxHeight + (maxHeight > 0 ? crossGap : 0) + itemSize;
514
830
 
515
- getValidIndex: function(index) {
516
- if (typeof index === 'undefined' || index > (this.$items.length - 1)) index = 0;
517
- if (index < 0) index = this.$items.length - 1;
831
+ for (let i = laneIndex; i < endLane; i++) {
832
+ this.laneHeights[i] = newHeight;
833
+ }
834
+ }
518
835
 
519
- return index;
520
- },
836
+ placeAutoItem(element, itemStyles, containerStyles) {
837
+ const gap = this.isVertical
838
+ ? containerStyles.columnGap
839
+ : containerStyles.rowGap;
840
+ const crossGap = this.isVertical
841
+ ? containerStyles.rowGap
842
+ : containerStyles.columnGap;
843
+ const tolerance = containerStyles.tolerance;
844
+ const span = Math.min(
845
+ this.isVertical ? itemStyles.columnSpan : itemStyles.rowSpan,
846
+ this.lanes.length
847
+ );
848
+
849
+ // Find the best lane(s) considering tolerance
850
+ let bestLane = 0;
851
+ let bestHeight = Infinity;
852
+
853
+ for (let i = 0; i <= this.lanes.length - span; i++) {
854
+ // Get the max height across the span
855
+ let maxHeight = 0;
856
+ for (let j = i; j < i + span; j++) {
857
+ maxHeight = Math.max(maxHeight, this.laneHeights[j]);
858
+ }
859
+
860
+ // Use tolerance to determine if this is meaningfully better
861
+ if (bestHeight - maxHeight > tolerance) {
862
+ bestHeight = maxHeight;
863
+ bestLane = i;
864
+ }
865
+ // Within tolerance: keep the current (earlier) bestLane — spec prefers earliest equivalent lane
866
+ }
521
867
 
522
- attachEvents: function() {
523
- this.$element.find('.frame img');
524
- var _this = this;
868
+ // Calculate position
869
+ let position = 0;
870
+ for (let i = 0; i < bestLane; i++) {
871
+ position += this.lanes[i] + gap;
872
+ }
525
873
 
526
- $(document).on('click', '[data-behavior="pause-slideshow"]', function(e) {
527
- e.preventDefault();
874
+ // Calculate size (for spanning)
875
+ let size = 0;
876
+ const endLane = Math.min(bestLane + span, this.lanes.length);
877
+ for (let i = bestLane; i < endLane; i++) {
878
+ size += this.lanes[i];
879
+ if (i < endLane - 1) size += gap;
880
+ }
528
881
 
529
- _this.pause();
530
- });
882
+ // Position the element
883
+ element.style.position = "absolute";
884
+
885
+ if (this.isVertical) {
886
+ element.style.left = `${position}px`;
887
+ element.style.top = `${bestHeight > 0 ? bestHeight + crossGap : 0}px`;
888
+ element.style.width = `${size}px`;
889
+ } else {
890
+ element.style.top = `${position}px`;
891
+ element.style.left = `${bestHeight > 0 ? bestHeight + crossGap : 0}px`;
892
+ element.style.height = `${size}px`;
893
+ element.style.width = "";
894
+ }
531
895
 
532
- $(document).on('click', '[data-behavior="start-slideshow"]', function(e) {
533
- e.preventDefault();
896
+ // Update lane heights
897
+ const itemRect = element.getBoundingClientRect();
898
+ const itemSize = this.isVertical ? itemRect.height : itemRect.width;
899
+ const newHeight = bestHeight + (bestHeight > 0 ? crossGap : 0) + itemSize;
534
900
 
535
- _this.play();
536
- });
901
+ for (let i = bestLane; i < endLane; i++) {
902
+ this.laneHeights[i] = newHeight;
903
+ }
904
+ }
537
905
 
538
- $(document).on('click', '[data-slide], [data-bs-slide], [data-slide-to], [data-bs-slide-to]', function(e) {
539
- e.preventDefault();
906
+ destroy() {
907
+ if (this.resizeObserver) {
908
+ this.resizeObserver.disconnect();
909
+ }
910
+ if (this.mutationObserver) {
911
+ this.mutationObserver.disconnect();
912
+ }
540
913
 
541
- const pos = parseInt($(this).attr('data-slide-to') || $(this).attr('data-bs-slide-to'), 10) ||
542
- $(this).attr('data-slide') ||
543
- $(this).attr('data-bs-slide');
914
+ this.container.removeAttribute(POLYFILL_ATTR);
915
+ this.container.style.position = "";
916
+ this.container.style.display = "";
917
+ this.container.style.minHeight = "";
918
+
919
+ // Reset item styles
920
+ for (const item of this.container.children) {
921
+ if (item.nodeType === Node.ELEMENT_NODE) {
922
+ item.style.position = "";
923
+ item.style.left = "";
924
+ item.style.top = "";
925
+ item.style.width = "";
926
+ item.style.height = "";
927
+ }
928
+ }
929
+ }
544
930
 
545
- if (pos === 'next' || pos === 'prev') _this.pause();
546
- _this.to(pos);
547
- });
931
+ refresh() {
932
+ this.layout();
933
+ }
934
+ }
548
935
 
549
- // pause slideshow on modal close
550
- $('#slideshow-modal').on('hidden.bs.modal', function() {
551
- _this.pause();
552
- });
553
- }
554
- };
936
+ // ============================================================================
937
+ // PUBLIC API
938
+ // ============================================================================
555
939
 
940
+ /**
941
+ * Initialize the polyfill on all grid-lanes elements in the document
942
+ */
943
+ function init(options = {}) {
944
+ // Check if native support exists
945
+ if (supportsGridLanes() && !options.force) {
946
+ console.log(
947
+ `${POLYFILL_NAME}: Native support detected, polyfill not needed.`
948
+ );
949
+ return { supported: true, instances: [] }
950
+ }
556
951
 
557
- Slideshow.DEFAULTS = {
558
- autoPlay: false,
559
- interval: 5000 // in milliseconds
560
- };
952
+ const instances = new Map();
561
953
 
954
+ // Process existing containers
955
+ const containers = findElements();
562
956
 
563
- $.fn.slideshow = function(option) {
564
- return this.each(function() {
565
- var $this = $(this);
566
- var data = $this.data('slideshow');
567
- var options = $.extend({}, Slideshow.DEFAULTS, $this.data(), typeof option == 'object' && option);
957
+ for (const container of containers) {
958
+ if (!container.hasAttribute(POLYFILL_ATTR)) {
959
+ instances.set(container, new GridLanesLayout(container, options));
960
+ }
961
+ }
568
962
 
569
- if (!data) $this.data('slideshow', (data = new Slideshow(this, options)));
570
- })
963
+ // Watch for new stylesheets and elements
964
+ // Debounce for ancestor attribute changes to avoid excessive relayouts
965
+ let ancestorLayoutTimeout = null;
966
+ const pendingAncestorInstances = new Map();
967
+ const debouncedRelayoutAll = () => {
968
+ if (ancestorLayoutTimeout) clearTimeout(ancestorLayoutTimeout);
969
+ ancestorLayoutTimeout = setTimeout(() => {
970
+ for (const [container, instance] of pendingAncestorInstances) {
971
+ if (instances.has(container)) {
972
+ removeCachedStyle(container);
973
+ instance.refresh();
974
+ }
975
+ }
976
+ pendingAncestorInstances.clear();
977
+ }, 16);
571
978
  };
572
979
 
573
- })(jQuery);
574
-
575
- (function($){
576
- $.fn.BlacklightMasonry = function() {
577
- var container = this;
578
- if(container.length > 0) {
579
- container.imagesLoaded().progress(function(){
580
- container.masonry($.fn.BlacklightMasonry.options);
581
- });
980
+ const observer = new MutationObserver(mutations => {
981
+ for (const mutation of mutations) {
982
+ if (mutation.type === "attributes") {
983
+ removeCachedStyle(mutation.target);
984
+ // If the changed node is an ancestor of (or is) a container,
985
+ // cascaded/inherited styles on the container may have changed.
986
+ for (const [container] of instances) {
987
+ if (mutation.target.contains(container)) {
988
+ pendingAncestorInstances.set(container, instances.get(container));
989
+ }
990
+ }
991
+ if (pendingAncestorInstances.size > 0) {
992
+ debouncedRelayoutAll();
993
+ }
994
+ }
995
+
996
+ if (mutation.type === "childList") {
997
+ for (const node of mutation.addedNodes) {
998
+ if (node.nodeType !== node.ELEMENT_NODE) continue
999
+
1000
+ processSubtree(node, instances, options);
1001
+ }
1002
+ for (const node of mutation.removedNodes) {
1003
+ if (node.nodeType !== node.ELEMENT_NODE) continue
1004
+ if (instances.has(node)) {
1005
+ instances.get(node).destroy();
1006
+ instances.delete(node);
1007
+ }
1008
+ styleMap.delete(node);
1009
+ }
1010
+ }
582
1011
  }
583
- };
1012
+ });
1013
+
1014
+ observer.observe(document.documentElement, {
1015
+ childList: true,
1016
+ subtree: true,
1017
+ attributes: true,
1018
+ attributeFilter: ["style", "class"]
1019
+ });
1020
+
1021
+ console.log(
1022
+ `${POLYFILL_NAME}: Initialized, ${instances.size} container(s) found.`
1023
+ );
1024
+
1025
+ return {
1026
+ supported: false,
1027
+ instances,
1028
+ observer,
1029
+ refresh() {
1030
+ for (const instance of instances.values()) {
1031
+ instance.refresh();
1032
+ }
1033
+ },
1034
+ destroy() {
1035
+ observer.disconnect();
1036
+ for (const instance of instances.values()) {
1037
+ instance.destroy();
1038
+ }
1039
+ instances.clear();
1040
+ // TODO: Ensure GC is triggered
1041
+ }
1042
+ }
1043
+ }
584
1044
 
585
- $.fn.BlacklightMasonry.options = { gutter: 8 };
586
- })(jQuery);
1045
+ document.addEventListener("DOMContentLoaded", () => {
1046
+ if (!supportsGridLanes()) {
1047
+ init({ force: true });
1048
+ }
1049
+ });
1050
+
1051
+ // When a thumbnail opens the slideshow modal, show the slide for that thumbnail.
1052
+ // Bootstrap's carousel reads the current slide from the DOM, so moving the
1053
+ // active class before the modal is shown is enough.
1054
+ document.addEventListener("show.bs.modal", (event) => {
1055
+ const slideTo = event.relatedTarget?.dataset.bsSlideTo;
1056
+ const items = event.target.querySelectorAll(".carousel-item");
1057
+ if (slideTo === undefined || !items[slideTo]) return
1058
+
1059
+ items.forEach((item) => item.classList.remove("active"));
1060
+ items[slideTo].classList.add("active");
1061
+ });
587
1062
  //# sourceMappingURL=blacklight-gallery.esm.js.map