neiki-gallery 2.1.0 → 3.0.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.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 neikiri
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 neikiri
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  <img src="https://img.shields.io/badge/css-%23663399.svg?style=for-the-badge&logo=css&logoColor=white" alt="CSS">
11
11
  <br>
12
12
  <img src="https://img.shields.io/badge/License-MIT-2563EB?style=for-the-badge&logo=open-source-initiative&logoColor=white&labelColor=000F15&logoWidth=20" alt="License">
13
- <img src="https://img.shields.io/badge/Version-2.1.0-2563EB?style=for-the-badge&logo=semantic-release&logoColor=white&labelColor=000F15&logoWidth=20" alt="Version">
13
+ <img src="https://img.shields.io/badge/Version-3.0.0-2563EB?style=for-the-badge&logo=semantic-release&logoColor=white&labelColor=000F15&logoWidth=20" alt="Version">
14
14
  </p>
15
15
 
16
16
  <p align="center">
@@ -55,7 +55,7 @@
55
55
  - **Multiple instances** — independent galleries on one page
56
56
  - **Dark & light themes** — CSS custom properties + accent color system (`--neiki-accent`)
57
57
  - **Accessibility** — ARIA attributes, focus management, `prefers-reduced-motion`
58
- - **Event system** — `open`, `close`, `change`, `filter`, `select`, `slideshowStart`, `slideshowStop`
58
+ - **Event system** — `open`, `close`, `change`, `filter`, `select`, `slideshowStart`, `slideshowStop`, `favorite`, `unfavorite`, `infoOpen`, `infoClose`, `print`, `editorOpen`, `editorExport`, `annotateOpen`, `annotateExport`, `append`, `remove`
59
59
  - **Blurhash placeholders** — `data-blurhash` attribute decoded into blurred preview (replaces shimmer)
60
60
  - **Story mode** — Instagram-like vertical fullscreen viewer with progress bars and tap navigation
61
61
  - **Picture-in-Picture** — minimize lightbox to a resizable corner window
@@ -68,7 +68,21 @@
68
68
  - **Drag & drop reorder** — HTML5 drag to reorder grid items
69
69
  - **Aspect-ratio skeleton** — `data-width`/`data-height` for placeholder sizing
70
70
  - **Web Share API** — native mobile sharing with clipboard fallback
71
- - **Cross-browser** — Chrome 60+, Firefox 55+, Safari 12+, Edge 79+
71
+ - **Video & embeds** — auto-detect MP4/WebM/Ogg/MOV/M4V, YouTube and Vimeo URLs in lightbox
72
+ - **Plugin system** — `NeikiGallery.registerPlugin()` with `init`/`open`/`change`/`close`/`destroy` lifecycle hooks
73
+ - **Album groups** — `data-group="album"` links galleries; navigation traverses across all
74
+ - **Favorites** — heart button + localStorage persistence + `B` key shortcut
75
+ - **Info panel** — slide-out sidebar with metadata, EXIF, source URL (`I` key)
76
+ - **Image editor** — rotate / flip / export to PNG via `<canvas>`
77
+ - **Annotation layer** — freehand drawing with color picker, brush size, undo, clear, export
78
+ - **Print** — print current image with caption (`P` key)
79
+ - **Right-click context menu** — open original, copy link, download, share, print, favorite
80
+ - **Infinite scroll** — `loadMore` callback + `append()` / `remove()` API
81
+ - **Keyboard shortcuts overlay** — press `?` to view all shortcuts
82
+ - **System theme detection** — `theme: 'system'` follows OS `prefers-color-scheme`
83
+ - **Kenburns slideshow** — slow zoom-and-pan animation per slide
84
+ - **Configurable counter** — `counterFormat` with `{current}`/`{total}`/`{percent}` tokens
85
+ - **Cross-browser** — Chrome 80+, Firefox 75+, Safari 14+, Edge 80+
72
86
 
73
87
  ## 🚀 Quick Start
74
88
 
@@ -103,7 +117,7 @@ That's it — galleries with `data-neiki-gallery` auto-initialize on page load.
103
117
 
104
118
  > **Pin a specific version** to avoid unexpected changes:
