@gallerist/plugin-fullscreen 0.0.0-stage → 0.3.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shafayet Ahmmed
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
@@ -1,3 +1,51 @@
1
- # Temporary Holding Version
1
+ # @gallerist/plugin-fullscreen
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Gallerist plugin — native Fullscreen API toggle (toolbar button + 'f' key).
4
+
5
+ Part of [Gallerist](https://github.com/shafayet9780/gallerist#readme) — a headless, framework-agnostic media viewer for grouped / nested galleries with image-space overlays and virtualized rendering.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm i @gallerist/plugin-fullscreen
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { createViewer } from '@gallerist/core'
17
+ import { fullscreen } from '@gallerist/plugin-fullscreen'
18
+ import { mount } from '@gallerist/renderer-dom'
19
+
20
+ const viewer = createViewer({ groups, plugins: [fullscreen()] })
21
+ mount(viewer)
22
+
23
+ viewer.on('fullscreenChange', ({ fullscreen }) => console.log('fullscreen:', fullscreen))
24
+ ```
25
+
26
+ A toggle joins the toolbar (pressed while fullscreen), and `f` toggles. The plugin fullscreens this
27
+ viewer's own root, so two viewers on a page don't fullscreen each other, and a fullscreen `<video>`
28
+ elsewhere on the page doesn't count as ours. Older Safari's prefixed API is used where the standard one
29
+ is missing.
30
+
31
+ | Option | Default | |
32
+ |---|---|---|
33
+ | `target` | the viewer's root | a CSS selector for the element to fullscreen instead |
34
+ | `strings` | English | the button's label (`toggle`), for translation; defaults in `DEFAULT_FULLSCREEN_STRINGS` |
35
+
36
+ While the viewer is fullscreen the default renderer collapses the thumbnail rail, and restores it on
37
+ exit (a toggle of the rail while fullscreen wins). The viewer emits `fullscreenChange` with
38
+ `{ viewer, fullscreen }` on every change.
39
+
40
+ Browsers only enter fullscreen from a user gesture (a click or a key press); a refused request is
41
+ ignored. iPhone Safari has no element fullscreen (only `<video>`), so there the button does nothing;
42
+ hide it with CSS (`.gallerist-fullscreen-btn`) if you'd rather not show it.
43
+
44
+ See [Plugin authoring](https://github.com/shafayet9780/gallerist/blob/main/docs/PLUGIN-AUTHORING.md):
45
+ this plugin is the small example of a toolbar button.
46
+
47
+ Full docs: the [monorepo README](https://github.com/shafayet9780/gallerist#readme) and [`docs/`](https://github.com/shafayet9780/gallerist/tree/main/docs).
48
+
49
+ ## License
50
+
51
+ MIT © Shafayet Ahmmed
package/dist/index.cjs ADDED
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ var internal = require('@gallerist/core/internal');
4
+
5
+ // src/index.ts
6
+ var DEFAULT_FULLSCREEN_STRINGS = { toggle: "Toggle fullscreen" };
7
+ function fullscreen(opts = {}) {
8
+ return (viewer) => {
9
+ let boundRoot = null;
10
+ const resolveRoot = (from) => {
11
+ if (opts.target) return document.querySelector(opts.target);
12
+ return boundRoot ?? from?.closest(".gallerist") ?? document.querySelector(".gallerist");
13
+ };
14
+ const fsElement = () => document.fullscreenElement ?? document.webkitFullscreenElement ?? null;
15
+ const isOurFullscreen = (el) => {
16
+ const fe = fsElement();
17
+ return !!fe && !!el && (fe === el || el.contains(fe));
18
+ };
19
+ const requestFs = (el) => (el.requestFullscreen ?? el.webkitRequestFullscreen)?.call(el);
20
+ const exitFs = () => (document.exitFullscreen ?? document.webkitExitFullscreen)?.call(document);
21
+ const toggle = async (from) => {
22
+ const el = resolveRoot(from);
23
+ if (!el) return;
24
+ boundRoot = el;
25
+ try {
26
+ if (isOurFullscreen(el)) await exitFs();
27
+ else await requestFs(el);
28
+ } catch {
29
+ }
30
+ };
31
+ let btnEl = null;
32
+ const onChange = () => {
33
+ const fullscreen2 = isOurFullscreen(resolveRoot());
34
+ btnEl?.setAttribute("aria-pressed", String(fullscreen2));
35
+ internal.getInternals(viewer).emit("fullscreenChange", { viewer, fullscreen: fullscreen2 });
36
+ };
37
+ const button = () => {
38
+ const btn = document.createElement("button");
39
+ btn.type = "button";
40
+ btn.className = "gallerist-fullscreen-btn";
41
+ btn.setAttribute("aria-label", opts.strings?.toggle ?? DEFAULT_FULLSCREEN_STRINGS.toggle);
42
+ btn.setAttribute("aria-pressed", "false");
43
+ btn.innerHTML = internal.ICONS.fullscreen;
44
+ btnEl = btn;
45
+ btn.addEventListener("click", () => void toggle(btn));
46
+ queueMicrotask(() => {
47
+ boundRoot = btn.closest(".gallerist") ?? boundRoot;
48
+ });
49
+ return btn;
50
+ };
51
+ return {
52
+ name: "fullscreen",
53
+ init() {
54
+ if (typeof document !== "undefined") {
55
+ document.addEventListener("fullscreenchange", onChange);
56
+ document.addEventListener("webkitfullscreenchange", onChange);
57
+ }
58
+ },
59
+ destroy() {
60
+ btnEl = null;
61
+ if (typeof document !== "undefined") {
62
+ document.removeEventListener("fullscreenchange", onChange);
63
+ document.removeEventListener("webkitfullscreenchange", onChange);
64
+ }
65
+ },
66
+ keymap: {
67
+ f: () => void toggle()
68
+ },
69
+ regions: {
70
+ toolbar: button
71
+ }
72
+ };
73
+ };
74
+ }
75
+
76
+ exports.DEFAULT_FULLSCREEN_STRINGS = DEFAULT_FULLSCREEN_STRINGS;
77
+ exports.fullscreen = fullscreen;
78
+ //# sourceMappingURL=index.cjs.map
79
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["fullscreen","getInternals","ICONS"],"mappings":"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","file":"index.cjs","sourcesContent":["// @gallerist/plugin-fullscreen — native Fullscreen API toggle (spec §14).\n// Adds a toolbar button + the `f` key, fullscreens the viewer root, and emits `fullscreenChange`.\n// Plugins may touch the DOM (the headless invariant binds @gallerist/core only).\n\nimport type { Plugin } from '@gallerist/core'\nimport { ICONS, getInternals } from '@gallerist/core/internal'\n\nexport interface FullscreenStrings {\n /** the toolbar button's label */\n toggle: string\n}\n\nexport const DEFAULT_FULLSCREEN_STRINGS: FullscreenStrings = { toggle: 'Toggle fullscreen' }\n\nexport interface FullscreenOptions {\n /** CSS selector for the element to fullscreen; defaults to the bound viewer's root. */\n target?: string\n /** UI text overrides, for translation; merged over English. */\n strings?: Partial<FullscreenStrings>\n}\n\n// WebKit (older Safari / iPadOS) ships the prefixed Fullscreen API. iPhone has no element\n// fullscreen at all (only <video>), which the unprefixed-or-prefixed calls below no-op on.\ntype WebkitDoc = Document & {\n webkitFullscreenElement?: Element | null\n webkitExitFullscreen?: () => Promise<void> | void\n}\ntype WebkitEl = HTMLElement & { webkitRequestFullscreen?: () => Promise<void> | void }\n\nexport function fullscreen(opts: FullscreenOptions = {}): Plugin {\n return (viewer) => {\n // The plugin is bound to one viewer, but the Fullscreen API and its change event are\n // document-global. Resolve and remember *this* viewer's root (captured from the mounted\n // toolbar button) so multi-viewer pages don't fullscreen / exit the wrong element (I-PL4).\n let boundRoot: HTMLElement | null = null\n const resolveRoot = (from?: HTMLElement | null): HTMLElement | null => {\n if (opts.target) return document.querySelector<HTMLElement>(opts.target)\n return (\n boundRoot ??\n from?.closest<HTMLElement>('.gallerist') ??\n document.querySelector('.gallerist')\n )\n }\n\n const fsElement = (): Element | null =>\n document.fullscreenElement ?? (document as WebkitDoc).webkitFullscreenElement ?? null\n // Base enter/exit on whether *our* root is the fullscreen element, not just \"is anything\n // fullscreen\" — otherwise an unrelated fullscreen <video> would make our button exit it (S-PL5).\n const isOurFullscreen = (el: HTMLElement | null): boolean => {\n const fe = fsElement()\n return !!fe && !!el && (fe === el || el.contains(fe))\n }\n const requestFs = (el: HTMLElement) =>\n (el.requestFullscreen ?? (el as WebkitEl).webkitRequestFullscreen)?.call(el)\n const exitFs = () =>\n (document.exitFullscreen ?? (document as WebkitDoc).webkitExitFullscreen)?.call(document)\n\n const toggle = async (from?: HTMLElement | null) => {\n const el = resolveRoot(from)\n if (!el) return\n boundRoot = el\n try {\n if (isOurFullscreen(el)) await exitFs()\n else await requestFs(el)\n } catch {\n // user-gesture / permission failures are non-fatal\n }\n }\n\n let btnEl: HTMLButtonElement | null = null\n // Look the internals up when emitting, not at construction, so building the plugin against a\n // wrapped viewer or a test double doesn't throw.\n const onChange = () => {\n const fullscreen = isOurFullscreen(resolveRoot())\n btnEl?.setAttribute('aria-pressed', String(fullscreen)) // a toggle: pressed while fullscreen\n getInternals(viewer).emit('fullscreenChange', { viewer, fullscreen })\n }\n\n const button = () => {\n const btn = document.createElement('button')\n btn.type = 'button'\n btn.className = 'gallerist-fullscreen-btn'\n btn.setAttribute('aria-label', opts.strings?.toggle ?? DEFAULT_FULLSCREEN_STRINGS.toggle)\n btn.setAttribute('aria-pressed', 'false')\n btn.innerHTML = ICONS.fullscreen\n btnEl = btn\n btn.addEventListener('click', () => void toggle(btn))\n // Capture our root once the button is mounted (so onChange/`f` target the right viewer).\n queueMicrotask(() => {\n boundRoot = btn.closest<HTMLElement>('.gallerist') ?? boundRoot\n })\n return btn\n }\n\n return {\n name: 'fullscreen',\n init() {\n if (typeof document !== 'undefined') {\n document.addEventListener('fullscreenchange', onChange)\n document.addEventListener('webkitfullscreenchange', onChange)\n }\n },\n destroy() {\n btnEl = null\n if (typeof document !== 'undefined') {\n document.removeEventListener('fullscreenchange', onChange)\n document.removeEventListener('webkitfullscreenchange', onChange)\n }\n },\n keymap: {\n f: () => void toggle(),\n },\n regions: {\n toolbar: button,\n },\n }\n }\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import { Plugin } from '@gallerist/core';
2
+
3
+ interface FullscreenStrings {
4
+ /** the toolbar button's label */
5
+ toggle: string;
6
+ }
7
+ declare const DEFAULT_FULLSCREEN_STRINGS: FullscreenStrings;
8
+ interface FullscreenOptions {
9
+ /** CSS selector for the element to fullscreen; defaults to the bound viewer's root. */
10
+ target?: string;
11
+ /** UI text overrides, for translation; merged over English. */
12
+ strings?: Partial<FullscreenStrings>;
13
+ }
14
+ declare function fullscreen(opts?: FullscreenOptions): Plugin;
15
+
16
+ export { DEFAULT_FULLSCREEN_STRINGS, type FullscreenOptions, type FullscreenStrings, fullscreen };
@@ -0,0 +1,16 @@
1
+ import { Plugin } from '@gallerist/core';
2
+
3
+ interface FullscreenStrings {
4
+ /** the toolbar button's label */
5
+ toggle: string;
6
+ }
7
+ declare const DEFAULT_FULLSCREEN_STRINGS: FullscreenStrings;
8
+ interface FullscreenOptions {
9
+ /** CSS selector for the element to fullscreen; defaults to the bound viewer's root. */
10
+ target?: string;
11
+ /** UI text overrides, for translation; merged over English. */
12
+ strings?: Partial<FullscreenStrings>;
13
+ }
14
+ declare function fullscreen(opts?: FullscreenOptions): Plugin;
15
+
16
+ export { DEFAULT_FULLSCREEN_STRINGS, type FullscreenOptions, type FullscreenStrings, fullscreen };
package/dist/index.js ADDED
@@ -0,0 +1,76 @@
1
+ import { getInternals, ICONS } from '@gallerist/core/internal';
2
+
3
+ // src/index.ts
4
+ var DEFAULT_FULLSCREEN_STRINGS = { toggle: "Toggle fullscreen" };
5
+ function fullscreen(opts = {}) {
6
+ return (viewer) => {
7
+ let boundRoot = null;
8
+ const resolveRoot = (from) => {
9
+ if (opts.target) return document.querySelector(opts.target);
10
+ return boundRoot ?? from?.closest(".gallerist") ?? document.querySelector(".gallerist");
11
+ };
12
+ const fsElement = () => document.fullscreenElement ?? document.webkitFullscreenElement ?? null;
13
+ const isOurFullscreen = (el) => {
14
+ const fe = fsElement();
15
+ return !!fe && !!el && (fe === el || el.contains(fe));
16
+ };
17
+ const requestFs = (el) => (el.requestFullscreen ?? el.webkitRequestFullscreen)?.call(el);
18
+ const exitFs = () => (document.exitFullscreen ?? document.webkitExitFullscreen)?.call(document);
19
+ const toggle = async (from) => {
20
+ const el = resolveRoot(from);
21
+ if (!el) return;
22
+ boundRoot = el;
23
+ try {
24
+ if (isOurFullscreen(el)) await exitFs();
25
+ else await requestFs(el);
26
+ } catch {
27
+ }
28
+ };
29
+ let btnEl = null;
30
+ const onChange = () => {
31
+ const fullscreen2 = isOurFullscreen(resolveRoot());
32
+ btnEl?.setAttribute("aria-pressed", String(fullscreen2));
33
+ getInternals(viewer).emit("fullscreenChange", { viewer, fullscreen: fullscreen2 });
34
+ };
35
+ const button = () => {
36
+ const btn = document.createElement("button");
37
+ btn.type = "button";
38
+ btn.className = "gallerist-fullscreen-btn";
39
+ btn.setAttribute("aria-label", opts.strings?.toggle ?? DEFAULT_FULLSCREEN_STRINGS.toggle);
40
+ btn.setAttribute("aria-pressed", "false");
41
+ btn.innerHTML = ICONS.fullscreen;
42
+ btnEl = btn;
43
+ btn.addEventListener("click", () => void toggle(btn));
44
+ queueMicrotask(() => {
45
+ boundRoot = btn.closest(".gallerist") ?? boundRoot;
46
+ });
47
+ return btn;
48
+ };
49
+ return {
50
+ name: "fullscreen",
51
+ init() {
52
+ if (typeof document !== "undefined") {
53
+ document.addEventListener("fullscreenchange", onChange);
54
+ document.addEventListener("webkitfullscreenchange", onChange);
55
+ }
56
+ },
57
+ destroy() {
58
+ btnEl = null;
59
+ if (typeof document !== "undefined") {
60
+ document.removeEventListener("fullscreenchange", onChange);
61
+ document.removeEventListener("webkitfullscreenchange", onChange);
62
+ }
63
+ },
64
+ keymap: {
65
+ f: () => void toggle()
66
+ },
67
+ regions: {
68
+ toolbar: button
69
+ }
70
+ };
71
+ };
72
+ }
73
+
74
+ export { DEFAULT_FULLSCREEN_STRINGS, fullscreen };
75
+ //# sourceMappingURL=index.js.map
76
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["fullscreen"],"mappings":"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","file":"index.js","sourcesContent":["// @gallerist/plugin-fullscreen — native Fullscreen API toggle (spec §14).\n// Adds a toolbar button + the `f` key, fullscreens the viewer root, and emits `fullscreenChange`.\n// Plugins may touch the DOM (the headless invariant binds @gallerist/core only).\n\nimport type { Plugin } from '@gallerist/core'\nimport { ICONS, getInternals } from '@gallerist/core/internal'\n\nexport interface FullscreenStrings {\n /** the toolbar button's label */\n toggle: string\n}\n\nexport const DEFAULT_FULLSCREEN_STRINGS: FullscreenStrings = { toggle: 'Toggle fullscreen' }\n\nexport interface FullscreenOptions {\n /** CSS selector for the element to fullscreen; defaults to the bound viewer's root. */\n target?: string\n /** UI text overrides, for translation; merged over English. */\n strings?: Partial<FullscreenStrings>\n}\n\n// WebKit (older Safari / iPadOS) ships the prefixed Fullscreen API. iPhone has no element\n// fullscreen at all (only <video>), which the unprefixed-or-prefixed calls below no-op on.\ntype WebkitDoc = Document & {\n webkitFullscreenElement?: Element | null\n webkitExitFullscreen?: () => Promise<void> | void\n}\ntype WebkitEl = HTMLElement & { webkitRequestFullscreen?: () => Promise<void> | void }\n\nexport function fullscreen(opts: FullscreenOptions = {}): Plugin {\n return (viewer) => {\n // The plugin is bound to one viewer, but the Fullscreen API and its change event are\n // document-global. Resolve and remember *this* viewer's root (captured from the mounted\n // toolbar button) so multi-viewer pages don't fullscreen / exit the wrong element (I-PL4).\n let boundRoot: HTMLElement | null = null\n const resolveRoot = (from?: HTMLElement | null): HTMLElement | null => {\n if (opts.target) return document.querySelector<HTMLElement>(opts.target)\n return (\n boundRoot ??\n from?.closest<HTMLElement>('.gallerist') ??\n document.querySelector('.gallerist')\n )\n }\n\n const fsElement = (): Element | null =>\n document.fullscreenElement ?? (document as WebkitDoc).webkitFullscreenElement ?? null\n // Base enter/exit on whether *our* root is the fullscreen element, not just \"is anything\n // fullscreen\" — otherwise an unrelated fullscreen <video> would make our button exit it (S-PL5).\n const isOurFullscreen = (el: HTMLElement | null): boolean => {\n const fe = fsElement()\n return !!fe && !!el && (fe === el || el.contains(fe))\n }\n const requestFs = (el: HTMLElement) =>\n (el.requestFullscreen ?? (el as WebkitEl).webkitRequestFullscreen)?.call(el)\n const exitFs = () =>\n (document.exitFullscreen ?? (document as WebkitDoc).webkitExitFullscreen)?.call(document)\n\n const toggle = async (from?: HTMLElement | null) => {\n const el = resolveRoot(from)\n if (!el) return\n boundRoot = el\n try {\n if (isOurFullscreen(el)) await exitFs()\n else await requestFs(el)\n } catch {\n // user-gesture / permission failures are non-fatal\n }\n }\n\n let btnEl: HTMLButtonElement | null = null\n // Look the internals up when emitting, not at construction, so building the plugin against a\n // wrapped viewer or a test double doesn't throw.\n const onChange = () => {\n const fullscreen = isOurFullscreen(resolveRoot())\n btnEl?.setAttribute('aria-pressed', String(fullscreen)) // a toggle: pressed while fullscreen\n getInternals(viewer).emit('fullscreenChange', { viewer, fullscreen })\n }\n\n const button = () => {\n const btn = document.createElement('button')\n btn.type = 'button'\n btn.className = 'gallerist-fullscreen-btn'\n btn.setAttribute('aria-label', opts.strings?.toggle ?? DEFAULT_FULLSCREEN_STRINGS.toggle)\n btn.setAttribute('aria-pressed', 'false')\n btn.innerHTML = ICONS.fullscreen\n btnEl = btn\n btn.addEventListener('click', () => void toggle(btn))\n // Capture our root once the button is mounted (so onChange/`f` target the right viewer).\n queueMicrotask(() => {\n boundRoot = btn.closest<HTMLElement>('.gallerist') ?? boundRoot\n })\n return btn\n }\n\n return {\n name: 'fullscreen',\n init() {\n if (typeof document !== 'undefined') {\n document.addEventListener('fullscreenchange', onChange)\n document.addEventListener('webkitfullscreenchange', onChange)\n }\n },\n destroy() {\n btnEl = null\n if (typeof document !== 'undefined') {\n document.removeEventListener('fullscreenchange', onChange)\n document.removeEventListener('webkitfullscreenchange', onChange)\n }\n },\n keymap: {\n f: () => void toggle(),\n },\n regions: {\n toolbar: button,\n },\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "@gallerist/plugin-fullscreen",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.3.0",
4
+ "description": "Gallerist plugin — native Fullscreen API toggle (toolbar button + 'f' key).",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "files": [
9
+ "dist",
10
+ "LICENSE",
11
+ "README.md"
12
+ ],
13
+ "main": "./dist/index.cjs",
14
+ "module": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "import": {
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ },
22
+ "require": {
23
+ "types": "./dist/index.d.cts",
24
+ "default": "./dist/index.cjs"
25
+ }
26
+ }
27
+ },
28
+ "peerDependencies": {
29
+ "@gallerist/core": ">=0.3.0 <1"
30
+ },
31
+ "devDependencies": {
32
+ "tsup": "^8.3.5",
33
+ "typescript": "^5.7.2",
34
+ "@gallerist/core": "0.3.0"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/shafayet9780/gallerist.git",
39
+ "directory": "packages/plugin-fullscreen"
40
+ },
41
+ "homepage": "https://github.com/shafayet9780/gallerist#readme",
42
+ "bugs": {
43
+ "url": "https://github.com/shafayet9780/gallerist/issues"
44
+ },
45
+ "author": "Shafayet Ahmmed",
46
+ "keywords": [
47
+ "lightbox",
48
+ "gallery",
49
+ "image-viewer",
50
+ "media-viewer",
51
+ "photo-gallery",
52
+ "carousel",
53
+ "zoom",
54
+ "pan",
55
+ "overlays",
56
+ "headless",
57
+ "framework-agnostic",
58
+ "virtualized",
59
+ "nested-gallery"
60
+ ],
61
+ "publishConfig": {
62
+ "access": "public"
63
+ },
64
+ "scripts": {
65
+ "build": "tsup",
66
+ "typecheck": "tsc --noEmit"
67
+ }
6
68
  }