@mohasinac/appkit 4.9.5 → 4.10.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 (108) hide show
  1. package/dist/_internal/server/features/auctions/actions.js +3 -2
  2. package/dist/_internal/server/features/auctions/service.d.ts +4 -3
  3. package/dist/_internal/server/features/auctions/service.js +6 -6
  4. package/dist/_internal/server/features/bundles/data.d.ts +9 -0
  5. package/dist/_internal/server/features/bundles/data.js +14 -0
  6. package/dist/_internal/server/features/bundles/index.d.ts +1 -1
  7. package/dist/_internal/server/features/bundles/index.js +1 -1
  8. package/dist/_internal/server/features/catalogue/og.d.ts +20 -0
  9. package/dist/_internal/server/features/catalogue/og.js +27 -0
  10. package/dist/_internal/server/features/grouped/data.d.ts +12 -0
  11. package/dist/_internal/server/features/grouped/data.js +26 -1
  12. package/dist/_internal/server/features/grouped/index.d.ts +1 -1
  13. package/dist/_internal/server/features/grouped/index.js +1 -1
  14. package/dist/_internal/server/features/products/data.d.ts +23 -0
  15. package/dist/_internal/server/features/products/data.js +159 -0
  16. package/dist/_internal/server/features/products/index.d.ts +1 -1
  17. package/dist/_internal/server/features/products/index.js +1 -1
  18. package/dist/_internal/shared/features/auctions/config.d.ts +20 -0
  19. package/dist/_internal/shared/features/auctions/config.js +31 -1
  20. package/dist/_internal/shared/listing-types/art/config.d.ts +1 -1
  21. package/dist/_internal/shared/listing-types/art/config.js +1 -1
  22. package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -1
  23. package/dist/_internal/shared/listing-types/classified/config.js +1 -1
  24. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -1
  25. package/dist/_internal/shared/listing-types/digital-code/config.js +1 -1
  26. package/dist/_internal/shared/listing-types/live/config.d.ts +1 -1
  27. package/dist/_internal/shared/listing-types/live/config.js +1 -1
  28. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -1
  29. package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
  30. package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -1
  31. package/dist/_internal/shared/listing-types/stickers/config.js +1 -1
  32. package/dist/client.d.ts +0 -6
  33. package/dist/client.js +1 -4
  34. package/dist/constants/api-endpoints.d.ts +3 -0
  35. package/dist/constants/api-endpoints.js +1 -0
  36. package/dist/features/admin/components/AdminSiteSettingsView.js +12 -4
  37. package/dist/features/admin/schemas/firestore.d.ts +9 -0
  38. package/dist/features/admin/schemas/firestore.js +6 -0
  39. package/dist/features/auctions/actions/bid-actions.js +4 -1
  40. package/dist/features/auctions/components/AuctionBottomActions.d.ts +2 -1
  41. package/dist/features/auctions/components/AuctionBottomActions.js +14 -1
  42. package/dist/features/auctions/components/AuctionDetailPageView.js +21 -9
  43. package/dist/features/auctions/components/LiveMinIncrement.d.ts +18 -0
  44. package/dist/features/auctions/components/LiveMinIncrement.js +17 -0
  45. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +5 -2
  46. package/dist/features/auctions/components/PlaceBidFormClient.js +7 -3
  47. package/dist/features/catalogue/components/PublicCatalogueItemDetailView.d.ts +11 -0
  48. package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +33 -0
  49. package/dist/features/catalogue/components/PublicCatalogueView.js +2 -1
  50. package/dist/features/catalogue/index.d.ts +2 -0
  51. package/dist/features/catalogue/index.js +1 -0
  52. package/dist/features/categories/components/BundleDetailView.d.ts +3 -1
  53. package/dist/features/categories/components/BundleDetailView.js +3 -2
  54. package/dist/features/classified/components/ClassifiedContactSellerPanel.d.ts +12 -0
  55. package/dist/features/classified/components/ClassifiedContactSellerPanel.js +49 -0
  56. package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +7 -0
  57. package/dist/features/classified/components/ClassifiedDetailPageView.js +69 -0
  58. package/dist/features/contact/components/ContactPageView.js +8 -2
  59. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.d.ts +19 -0
  60. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +79 -0
  61. package/dist/features/grouped/components/GroupedListingsCarousel.d.ts +13 -0
  62. package/dist/features/grouped/components/GroupedListingsCarousel.js +31 -0
  63. package/dist/features/grouped/repository/grouped-listings.repository.d.ts +8 -0
  64. package/dist/features/grouped/repository/grouped-listings.repository.js +16 -0
  65. package/dist/features/layout/BottomActions.js +4 -3
  66. package/dist/features/layout/BottomActionsContext.d.ts +10 -0
  67. package/dist/features/layout/BottomActionsContext.js +5 -1
  68. package/dist/features/layout/hooks/useBottomActions.d.ts +2 -0
  69. package/dist/features/layout/hooks/useBottomActions.js +4 -1
  70. package/dist/features/live/components/LiveItemDetailPageView.d.ts +9 -0
  71. package/dist/features/live/components/LiveItemDetailPageView.js +74 -0
  72. package/dist/features/media/MediaVideo.js +17 -1
  73. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +9 -1
  74. package/dist/features/products/components/PrizeDrawDetailPageView.js +11 -1
  75. package/dist/features/products/components/ProductDetailPageView.js +11 -116
  76. package/dist/features/products/components/RelatedItemsSection.d.ts +16 -0
  77. package/dist/features/products/components/RelatedItemsSection.js +17 -0
  78. package/dist/features/seller/components/SellerProductShell.js +1 -1
  79. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +83 -3
  80. package/dist/index.d.ts +2 -0
  81. package/dist/index.js +1 -0
  82. package/dist/next/routing/route-map.d.ts +2 -0
  83. package/dist/next/routing/route-map.js +7 -0
  84. package/dist/seed/bids-seed-data.js +48 -3
  85. package/dist/seed/grouped-listings-seed-data.js +167 -0
  86. package/dist/seed/payouts-seed-data.js +4 -4
  87. package/dist/seed/products-art-seed-data.js +188 -5
  88. package/dist/seed/products-auctions-seed-data.js +215 -6
  89. package/dist/seed/products-classifieds-seed-data.js +255 -8
  90. package/dist/seed/products-digital-codes-seed-data.js +258 -19
  91. package/dist/seed/products-live-items-seed-data.js +143 -10
  92. package/dist/seed/products-preorders-seed-data.js +213 -8
  93. package/dist/seed/products-prize-draws-seed-data.js +198 -0
  94. package/dist/seed/products-standard-seed-data.js +141 -0
  95. package/dist/seed/products-stickers-seed-data.js +178 -5
  96. package/dist/seed/site-settings-seed-data.js +10 -29
  97. package/dist/seed/store-extensions-seed-data.js +1 -1
  98. package/dist/server-entry.d.ts +4 -1
  99. package/dist/server-entry.js +4 -1
  100. package/dist/server.d.ts +4 -2
  101. package/dist/server.js +4 -2
  102. package/dist/styles.css +29 -1
  103. package/dist/tailwind-utilities.css +1 -1
  104. package/dist/ui/components/ImageLightbox.js +37 -3
  105. package/dist/ui/components/ImageLightbox.style.css +28 -0
  106. package/dist/utils/media-url.d.ts +9 -0
  107. package/dist/utils/media-url.js +39 -0
  108. package/package.json +1 -1