105
119
  > ```
106
- > https://cdn.neiki.eu/neiki-gallery/2.1.0/neiki-gallery.min.js
120
+ > https://cdn.neiki.eu/neiki-gallery/3.0.0/neiki-gallery.min.js
107
121
  > ```
108
122
 
109
123
  ### Self-Hosting (Local)
@@ -147,26 +161,45 @@ const gallery = new NeikiGallery('#my-gallery', {
147
161
  contextualZoom: false, // zoom to click point
148
162
  fullscreen: true,
149
163
  transition: 'fade', // 'fade' | 'slide'
150
- theme: 'dark', // 'dark' | 'light'
164
+ theme: 'system', // 'dark' | 'light' | 'system' (auto-detect) ← v3 default
151
165
  hashNavigation: true,
152
- stagger: true, // entrance animation
153
- slideshow: false, // auto-start slideshow on open
154
- slideshowInterval: 4000, // ms between slides
155
- share: true, // share/download popup
156
- filter: false, // tag filtering
157
- batchSelect: false, // Shift+click multi-select
166
+ counter: true,
167
+ counterFormat: '{current} / {total}', // v3 tokens: {current}, {total}, {percent}
168
+ stagger: true,
169
+ slideshow: { // v3 — nested config (boolean still works)
170
+ interval: 4000,
171
+ pauseOnHover: true,
172
+ kenburns: false,
173
+ direction: 'forward' // or 'reverse'
174
+ },
175
+ share: true,
176
+ filter: false,
177
+ batchSelect: false,
158
178
  // v2.1.0
159
- focusPoint: true, // respect data-focus for object-position
160
- blurhash: true, // decode data-blurhash placeholders
161
- exif: false, // show EXIF overlay in lightbox
162
- storyMode: false, // vertical fullscreen story viewer
163
- pip: false, // picture-in-picture lightbox
164
- virtualScroll: false, // virtualize grid for large galleries
165
- dragReorder: false, // drag-and-drop reorder
166
- backdropTint: false, // tint overlay with dominant color
167
- morphTransition: false, // FLIP morph from grid to lightbox
168
- colorPalette: false, // extract dominant colors
169
- aspectSkeleton: true, // data-width/data-height skeleton
179
+ focusPoint: true,
180
+ blurhash: true,
181
+ exif: false,
182
+ storyMode: false,
183
+ pip: false,
184
+ virtualScroll: false,
185
+ dragReorder: false,
186
+ backdropTint: false,
187
+ morphTransition: false,
188
+ colorPalette: false,
189
+ aspectSkeleton: true,
190
+ // v3.0.0
191
+ video: true, // detect MP4 / YouTube / Vimeo
192
+ plugins: ['watermark', { name: 'analytics', trackingId: 'X' }],
193
+ group: '', // album group name (or use data-group)
194
+ favorites: false, // ❤️ button + localStorage
195
+ favoritesKey: '', // custom localStorage key suffix
196
+ infoPanel: false, // sidebar with metadata (I key)
197
+ contextMenu: false, // right-click menu on items
198
+ shortcutsHelp: true, // overlay on '?' key
199
+ infiniteScroll: false, // auto-load more
200
+ loadMore: null, // function(currentLength) → array | Promise<array>
201
+ editor: false, // crop/rotate/flip toolbar
202
+ annotate: false // freehand drawing layer
170
203
  });
171
204
  ```
172
205
 
@@ -177,15 +210,50 @@ const gallery = new NeikiGallery('#my-gallery', {
177
210
  ```js
178
211
  gallery.open(index); // Open lightbox at image index
179
212
  gallery.close(); // Close lightbox
180
- gallery.next(); // Go to next image
181
- gallery.prev(); // Go to previous image
213
+ gallery.next(); // Go to next image (group-aware)
214
+ gallery.prev(); // Go to previous image (group-aware)
182
215
  gallery.startSlideshow(); // Start autoplay
183
216
  gallery.stopSlideshow(); // Stop autoplay
217
+ gallery.pauseSlideshow(); // v3 — pause without emitting stop
184
218
  gallery.toggleSlideshow(); // Toggle autoplay
185
219
  gallery.filter('landscape'); // Filter by tag (null = show all)
186
220
  gallery.getSelected(); // Get batch-selected items
187
221
  gallery.clearSelection(); // Clear batch selection
