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