@@ -2,11 +2,12 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useRef, useState } from "react";
4
4
  import { createPortal } from "react-dom";
5
- import { ChevronLeft, ChevronRight, X, ZoomIn, ZoomOut, RotateCw, Maximize2 } from "lucide-react";
5
+ import { ChevronLeft, ChevronRight, X, ZoomIn, ZoomOut, RotateCw, Maximize2, Minimize2 } from "lucide-react";
6
6
  import { Button } from "./Button";
7
7
  import { Div } from "./Div";
8
8
  import { Text, Span } from "./Typography";
9
9
  import { MediaImage } from "../../features/media/MediaImage";
10
+ import { getYouTubeVideoId } from "../../utils/media-url";
10
11
  const CLS_CLOSE_BTN = "w-10 h-10 p-0 !min-h-0 rounded-full bg-white/15 hover:bg-error-surface text-white flex items-center justify-center";
11
12
  const MIN_ZOOM = 10;
12
13
  const MAX_ZOOM = 300;
@@ -15,6 +16,7 @@ export function ImageLightbox({ images, activeIndex, onClose, onNavigate, showTh
15
16
  const [currentIndex, setCurrentIndex] = useState(activeIndex ?? 0);
16
17
  const [zoom, setZoom] = useState(100);
17
18
  const [rotation, setRotation] = useState(0);
19
+ const [isFullscreen, setIsFullscreen] = useState(false);
18
20
  const overlayRef = useRef(null);
19
21
  // Sync external activeIndex and reset transforms
20
22
  useEffect(() => {
@@ -33,6 +35,37 @@ export function ImageLightbox({ images, activeIndex, onClose, onNavigate, showTh
33
35
  document.body.style.overflow = "hidden";
34
36
  return () => { document.body.style.overflow = prev; };
35
37
  }, [isOpen]);
38
+ // Track native Fullscreen API state so the toggle button + icon stay in sync
39
+ // even when the user exits via Esc/browser chrome rather than our button.
40
+ useEffect(() => {
41
+ const onFsChange = () => {
42
+ const doc = document;
43
+ const fsEl = document.fullscreenElement ?? doc.webkitFullscreenElement ?? null;
44
+ setIsFullscreen(fsEl === overlayRef.current);
45
+ };
46
+ document.addEventListener("fullscreenchange", onFsChange);
47
+ document.addEventListener("webkitfullscreenchange", onFsChange);
48
+ return () => {
49
+ document.removeEventListener("fullscreenchange", onFsChange);
50
+ document.removeEventListener("webkitfullscreenchange", onFsChange);
51
+ };
52
+ }, []);
53
+ // Exit native fullscreen when the lightbox itself closes.
54
+ useEffect(() => {
55
+ const doc = document;
56
+ if (!isOpen && (document.fullscreenElement || doc.webkitFullscreenElement)) {
57
+ (document.exitFullscreen?.() ?? doc.webkitExitFullscreen?.())?.catch?.(() => { });
58
+ }
59
+ }, [isOpen]);
60
+ const toggleFullscreen = useCallback(() => {
61
+ const doc = document;
62
+ if (document.fullscreenElement || doc.webkitFullscreenElement) {
63
+ (document.exitFullscreen?.() ?? doc.webkitExitFullscreen?.())?.catch?.(() => { });
64
+ return;
65
+ }
66
+ const el = overlayRef.current;
67
+ (el?.requestFullscreen?.() ?? el?.webkitRequestFullscreen?.())?.catch?.(() => { });
68
+ }, []);
36
69
  const navigate = useCallback((dir) => {
37
70
  setCurrentIndex((prev) => {
38
71
  const next = (prev + dir + images.length) % images.length;
@@ -89,11 +122,12 @@ export function ImageLightbox({ images, activeIndex, onClose, onNavigate, showTh
89
122
  return null;
90
123
  const image = images[currentIndex];
91
124
  const hasMultiple = images.length > 1;
125
+ const youtubeId = image.kind === "video" ? getYouTubeVideoId(image.src) : null;
92
126
  const iconBtnClass = "w-10 h-10 p-0 !min-h-0 rounded-full bg-white/15 hover:bg-white/30 text-white flex items-center justify-center";
93
- return createPortal(_jsxs("div", { ref: overlayRef, tabIndex: -1, className: "appkit-lightbox", role: "dialog", "aria-modal": "true", "aria-label": "Image lightbox", onKeyDown: handleKeyDown, onWheel: handleWheel, children: [_jsxs("div", { className: "absolute top-0 left-0 right-0 z-10 flex items-center justify-between px-4 py-3 bg-gradient-to-b from-black/60 to-transparent", children: [_jsx(Span, { size: "sm", weight: "medium", className: "text-white/70", children: hasMultiple ? `${currentIndex + 1} / ${images.length}` : "" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => adjustZoom(-ZOOM_STEP), className: iconBtnClass, "aria-label": "Zoom out", disabled: zoom <= MIN_ZOOM, children: _jsx(ZoomOut, { className: "w-4 h-4" }) }), _jsxs("button", { type: "button", onClick: () => { setZoom(100); setRotation(0); }, className: "text-white/70 hover:text-white text-xs font-mono min-w-[3rem] text-center", "aria-label": "Reset zoom", children: [zoom, "%"] }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => adjustZoom(ZOOM_STEP), className: iconBtnClass, "aria-label": "Zoom in", disabled: zoom >= MAX_ZOOM, children: _jsx(ZoomIn, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => setRotation((r) => (r + 90) % 360), className: iconBtnClass, "aria-label": "Rotate 90\u00B0", children: _jsx(RotateCw, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => { setZoom(100); setRotation(0); }, className: iconBtnClass, "aria-label": "Reset", children: _jsx(Maximize2, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: onClose, className: CLS_CLOSE_BTN, "aria-label": "Close lightbox", children: _jsx(X, { className: "w-5 h-5" }) })] })] }), hasMultiple && (_jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => navigate(-1), className: "absolute left-4 top-1/2 -translate-y-1/2 w-12 h-12 p-0 !min-h-0 rounded-full bg-white/15 hover:bg-white/30 text-white z-10 flex items-center justify-center", "aria-label": "Previous image", children: _jsx(ChevronLeft, { className: "w-7 h-7" }) })), _jsxs("div", { className: "appkit-lightbox__image-wrap", style: { cursor: zoom > 100 ? "grab" : "default" }, children: [image.kind === "video" ? (_jsx("video", { src: image.src, poster: image.poster, controls: true, playsInline: true, "aria-label": image.alt ?? "Video", className: "appkit-lightbox__img", style: {
127
+ return createPortal(_jsxs("div", { ref: overlayRef, tabIndex: -1, className: "appkit-lightbox", role: "dialog", "aria-modal": "true", "aria-label": "Image lightbox", onKeyDown: handleKeyDown, onWheel: handleWheel, children: [_jsxs("div", { className: "absolute top-0 left-0 right-0 z-10 flex items-center justify-between px-4 py-3 bg-gradient-to-b from-black/60 to-transparent", children: [_jsx(Span, { size: "sm", weight: "medium", className: "text-white/70", children: hasMultiple ? `${currentIndex + 1} / ${images.length}` : "" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => adjustZoom(-ZOOM_STEP), className: iconBtnClass, "aria-label": "Zoom out", disabled: zoom <= MIN_ZOOM, children: _jsx(ZoomOut, { className: "w-4 h-4" }) }), _jsxs("button", { type: "button", onClick: () => { setZoom(100); setRotation(0); }, className: "text-white/70 hover:text-white text-xs font-mono min-w-[3rem] text-center", "aria-label": "Reset zoom", children: [zoom, "%"] }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => adjustZoom(ZOOM_STEP), className: iconBtnClass, "aria-label": "Zoom in", disabled: zoom >= MAX_ZOOM, children: _jsx(ZoomIn, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => setRotation((r) => (r + 90) % 360), className: iconBtnClass, "aria-label": "Rotate 90\u00B0", children: _jsx(RotateCw, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: toggleFullscreen, className: iconBtnClass, "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen", children: isFullscreen ? _jsx(Minimize2, { className: "w-4 h-4" }) : _jsx(Maximize2, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: onClose, className: CLS_CLOSE_BTN, "aria-label": "Close lightbox", children: _jsx(X, { className: "w-5 h-5" }) })] })] }), hasMultiple && (_jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => navigate(-1), className: "absolute left-4 top-1/2 -translate-y-1/2 w-12 h-12 p-0 !min-h-0 rounded-full bg-white/15 hover:bg-white/30 text-white z-10 flex items-center justify-center", "aria-label": "Previous image", children: _jsx(ChevronLeft, { className: "w-7 h-7" }) })), _jsxs("div", { className: "appkit-lightbox__image-wrap", style: { cursor: zoom > 100 ? "grab" : "default" }, children: [image.kind === "video" ? (youtubeId ? (_jsx("iframe", { src: `https://www.youtube-nocookie.com/embed/${youtubeId}?playsinline=1&rel=0`, title: image.alt ?? "Video", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", allowFullScreen: true, className: "appkit-lightbox__img appkit-lightbox__video border-0" })) : (_jsx("video", { src: image.src, poster: image.poster, controls: true, playsInline: true, "aria-label": image.alt ?? "Video", className: "appkit-lightbox__img appkit-lightbox__video", style: {
94
128
  transform: `scale(${zoom / 100}) rotate(${rotation}deg)`,
95
129
  transition: "transform 0.2s ease",
96
- } })) : (_jsx(MediaImage, { src: image.src, alt: image.alt ?? "", size: "hero", objectFit: "contain", className: "appkit-lightbox__img", style: {
130
+ } }))) : (_jsx(MediaImage, { src: image.src, alt: image.alt ?? "", size: "hero", objectFit: "contain", className: "appkit-lightbox__img", style: {
97
131
  transform: `scale(${zoom / 100}) rotate(${rotation}deg)`,
98
132
  transition: "transform 0.2s ease",
99
133
  } })), image.badge && (_jsx(Div, { className: "absolute left-3 top-3 rounded bg-black/70 px-2 py-1 text-xs font-semibold text-white pointer-events-none", children: image.badge }))] }), (image.caption || image.sub) && (_jsxs("div", { className: "flex-shrink-0 text-center px-8 pb-2", children: [image.caption && (_jsx(Text, { size: "sm", variant: "secondary", className: "!text-white/80", children: image.caption })), image.sub && (_jsx(Text, { size: "sm", variant: "secondary", className: "!text-white/50", children: image.sub }))] })), showThumbnails && images.length > 1 && (_jsx("div", { className: "flex-shrink-0 flex gap-2 overflow-x-auto px-4 pb-3 justify-center", children: images.map((thumb, i) => (_jsx("button", { type: "button", onClick: () => {
@@ -37,6 +37,14 @@
37
37
  position: relative;
38
38
  }
39
39
 
40
+ @media (max-width: 640px) {
41
+ .appkit-lightbox__image-wrap {
42
+ /* Tighter than the desktop 4rem so a video's min-width (below) has room
43
+ to fit inside narrow phone viewports without forcing horizontal scroll. */
44
+ padding: 1.5rem 1rem;
45
+ }
46
+ }
47
+
40
48
  .appkit-lightbox__img {
41
49
  max-height: 100%;
42
50
  max-width: 100%;
@@ -44,6 +52,26 @@
44
52
  user-select: none;
45
53
  }
46
54
 
55
+ /* -- Video / YouTube-embed area ----------------------- */
56
+ /* Applied alongside .appkit-lightbox__img on the theater-mode <video>/<iframe>
57
+ so a video always occupies a real, tappable box — never the browser's tiny
58
+ ~300x150 default when a src fails to load or an embed reports no intrinsic
59
+ size (Recurrent Root Cause Pattern #27-adjacent: video needs its own sizing
60
+ contract, it can't just inherit the image one). */
61
+ .appkit-lightbox__video {
62
+ width: 100%;
63
+ height: 100%;
64
+ min-width: 260px;
65
+ min-height: 190px;
66
+ }
67
+
68
+ @media (min-width: 768px) {
69
+ .appkit-lightbox__video {
70
+ min-width: 480px;
71
+ min-height: 320px;
72
+ }
73
+ }
74
+
47
75
  /* -- Zoom hint --------------------------------------- */
48
76
 
49
77
  .appkit-lightbox__zoom-hint {
@@ -16,3 +16,12 @@ export declare const GCS_HOST = "storage.googleapis.com";
16
16
  * - Falsy → undefined
17
17
  */
18
18
  export declare function resolveMediaUrl(url: string | null | undefined): string | undefined;
19
+ /**
20
+ * Extracts the 11-char YouTube video ID from a watch/share/embed URL, or
21
+ * `null` when `url` isn't a recognized YouTube URL. A video field can be
22
+ * sourced via `MediaUploadField`'s "YouTube" tab (see `showYoutube` there),
23
+ * which stores a `youtube.com/watch?v=...` URL — that URL is never a raw
24
+ * playable media file, so every `<video src>` renderer (`MediaVideo`,
25
+ * `ImageLightbox`) must check this first and fall back to an iframe embed.
26
+ */
27
+ export declare function getYouTubeVideoId(url: string | null | undefined): string | null;
@@ -37,3 +37,42 @@ export function resolveMediaUrl(url) {
37
37
  return url;
38
38
  }
39
39
  }
40
+ const YOUTUBE_HOSTS = new Set([
41
+ "youtube.com",
42
+ "m.youtube.com",
43
+ "youtu.be",
44
+ "youtube-nocookie.com",
45
+ ]);
46
+ /**
47
+ * Extracts the 11-char YouTube video ID from a watch/share/embed URL, or
48
+ * `null` when `url` isn't a recognized YouTube URL. A video field can be
49
+ * sourced via `MediaUploadField`'s "YouTube" tab (see `showYoutube` there),
50
+ * which stores a `youtube.com/watch?v=...` URL — that URL is never a raw
51
+ * playable media file, so every `<video src>` renderer (`MediaVideo`,
52
+ * `ImageLightbox`) must check this first and fall back to an iframe embed.
53
+ */
54
+ export function getYouTubeVideoId(url) {
55
+ if (!url)
56
+ return null;
57
+ try {
58
+ const parsed = new URL(url);
59
+ const host = parsed.hostname.replace(/^www\./, "");
60
+ if (!YOUTUBE_HOSTS.has(host))
61
+ return null;
62
+ if (host === "youtu.be") {
63
+ const id = parsed.pathname.slice(1);
64
+ return /^[\w-]{11}$/.test(id) ? id : null;
65
+ }
66
+ const fromQuery = parsed.searchParams.get("v");
67
+ if (fromQuery && /^[\w-]{11}$/.test(fromQuery))
68
+ return fromQuery;
69
+ const embedMatch = parsed.pathname.match(/\/embed\/([\w-]{11})/);
70
+ if (embedMatch)
71
+ return embedMatch[1];
72
+ return null;
73
+ }
74
+ catch (_err) {
75
+ void normalizeError(_err);
76
+ return null;
77
+ }
78
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.9.5",
3
+ "version": "4.10.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"