188
222
  gallery.getOrder(); // Get current item order (after drag reorder)
223
+
224
+ // v3.0.0 — Favorites
225
+ gallery.toggleFavorite(); // Toggle favorite for current image
226
+ gallery.isFavorite(index); // Check if image is favorited
227
+ gallery.getFavorites(); // Get array of favorited image src URLs
228
+ gallery.clearFavorites(); // Remove all favorites
229
+
230
+ // v3.0.0 — Info panel / overlays
231
+ gallery.toggleInfoPanel(force); // Toggle info sidebar (force = bool)
232
+ gallery.toggleShortcutsHelp(force); // Toggle keyboard shortcuts overlay
233
+
234
+ // v3.0.0 — Editor & annotation
235
+ gallery.openEditor(); // Open crop/rotate/flip editor
236
+ gallery.closeEditor();
237
+ gallery.getEditedBlob(); // Last exported edited image as Blob
238
+
239
+ gallery.openAnnotate(); // Open drawing layer
240
+ gallery.closeAnnotate();
241
+ gallery.getAnnotatedBlob();
242
+
243
+ // v3.0.0 — Print & dynamic content
244
+ gallery.print(index); // Print current or specified image
245
+ gallery.append([{src,...}]); // Append new items at runtime
246
+ gallery.remove(index); // Remove item by index
247
+
248
+ // v3.0.0 — Plugin access
249
+ gallery.plugin('watermark'); // Get plugin instance by name
250
+
251
+ // v3.0.0 — Event helpers
252
+ gallery.on('change', fn);
253
+ gallery.off('change', fn); // Remove specific listener
254
+ gallery.off('change'); // Remove all listeners for event
255
+ gallery.once('open', fn); // Auto-removed after first fire
256
+
189
257
  gallery.destroy(); // Remove gallery, clean up all listeners & DOM
190
258
  ```
191
259
 
@@ -221,8 +289,57 @@ NeikiGallery.parseExif('photo.jpg', (tags) => {
221
289
 
222
290
  // Blurhash decoding (v2.1.0)
223
291
  const pixels = NeikiGallery.decodeBlurhash('LEHV6nWB2y...', 32, 32);
292
+
293
+ // Media type detection (v3.0.0)
294
+ NeikiGallery.detectMediaType('https://youtube.com/watch?v=abc'); // 'youtube'
295
+ NeikiGallery.detectMediaType('clip.mp4'); // 'video'
296
+ NeikiGallery.detectMediaType('photo.jpg'); // 'image'
297
+
298
+ // Plugin registration (v3.0.0)
299
+ NeikiGallery.registerPlugin('watermark', (gallery, options) => ({
300
+ init() { /* set up DOM, listeners */ },
301
+ open() { /* on lightbox open */ },
302
+ change(d){ /* on slide change, d = {index, item, prevIndex} */ },
303
+ close() { /* on lightbox close */ },
304
+ destroy(){ /* cleanup */ }
305
+ }));
306
+
307
+ NeikiGallery.unregisterPlugin('watermark');
308
+ NeikiGallery.getRegisteredPlugins(); // ['watermark', ...]
309
+
310
+ // Version
311
+ console.log(NeikiGallery.version); // '3.0.0'
224
312
  ```
225
313
 
314
+ ### Plugin System
315
+
316
+ A plugin is a factory function that receives the gallery instance and an options
317
+ object, and returns an object with optional lifecycle methods:
318
+
319
+ ```js
320
+ NeikiGallery.registerPlugin('analytics', (gallery, opts) => ({
321
+ init() {
322
+ console.log('[analytics] gallery initialized');
323
+ },
324
+ open(data) {
325
+ track('lightbox_open', { index: data.index });
326
+ },
327
+ change(data) {
328
+ track('slide_view', { src: data.item.src, index: data.index });
329
+ },
330
+ close() {
331
+ track('lightbox_close');
332
+ },
333
+ destroy() {
334
+ console.log('[analytics] cleanup');
335
+ }
336
+ }));
337
+
338
+ new NeikiGallery('#g', { plugins: [{ name: 'analytics', sampleRate: 0.5 }] });
339
+ ```
340
+
341
+ You can access plugin instances on a gallery via `gallery.plugin('analytics')`.
342
+
226
343
  ### Events
227
344
 
228
345
  ```js
@@ -256,6 +373,16 @@ gallery.on('reorder', (order) => {
256
373
 
257
374
  gallery.on('pipEnter', () => console.log('PiP on'));
258
375
  gallery.on('storyEnter', () => console.log('Story opened'));
376
+
377
+ // v3.0.0 events
378
+ gallery.on('favorite', ({ index, src }) => console.log('★ favorited', src));
379
+ gallery.on('unfavorite', ({ index, src }) => console.log('☆ removed'));
380
+ gallery.on('infoOpen', () => console.log('info panel opened'));
381
+ gallery.on('print', ({ index, src }) => console.log('printed', src));
382
+ gallery.on('editorExport', ({ blob, url }) => console.log('edited blob', url));
383
+ gallery.on('annotateExport', ({ blob, url }) => console.log('annotated', url));
384
+ gallery.on('append', (items) => console.log('appended', items.length));
385
+ gallery.on('remove', ({ index }) => console.log('removed item', index));
259
386
  ```
260
387
 
261
388
  ### Options
@@ -269,15 +396,15 @@ gallery.on('storyEnter', () => console.log('Story opened'));
269
396
  | `contextualZoom` | `boolean` | `false` | Zoom to cursor point instead of center |
270
397
  | `fullscreen` | `boolean` | `true` | Show fullscreen button |
271
398
  | `transition` | `string` | `'fade'` | `'fade'` or `'slide'` |
272
- | `theme` | `string` | `'dark'` | `'dark'` or `'light'` |
399
+ | `theme` | `string` | `'system'` | `'dark'` · `'light'` · `'system'` (auto-detect) |
273
400
  | `hashNavigation` | `boolean` | `true` | URL hash deep linking |
274
401
  | `counter` | `boolean` | `true` | Show image counter |
402
+ | `counterFormat` | `string` | `'{current} / {total}'` | Counter format with `{current}`, `{total}`, `{percent}` tokens |
275
403
  | `captions` | `boolean` | `true` | Show image captions |
276
404
  | `preload` | `number` | `1` | Adjacent images to preload |
277
405
  | `lazyLoad` | `boolean` | `true` | Lazy load grid thumbnails |
278
406
  | `stagger` | `boolean` | `true` | Staggered entrance animation |
279
- | `slideshow` | `boolean` | `false` | Auto-start slideshow on open |
280
- | `slideshowInterval` | `number` | `4000` | Slideshow interval in ms |
407
+ | `slideshow` | `boolean \| object` | `false` | `true` or `{ interval, pauseOnHover, kenburns, direction }` |
281
408
  | `share` | `boolean` | `true` | Show share button in lightbox |
282
409
  | `filter` | `boolean` | `false` | Enable tag filtering bar |
283
410
  | `batchSelect` | `boolean` | `false` | Enable Shift+click multi-select |
@@ -292,6 +419,18 @@ gallery.on('storyEnter', () => console.log('Story opened'));
292
419
  | `morphTransition` | `boolean` | `false` | FLIP morph from grid to lightbox |
293
420
  | `colorPalette` | `boolean` | `false` | Show extracted color palette |
294
421
  | `aspectSkeleton` | `boolean` | `true` | Use `data-width`/`data-height` for skeleton |
422
+ | `video` | `boolean` | `true` | Auto-detect MP4 / YouTube / Vimeo URLs |
423
+ | `plugins` | `array` | `null` | Plugins to instantiate; e.g. `['name', { name, ...opts }]` |
424
+ | `group` | `string` | `''` | Album group name (also via `data-group`) |
425
+ | `favorites` | `boolean` | `false` | Heart button + localStorage |
426
+ | `favoritesKey` | `string` | `''` | Custom suffix for localStorage key |
427
+ | `infoPanel` | `boolean` | `false` | Slide-out metadata sidebar |
428
+ | `contextMenu` | `boolean` | `false` | Right-click menu on items |
429
+ | `shortcutsHelp` | `boolean` | `true` | `?` key shortcuts overlay |
430
+ | `infiniteScroll` | `boolean` | `false` | Auto-load more on scroll (with `loadMore`) |
431
+ | `loadMore` | `function` | `null` | `(currentLength) => array \| Promise<array>` |
432
+ | `editor` | `boolean` | `false` | Crop/rotate/flip toolbar |
433
+ | `annotate` | `boolean` | `false` | Freehand drawing layer |
295
434
 
296
435
  ### Data Attributes
297
436
 
@@ -306,6 +445,9 @@ All options can also be set via `data-` attributes on the container:
306
445
  data-stagger="true"
307
446
  data-slideshow="false"
308
447
  data-slideshow-interval="5000"
448
+ data-slideshow-pause-on-hover="true"
449
+ data-slideshow-kenburns="true"
450
+ data-counter-format="{current} of {total}"
309
451
  data-share="true"
310
452
  data-filter="true"
311
453
  data-batch-select="false"
@@ -318,8 +460,16 @@ All options can also be set via `data-` attributes on the container:
318
460
  data-color-palette="false"
319
461
  data-drag-reorder="false"
320
462
  data-virtual-scroll="false"
321
- data-aspect-skeleton="true">
322
- <a href="full.jpg" data-tags="landscape,nature" data-size="large" data-width="1200" data-height="800">
463
+ data-aspect-skeleton="true"
464
+ data-group="vacation"
465
+ data-favorites="true"
466
+ data-info-panel="true"
467
+ data-context-menu="true"
468
+ data-shortcuts-help="true"
469
+ data-editor="true"
470
+ data-annotate="true"
471
+ data-video="true">
472
+ <a href="full.jpg" data-tags="landscape,nature" data-size="large" data-width="1200" data-height="800" data-poster="poster.jpg" data-type="image">
323
473
  <img src="thumb.jpg" alt="Photo" data-focus="0.5 0.3" data-blurhash="LEHV6nWB2y...">
324
474
  </a>
325
475
  </div>
@@ -357,10 +507,59 @@ Switch themes at runtime by setting `data-theme="light"` or `data-theme="dark"`
357
507
 
358
508
  | Browser | Version |
359
509
  |---------|---------|
360
- | Chrome | 60+ |
361
- | Firefox | 55+ |
362
- | Safari | 12+ |
363
- | Edge | 79+ |
510
+ | Chrome | 80+ |
511
+ | Firefox | 75+ |
512
+ | Safari | 14+ |
513
+ | Edge | 80+ |
514
+
515
+ > **v3.0.0** raised the minimum to take advantage of `aspect-ratio`, `backdrop-filter`,
516
+ > stable `IntersectionObserver`, and modern Canvas/Blob APIs. If you need older
517
+ > browser support, pin to v2.1.0.
518
+
519
+ ## 🔁 Migration from v2.x to v3.0.0
520
+
521
+ **v3.0.0 contains breaking changes.** Most galleries will continue to work, but
522
+ review the following:
523
+
524
+ ### Slideshow config is nested
525
+
526
+ ```js
527
+ // v2.x
528
+ new NeikiGallery('#g', { slideshow: true, slideshowInterval: 5000 });
529
+
530
+ // v3.0.0 — preferred (fine-grained control)
531
+ new NeikiGallery('#g', {
532
+ slideshow: { interval: 5000, pauseOnHover: true, kenburns: true }
533
+ });
534
+
535
+ // v3.0.0 — still works (uses defaults)
536
+ new NeikiGallery('#g', { slideshow: true });
537
+ ```
538
+
539
+ ### Default theme is `'system'`
540
+
541
+ If you relied on the old default `'dark'`, set it explicitly:
542
+
543
+ ```js
544
+ new NeikiGallery('#g', { theme: 'dark' });
545
+ ```
546
+
547
+ ### Hash format changed
548
+
549
+ Old: `#neiki-1=3` → New: `#gallery-id-or-group-name/3`
550
+
551
+ If you have external links to the old format, update them or set
552
+ `hashNavigation: false` and implement custom routing.
553
+
554
+ ### Counter format
555
+
556
+ Default output (`"3 / 12"`) is unchanged, but it's now formatted via
557
+ `counterFormat: '{current} / {total}'`. Override with custom tokens.
558
+
559
+ ### Browser support
560
+
561
+ Minimum Chrome 80+ / Firefox 75+ / Safari 14+ / Edge 80+. If you need older
562
+ browser support, stay on v2.1.0.
364
563
 
365
564
  ## 📄 License
366
565