@tappify/extension-sdk 2.0.0 → 2.1.0-beta.2
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/README.md +208 -0
- package/dist/chunk-2CY5HVY7.mjs +59 -0
- package/dist/chunk-2CY5HVY7.mjs.map +1 -0
- package/dist/{chunk-2DBSVKAU.cjs → chunk-6HETNZJY.cjs} +1 -1
- package/dist/chunk-6HETNZJY.cjs.map +1 -0
- package/dist/{chunk-PYJMJF44.cjs → chunk-7YKH4LZN.cjs} +9 -2
- package/dist/chunk-7YKH4LZN.cjs.map +1 -0
- package/dist/{chunk-RMUWRESK.cjs → chunk-A74HCO6T.cjs} +31 -18
- package/dist/chunk-A74HCO6T.cjs.map +1 -0
- package/dist/chunk-BPCGXXMD.cjs +781 -0
- package/dist/chunk-BPCGXXMD.cjs.map +1 -0
- package/dist/chunk-EB5XWQAU.cjs +132 -0
- package/dist/chunk-EB5XWQAU.cjs.map +1 -0
- package/dist/{chunk-5AAPD5WV.cjs → chunk-GAUZYBXE.cjs} +11 -11
- package/dist/chunk-GAUZYBXE.cjs.map +1 -0
- package/dist/chunk-J5LGTIGS.mjs +10 -0
- package/dist/chunk-J5LGTIGS.mjs.map +1 -0
- package/dist/{chunk-ZT35X7KL.mjs → chunk-KZ5NGFUP.mjs} +22 -9
- package/dist/chunk-KZ5NGFUP.mjs.map +1 -0
- package/dist/chunk-NN263PGQ.cjs +70 -0
- package/dist/chunk-NN263PGQ.cjs.map +1 -0
- package/dist/{chunk-3YWVYA42.cjs → chunk-O357OJXL.cjs} +14 -18
- package/dist/chunk-O357OJXL.cjs.map +1 -0
- package/dist/chunk-ONJMPO3P.cjs +59 -0
- package/dist/chunk-ONJMPO3P.cjs.map +1 -0
- package/dist/{chunk-OPPYEA7F.cjs → chunk-PRCZMOLD.cjs} +3 -3
- package/dist/chunk-PRCZMOLD.cjs.map +1 -0
- package/dist/chunk-Q7SFCCGT.cjs +10 -0
- package/dist/chunk-Q7SFCCGT.cjs.map +1 -0
- package/dist/{chunk-QHM7XNRK.mjs → chunk-QA5AL3VZ.mjs} +2 -2
- package/dist/{chunk-QHM7XNRK.mjs.map → chunk-QA5AL3VZ.mjs.map} +1 -1
- package/dist/chunk-QJMZRV7A.mjs +132 -0
- package/dist/chunk-QJMZRV7A.mjs.map +1 -0
- package/dist/{chunk-CSTYBKZC.mjs → chunk-QXD7WVKK.mjs} +1 -1
- package/dist/chunk-QXD7WVKK.mjs.map +1 -0
- package/dist/{chunk-4C5Z3CIK.mjs → chunk-RBIQT75L.mjs} +2 -2
- package/dist/chunk-RBIQT75L.mjs.map +1 -0
- package/dist/{chunk-MNDVYPEP.mjs → chunk-SGCYEEXD.mjs} +9 -2
- package/dist/chunk-SGCYEEXD.mjs.map +1 -0
- package/dist/{chunk-BDCX6LRQ.mjs → chunk-SGEYDV3K.mjs} +3 -3
- package/dist/chunk-SGEYDV3K.mjs.map +1 -0
- package/dist/{chunk-IGK2AP6J.mjs → chunk-SLGOHIBL.mjs} +13 -17
- package/dist/chunk-SLGOHIBL.mjs.map +1 -0
- package/dist/chunk-SWG7CNTZ.mjs +781 -0
- package/dist/chunk-SWG7CNTZ.mjs.map +1 -0
- package/dist/{chunk-7KIKOH5H.cjs → chunk-VV7ELNRW.cjs} +3 -3
- package/dist/chunk-VV7ELNRW.cjs.map +1 -0
- package/dist/chunk-Z43DPNZ6.mjs +70 -0
- package/dist/chunk-Z43DPNZ6.mjs.map +1 -0
- package/dist/client-BO3kavn0.d.ts +25 -0
- package/dist/client-DE-vMgdO.d.cts +25 -0
- package/dist/codegen.cjs.map +1 -1
- package/dist/codegen.d.cts +43 -7
- package/dist/codegen.d.ts +43 -7
- package/dist/codegen.mjs.map +1 -1
- package/dist/{constants-DJ_KgZpN.d.cts → constants-Bh759FUI.d.cts} +44 -8
- package/dist/{constants-DJ_KgZpN.d.ts → constants-Bh759FUI.d.ts} +44 -8
- package/dist/{context-Dt6xh_5t.d.cts → context-DI0EJCRD.d.cts} +5 -4
- package/dist/{context-Ml7k_V14.d.ts → context-mr8R5CNt.d.ts} +5 -4
- package/dist/{errors-COsBh7kd.d.cts → errors-Cqh0wrqt.d.cts} +3 -2
- package/dist/{errors-COsBh7kd.d.ts → errors-Cqh0wrqt.d.ts} +3 -2
- package/dist/host.cjs +5 -4
- package/dist/host.cjs.map +1 -1
- package/dist/host.d.cts +5 -5
- package/dist/host.d.ts +5 -5
- package/dist/host.mjs +4 -3
- package/dist/host.mjs.map +1 -1
- package/dist/index.cjs +9 -656
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -627
- package/dist/index.d.ts +69 -627
- package/dist/index.mjs +5 -652
- package/dist/index.mjs.map +1 -1
- package/dist/manifest.cjs +142 -86
- package/dist/manifest.cjs.map +1 -1
- package/dist/manifest.d.cts +97 -12
- package/dist/manifest.d.ts +97 -12
- package/dist/manifest.mjs +129 -73
- package/dist/manifest.mjs.map +1 -1
- package/dist/server.cjs +15 -66
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +6 -6
- package/dist/server.d.ts +6 -6
- package/dist/server.mjs +10 -61
- package/dist/server.mjs.map +1 -1
- package/dist/testing/index.cjs +73 -750
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +30 -11
- package/dist/testing/index.d.ts +30 -11
- package/dist/testing/index.mjs +89 -766
- package/dist/testing/index.mjs.map +1 -1
- package/dist/testing/jest.cjs +6 -5
- package/dist/testing/jest.cjs.map +1 -1
- package/dist/testing/jest.mjs +3 -2
- package/dist/testing/jest.mjs.map +1 -1
- package/dist/testing/vitest.cjs +6 -5
- package/dist/testing/vitest.cjs.map +1 -1
- package/dist/testing/vitest.d.cts +10 -0
- package/dist/testing/vitest.d.ts +10 -0
- package/dist/testing/vitest.mjs +3 -2
- package/dist/testing/vitest.mjs.map +1 -1
- package/dist/{types-DDAGLpov.d.ts → types-BdDaLnqs.d.cts} +4 -3
- package/dist/{types-DrpQSv_2.d.cts → types-uGHrRaOj.d.ts} +4 -3
- package/dist/ui/styles.css +740 -0
- package/dist/ui.cjs +1018 -0
- package/dist/ui.cjs.map +1 -0
- package/dist/ui.d.cts +231 -0
- package/dist/ui.d.ts +231 -0
- package/dist/ui.mjs +1018 -0
- package/dist/ui.mjs.map +1 -0
- package/dist/vite-preview-bootstrap.cjs +66 -0
- package/dist/vite-preview-bootstrap.cjs.map +1 -0
- package/dist/vite-preview-bootstrap.d.cts +15 -0
- package/dist/vite-preview-bootstrap.d.ts +15 -0
- package/dist/vite-preview-bootstrap.mjs +66 -0
- package/dist/vite-preview-bootstrap.mjs.map +1 -0
- package/dist/vite-preview.cjs +876 -0
- package/dist/vite-preview.cjs.map +1 -0
- package/dist/vite-preview.d.cts +9 -0
- package/dist/vite-preview.d.ts +9 -0
- package/dist/vite-preview.mjs +876 -0
- package/dist/vite-preview.mjs.map +1 -0
- package/dist/vite.cjs +320 -60
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.d.cts +54 -8
- package/dist/vite.d.ts +54 -8
- package/dist/vite.mjs +312 -54
- package/dist/vite.mjs.map +1 -1
- package/dist/work-events-BlOVpK0k.d.ts +547 -0
- package/dist/work-events-D97EoqvS.d.cts +547 -0
- package/package.json +19 -2
- package/schema/extension-v2.json +440 -121
- package/dist/chunk-2DBSVKAU.cjs.map +0 -1
- package/dist/chunk-3YWVYA42.cjs.map +0 -1
- package/dist/chunk-4C5Z3CIK.mjs.map +0 -1
- package/dist/chunk-5AAPD5WV.cjs.map +0 -1
- package/dist/chunk-7KIKOH5H.cjs.map +0 -1
- package/dist/chunk-BDCX6LRQ.mjs.map +0 -1
- package/dist/chunk-CSTYBKZC.mjs.map +0 -1
- package/dist/chunk-IGK2AP6J.mjs.map +0 -1
- package/dist/chunk-MNDVYPEP.mjs.map +0 -1
- package/dist/chunk-OPPYEA7F.cjs.map +0 -1
- package/dist/chunk-PYJMJF44.cjs.map +0 -1
- package/dist/chunk-RMUWRESK.cjs.map +0 -1
- package/dist/chunk-ZT35X7KL.mjs.map +0 -1
- package/dist/handler-CZIlZ5vu.d.ts +0 -227
- package/dist/handler-kU9Jf36K.d.cts +0 -227
- package/dist/styles.css +0 -367
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/ui/cn.ts","../src/client/ui/primitives.tsx","../src/client/ui/TapDialog.tsx","../src/client/ui/TapForm.tsx"],"sourcesContent":["import clsx, { type ClassValue as ClsxClassValue } from 'clsx';\n\n/**\n * Anything `cn` accepts: a class string, a number, an array of these, a map of\n * class name to condition, or a value to drop.\n */\nexport type ClassValue = ClsxClassValue;\n\n/**\n * Joins class names, dropping the ones that are `false`, `null` or `undefined`.\n *\n * @remarks\n * This is `clsx`, re-exported so a vendor surface needs no class-name dependency\n * of its own. It does not merge conflicting utility classes.\n *\n * @example\n * ```tsx\n * import { cn } from '@tappify/extension-sdk';\n *\n * function Row({ compact }: { compact: boolean }) {\n * return <div className={cn('starter-row', compact && 'starter-row--compact')} />;\n * }\n * ```\n */\nexport function cn(...values: ClassValue[]): string {\n return clsx(values);\n}\n","import {\n useId,\n type ButtonHTMLAttributes,\n type InputHTMLAttributes,\n type ReactElement,\n type ReactNode,\n type SelectHTMLAttributes,\n type TextareaHTMLAttributes,\n} from 'react';\nimport { cn } from './cn';\n\n/** The props `TapButton` takes, on top of the native button attributes. */\nexport interface TapButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Defaults to `primary`. */\n variant?: 'primary' | 'secondary' | 'ghost' | 'danger';\n /** Defaults to `md`. */\n size?: 'sm' | 'md';\n /** Disables the button and marks it `aria-busy`. */\n loading?: boolean;\n}\n\n/**\n * Renders a button in the host's styling, in one of four variants.\n *\n * @remarks\n * Draws on `--tap-accent` and `--tap-accent-fg` for `primary`, `--bg-raised`,\n * `--fg-default` and `--divider` for `secondary`, `--fg-muted` for `ghost` and\n * `--tap-danger` for `danger`, and takes its radius from `--tap-radius`. The\n * element defaults to `type=\"button\"`, so a button inside a `TapForm` does not\n * submit it unless you pass `type=\"submit\"`. `loading` disables the button, so a\n * click cannot land twice.\n *\n * @example\n * ```tsx\n * import { TapButton } from '@tappify/extension-sdk';\n *\n * function ExportRow({ busy, onExport }: { busy: boolean; onExport: () => void }) {\n * return (\n * <TapButton variant=\"secondary\" size=\"sm\" loading={busy} onClick={onExport}>\n * Export\n * </TapButton>\n * );\n * }\n * ```\n */\nexport function TapButton({\n variant = 'primary',\n size = 'md',\n loading = false,\n disabled,\n className,\n children,\n ...rest\n}: TapButtonProps): ReactElement {\n return (\n <button\n type=\"button\"\n {...rest}\n disabled={disabled === true || loading}\n aria-busy={loading || undefined}\n className={cn(\n 'tap-btn',\n `tap-btn--${variant}`,\n size === 'sm' && 'tap-btn--sm',\n className,\n )}\n >\n {children}\n </button>\n );\n}\n\n/** The props `TapCard` takes. */\nexport interface TapCardProps {\n /** Rendered in the header, which appears only with a `title` or an `action`. */\n title?: ReactNode;\n /** Sits at the end of the header, for a button or a link. */\n action?: ReactNode;\n /** Rendered under a divider below the body. */\n footer?: ReactNode;\n className?: string;\n children: ReactNode;\n}\n\n/**\n * Renders the bordered surface a widget, tab or page section sits in.\n *\n * @remarks\n * Renders as a `section` on `--bg-raised` with a `--divider` border and the\n * `--tap-radius` corner, and the footer in `--fg-muted`. The header appears only\n * when `title` or `action` is given, and the body carries its own padding, so\n * nest content directly rather than wrapping it in another padded box. A widget\n * belongs in one card.\n *\n * @example\n * ```tsx\n * import { TapButton, TapCard, TapStat } from '@tappify/extension-sdk';\n *\n * function InstallSummary() {\n * return (\n * <TapCard\n * title=\"Install summary\"\n * action={<TapButton variant=\"ghost\" size=\"sm\">Compact</TapButton>}\n * footer={<span>Last seven days</span>}\n * >\n * <TapStat label=\"Installs\" value=\"1,260\" delta={0.087} />\n * </TapCard>\n * );\n * }\n * ```\n */\nexport function TapCard({\n title,\n action,\n footer,\n className,\n children,\n}: TapCardProps): ReactElement {\n return (\n <section className={cn('tap-card', className)}>\n {(title !== undefined || action !== undefined) && (\n <header className=\"tap-card__head\">\n <span>{title}</span>\n {action}\n </header>\n )}\n <div className=\"tap-card__body\">{children}</div>\n {footer !== undefined && (\n <footer className=\"tap-card__foot\">{footer}</footer>\n )}\n </section>\n );\n}\n\n/** The props `TapPageHeader` takes. */\nexport interface TapPageHeaderProps {\n title: ReactNode;\n description?: ReactNode;\n /** Laid out in a row at the end of the header. */\n actions?: ReactNode;\n}\n\n/**\n * Renders the title, description and actions at the top of a tab, a page or a\n * settings panel.\n *\n * @remarks\n * The title renders as an `h2`, because the host owns the page's `h1`. The\n * description follows `--fg-muted` and the rule under the header follows\n * `--divider`. Under 360 pixels of mount width the actions stack under the title.\n * A widget in a slot uses `TapCard`'s own header instead.\n *\n * @example\n * ```tsx\n * import { TapButton, TapPageHeader } from '@tappify/extension-sdk';\n *\n * function FunnelsHeader() {\n * return (\n * <TapPageHeader\n * title=\"Funnels\"\n * description=\"Every step from impression to install.\"\n * actions={<TapButton size=\"sm\">New funnel</TapButton>}\n * />\n * );\n * }\n * ```\n */\nexport function TapPageHeader({\n title,\n description,\n actions,\n}: TapPageHeaderProps): ReactElement {\n return (\n <div className=\"tap-page-header\">\n <div>\n <h2 className=\"tap-page-header__title\">{title}</h2>\n {description !== undefined && (\n <p className=\"tap-page-header__description\">{description}</p>\n )}\n </div>\n {actions !== undefined && <div className=\"tap-row\">{actions}</div>}\n </div>\n );\n}\n\n/** The props `TapSkeleton` takes. */\nexport interface TapSkeletonProps {\n /** How many bars to draw. Defaults to one. */\n lines?: number;\n /** A CSS width for every bar. Left out, each bar is 12% shorter than the last. */\n width?: string;\n className?: string;\n}\n\n/**\n * Renders the placeholder bars that stand in for content while a read is in\n * flight.\n *\n * @remarks\n * The bars are `aria-hidden`, so a screen reader is not told about them, and they\n * take their tone from `--bg-subtle`. The pulse stops under\n * `prefers-reduced-motion`. Render this while `isLoading` is `true`, in place of\n * the content rather than beside it.\n *\n * @example\n * ```tsx\n * import { TapSkeleton, TapStat, useTapQuery } from '@tappify/extension-sdk';\n *\n * function Keywords() {\n * const keywords = useTapQuery({ kind: 'keywords' });\n * if (keywords.isLoading) return <TapSkeleton lines={3} />;\n * return <TapStat label=\"Tracked\" value={keywords.data?.keywords.length ?? 0} />;\n * }\n * ```\n */\nexport function TapSkeleton({\n lines = 1,\n width,\n className,\n}: TapSkeletonProps): ReactElement {\n return (\n <div aria-hidden=\"true\">\n {Array.from({ length: lines }, (_, index) => (\n <span\n key={index}\n className={cn('tap-skeleton', className)}\n style={{ width: width ?? `${String(100 - index * 12)}%` }}\n />\n ))}\n </div>\n );\n}\n\n/** The props `TapEmptyState` takes. */\nexport interface TapEmptyStateProps {\n title: string;\n description?: string;\n /** Rendered under the text, for the one thing the owner can do next. */\n action?: ReactNode;\n}\n\n/**\n * Renders the centred message that stands in for content when there is nothing to\n * show.\n *\n * @remarks\n * Centres its text in `--fg-muted` with the title in `--fg-default`. Use it for an\n * empty result or a surface the install has not been granted; a failure belongs in\n * `TapErrorState`, which carries a retry and announces itself as an alert.\n *\n * @example\n * ```tsx\n * import { TapEmptyState, useTapQuery } from '@tappify/extension-sdk';\n *\n * function Reviews() {\n * const reviews = useTapQuery({\n * kind: 'reviews',\n * range: { from: '2026-09-01', to: '2026-09-08' },\n * });\n * if (reviews.data?.total === 0) {\n * return <TapEmptyState title=\"No reviews in this range\" />;\n * }\n * return <span>{reviews.data?.total ?? 0} reviews</span>;\n * }\n * ```\n */\nexport function TapEmptyState({\n title,\n description,\n action,\n}: TapEmptyStateProps): ReactElement {\n return (\n <div className=\"tap-state\">\n <span className=\"tap-state__title\">{title}</span>\n {description !== undefined && <span>{description}</span>}\n {action}\n </div>\n );\n}\n\n/** The props `TapErrorState` takes. */\nexport interface TapErrorStateProps {\n title: string;\n /** The failure's own message, which is what a caller shows the owner. */\n description?: string;\n /** An error code, rendered small in `--font-mono` under the description. */\n code?: string;\n /** Renders a Retry button, and is called by it. */\n onRetry?: () => void;\n}\n\n/**\n * Renders the centred failure message a surface shows in place of its content.\n *\n * @remarks\n * Carries `role=\"alert\"`, so the host announces it, and draws the title in\n * `--tap-danger`. The Retry button appears only with `onRetry`; wire it to the\n * `refetch` of every read the surface needs. Pass the failure's own `message` as\n * the description — an error from `useTapServer` carries the text your server sent\n * the owner — and the `code` of a `TapError` or `TapServerError` as `code`.\n *\n * @example\n * ```tsx\n * import { TapErrorState, useTapQuery } from '@tappify/extension-sdk';\n *\n * function Crashes() {\n * const crashes = useTapQuery({\n * kind: 'crashes',\n * range: { from: '2026-09-01', to: '2026-09-08' },\n * });\n * if (crashes.error !== null) {\n * return (\n * <TapErrorState\n * title=\"Crashes did not load\"\n * description={crashes.error.message}\n * onRetry={crashes.refetch}\n * />\n * );\n * }\n * return <span>{crashes.data?.issues.length ?? 0} issues</span>;\n * }\n * ```\n */\nexport function TapErrorState({\n title,\n description,\n code,\n onRetry,\n}: TapErrorStateProps): ReactElement {\n return (\n <div className=\"tap-state tap-state--error\" role=\"alert\">\n <span className=\"tap-state__title\">{title}</span>\n {description !== undefined && <span>{description}</span>}\n {code !== undefined && <span className=\"tap-state__code\">{code}</span>}\n {onRetry !== undefined && (\n <TapButton variant=\"secondary\" size=\"sm\" onClick={onRetry}>\n Retry\n </TapButton>\n )}\n </div>\n );\n}\n\n/** The props `TapStat` takes. */\nexport interface TapStatProps {\n label: string;\n value: ReactNode;\n /** A share of the previous period, so `0.087` renders as `+9%`. */\n delta?: number;\n /** A line of context under the number. */\n hint?: string;\n}\n\n/**\n * Renders one labelled number, with an optional change against the previous\n * period.\n *\n * @remarks\n * The label is uppercased in `--fg-muted` and the number uses tabular figures, so\n * a row of stats stays aligned as values change. `delta` is a ratio, not a\n * percentage: it renders as whole percent with a plus in front when it is above\n * zero, and is coloured `--tap-danger` below zero and `--tap-accent` at or above\n * it. Format `value`\n * yourself with `tap.format.number` or `tap.format.currency`, so it follows the\n * owner's locale.\n *\n * @example\n * ```tsx\n * import { TapStat, useTap } from '@tappify/extension-sdk';\n *\n * function Installs({ installs, delta }: { installs: number; delta: number }) {\n * const tap = useTap();\n * return (\n * <TapStat\n * label=\"Installs\"\n * value={tap.format.number(installs)}\n * delta={delta}\n * hint=\"Against the previous seven days\"\n * />\n * );\n * }\n * ```\n */\nexport function TapStat({\n label,\n value,\n delta,\n hint,\n}: TapStatProps): ReactElement {\n const percent =\n delta === undefined\n ? undefined\n : `${delta > 0 ? '+' : ''}${String(Math.round(delta * 100))}%`;\n\n return (\n <div className=\"tap-stat\">\n <span className=\"tap-stat__label\">{label}</span>\n <span className=\"tap-stat__value\">{value}</span>\n {percent !== undefined && (\n <span\n className={cn(\n delta !== undefined && delta < 0\n ? 'tap-stat__delta--down'\n : 'tap-stat__delta--up',\n )}\n >\n {percent}\n </span>\n )}\n {hint !== undefined && <span className=\"tap-field__hint\">{hint}</span>}\n </div>\n );\n}\n\ninterface FieldFrameProps {\n id: string;\n label?: string;\n hint?: string;\n error?: string;\n hintId: string;\n errorId: string;\n children: ReactNode;\n}\n\nfunction FieldFrame({\n id,\n label,\n hint,\n error,\n hintId,\n errorId,\n children,\n}: FieldFrameProps): ReactElement {\n return (\n <div className=\"tap-field\">\n {label !== undefined && (\n <label className=\"tap-field__label\" htmlFor={id}>\n {label}\n </label>\n )}\n {children}\n {hint !== undefined && (\n <span className=\"tap-field__hint\" id={hintId}>\n {hint}\n </span>\n )}\n {error !== undefined && (\n <span className=\"tap-field__error\" id={errorId}>\n {error}\n </span>\n )}\n </div>\n );\n}\n\nfunction describedByOf(\n fieldId: string,\n hint: string | undefined,\n error: string | undefined,\n): string | undefined {\n const value = cn(\n hint !== undefined && `${fieldId}-hint`,\n error !== undefined && `${fieldId}-error`,\n );\n return value === '' ? undefined : value;\n}\n\n/**\n * The `TapInput` props for a single-line control, on top of the native input\n * attributes minus `size`.\n */\nexport interface TapSingleLineInputProps extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n 'size'\n> {\n /** Rendered as a `label` bound to the control by id. */\n label?: string;\n /** Marks the control invalid and renders the message under it. */\n error?: string;\n /** A line of guidance under the control, referenced by `aria-describedby`. */\n hint?: string;\n}\n\nfunction TapSingleLineInput({\n label,\n error,\n hint,\n className,\n id,\n ...rest\n}: TapSingleLineInputProps): ReactElement {\n const generated = useId();\n const fieldId = id ?? generated;\n\n return (\n <FieldFrame\n id={fieldId}\n label={label}\n hint={hint}\n error={error}\n hintId={`${fieldId}-hint`}\n errorId={`${fieldId}-error`}\n >\n <input\n {...rest}\n id={fieldId}\n className={cn(\n 'tap-control',\n error !== undefined && 'tap-control--invalid',\n className,\n )}\n aria-invalid={error !== undefined || undefined}\n aria-describedby={describedByOf(fieldId, hint, error)}\n />\n </FieldFrame>\n );\n}\n\n/**\n * The `TapInput` props for a multiline control, on top of the native textarea\n * attributes.\n */\nexport interface TapTextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {\n /** Rendered as a `label` bound to the control by id. */\n label?: string;\n /** Marks the control invalid and renders the message under it. */\n error?: string;\n /** A line of guidance under the control, referenced by `aria-describedby`. */\n hint?: string;\n}\n\nfunction TapTextarea({\n label,\n error,\n hint,\n className,\n id,\n rows,\n ...rest\n}: TapTextareaProps): ReactElement {\n const generated = useId();\n const fieldId = id ?? generated;\n\n return (\n <FieldFrame\n id={fieldId}\n label={label}\n hint={hint}\n error={error}\n hintId={`${fieldId}-hint`}\n errorId={`${fieldId}-error`}\n >\n <textarea\n {...rest}\n id={fieldId}\n rows={rows ?? 4}\n className={cn(\n 'tap-control',\n error !== undefined && 'tap-control--invalid',\n className,\n )}\n aria-invalid={error !== undefined || undefined}\n aria-describedby={describedByOf(fieldId, hint, error)}\n />\n </FieldFrame>\n );\n}\n\n/**\n * The props `TapInput` takes: the single-line set, or the textarea set behind\n * `multiline`.\n */\nexport type TapInputProps =\n | ({ multiline?: false } & TapSingleLineInputProps)\n | ({ multiline: true } & TapTextareaProps);\n\n/**\n * Renders a labelled text control with its hint and error, as an input or, with\n * `multiline`, a textarea.\n *\n * @remarks\n * Generates an id when none is passed and binds the label, the hint and the error\n * to the control, so the field is announced whole; an `error` also sets\n * `aria-invalid` and the `--tap-danger` border. The textarea starts at four rows\n * and is resizable in height. Pass `type=\"checkbox\"` with\n * `className=\"tap-control--checkbox\"` for a boolean, which is what `TapForm`\n * does.\n *\n * @example\n * ```tsx\n * import { TapInput } from '@tappify/extension-sdk';\n *\n * function TermField({ term, onTerm }: { term: string; onTerm: (next: string) => void }) {\n * return (\n * <TapInput\n * label=\"Keyword\"\n * hint=\"One term per row in the report.\"\n * value={term}\n * onChange={event => onTerm(event.target.value)}\n * />\n * );\n * }\n * ```\n */\nexport function TapInput(props: TapInputProps): ReactElement {\n if (props.multiline === true) {\n const { multiline: _multiline, ...rest } = props;\n return <TapTextarea {...rest} />;\n }\n\n const { multiline: _multiline, ...rest } = props;\n return <TapSingleLineInput {...rest} />;\n}\n\n/** The props `TapSelect` takes, on top of the native select attributes. */\nexport interface TapSelectProps extends SelectHTMLAttributes<HTMLSelectElement> {\n /** Rendered as a `label` bound to the control by id. */\n label?: string;\n /** Marks the control invalid and renders the message under it. */\n error?: string;\n /** A line of guidance under the control, referenced by `aria-describedby`. */\n hint?: string;\n /** Rendered in order; the component adds no empty option of its own. */\n options: { value: string; label: string }[];\n}\n\n/**\n * Renders a labelled select over a list of options, with its hint and error.\n *\n * @remarks\n * Carries the same label, hint, error and `aria-describedby` wiring as\n * `TapInput`. Add your own empty option when the field starts unset — `TapForm`\n * puts a `Choose one` option in front of a schema's enum values.\n *\n * @example\n * ```tsx\n * import { TapSelect } from '@tappify/extension-sdk';\n *\n * function PlatformPicker({ value, onPick }: { value: string; onPick: (next: string) => void }) {\n * return (\n * <TapSelect\n * label=\"Platform\"\n * value={value}\n * options={[\n * { value: 'ios', label: 'iOS' },\n * { value: 'android', label: 'Android' },\n * ]}\n * onChange={event => onPick(event.target.value)}\n * />\n * );\n * }\n * ```\n */\nexport function TapSelect({\n label,\n error,\n hint,\n options,\n className,\n id,\n ...rest\n}: TapSelectProps): ReactElement {\n const generated = useId();\n const fieldId = id ?? generated;\n\n return (\n <FieldFrame\n id={fieldId}\n label={label}\n hint={hint}\n error={error}\n hintId={`${fieldId}-hint`}\n errorId={`${fieldId}-error`}\n >\n <select\n {...rest}\n id={fieldId}\n className={cn(\n 'tap-control',\n error !== undefined && 'tap-control--invalid',\n className,\n )}\n aria-invalid={error !== undefined || undefined}\n aria-describedby={describedByOf(fieldId, hint, error)}\n >\n {options.map(option => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </FieldFrame>\n );\n}\n\n/** One column of a `TapTable`. */\nexport interface TapTableColumnDef<T> {\n /** The key read from each row when there is no `render`. */\n key: string;\n label: string;\n /** `end` right-aligns the column and gives it tabular figures. */\n align?: 'start' | 'end';\n /** Draws the cell. Without it the cell shows the row's own string or number. */\n render?(row: T): ReactNode;\n}\n\n/** The props `TapTable` takes. */\nexport interface TapTableProps<T> {\n columns: TapTableColumnDef<T>[];\n rows: T[];\n /** Returns a stable key per row, which React uses for reconciliation. */\n rowKey: (row: T) => string;\n /** Rendered in place of the whole table when `rows` is empty. */\n empty?: ReactNode;\n /** Makes each row clickable and focusable, and is called on click or Enter. */\n onRowClick?: (row: T) => void;\n}\n\nfunction cellText(row: unknown, key: string): string {\n if (row === null || typeof row !== 'object') return '';\n const value: unknown = Reflect.get(row, key);\n if (typeof value === 'string') return value;\n if (\n typeof value === 'number' ||\n typeof value === 'boolean' ||\n typeof value === 'bigint'\n ) {\n return String(value);\n }\n return '';\n}\n\n/**\n * Renders rows under column headers, scrolling sideways inside the mount rather\n * than widening it.\n *\n * @remarks\n * A cell with no `render` shows the row's own value under `key` when that value is\n * a string, number, boolean or bigint, and nothing otherwise, so an object or an\n * array needs a `render`. With `empty` and no rows, the table is replaced whole by\n * that node. With `onRowClick` each row becomes focusable and answers Enter and\n * Space as well as a click, and hovers on `--bg-subtle`; the headers and rules\n * follow `--fg-muted` and `--divider`.\n *\n * @example\n * ```tsx\n * import { TapEmptyState, TapTable, useTapQuery } from '@tappify/extension-sdk';\n *\n * function Keywords() {\n * const keywords = useTapQuery({ kind: 'keywords' });\n * return (\n * <TapTable\n * columns={[\n * { key: 'term', label: 'Term' },\n * { key: 'position', label: 'Rank', align: 'end' },\n * ]}\n * rows={keywords.data?.keywords ?? []}\n * rowKey={keyword => keyword.id}\n * empty={<TapEmptyState title=\"No keywords tracked yet\" />}\n * />\n * );\n * }\n * ```\n */\nexport function TapTable<T>({\n columns,\n rows,\n rowKey,\n empty,\n onRowClick,\n}: TapTableProps<T>): ReactElement {\n if (rows.length === 0 && empty !== undefined) {\n return <>{empty}</>;\n }\n\n return (\n <div className=\"tap-table-wrap\">\n <table className=\"tap-table\">\n <thead>\n <tr>\n {columns.map(column => (\n <th\n key={column.key}\n className={cn(column.align === 'end' && 'tap-table__end')}\n scope=\"col\"\n >\n {column.label}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.map(row => (\n <tr\n key={rowKey(row)}\n data-clickable={onRowClick !== undefined}\n tabIndex={onRowClick === undefined ? undefined : 0}\n onClick={\n onRowClick === undefined ? undefined : () => onRowClick(row)\n }\n onKeyDown={\n onRowClick === undefined\n ? undefined\n : event => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n onRowClick(row);\n }\n }\n >\n {columns.map(column => (\n <td\n key={column.key}\n className={cn(column.align === 'end' && 'tap-table__end')}\n >\n {column.render\n ? column.render(row)\n : cellText(row, column.key)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n","import {\n useCallback,\n useEffect,\n useId,\n useRef,\n type KeyboardEvent,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { useTap } from '../context';\nimport { TapError } from '../errors';\n\nconst DIALOG_CLOSE_EVENT = 'tap:dialog-close';\n\nconst FOCUSABLE =\n 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * `document.activeElement` is the mount's shadow host whenever focus sits\n * inside the shadow tree, so the trap resolves focus from the dialog's own root.\n */\nfunction activeElementNear(node: Node | null): Element | null {\n const root = node?.getRootNode();\n return root instanceof ShadowRoot\n ? root.activeElement\n : document.activeElement;\n}\n\n/** The props `TapDialog` takes. */\nexport interface TapDialogProps {\n /** The dialog renders nothing while this is `false`. */\n open: boolean;\n /** Rendered in the header, and used as the dialog's accessible name. */\n title: string;\n /** Called by Escape, by a click on the scrim, and by the host's close event. */\n onClose: () => void;\n /** Rendered under a divider at the end, for the dialog's buttons. */\n footer?: ReactNode;\n children: ReactNode;\n /** The id of your own heading, when the accessible name is not the title. */\n labelledBy?: string;\n}\n\n/**\n * Renders a modal dialog into the mount's own portal node, so it covers the\n * surface without leaving the extension.\n *\n * @remarks\n * Rendered outside a Tappify mount it throws `TAP_OUTSIDE_HOST`, from the `useTap`\n * above it. Inside a mount whose shadow root has no portal node yet, opening it\n * throws `TapError` with code `TAP_NO_PORTAL`; `renderWithTap` provides that node\n * in tests. While open it traps Tab\n * inside itself, moves focus to the first focusable element and returns focus\n * where it was on close, and answers Escape and a click on the scrim with\n * `onClose`. The scrim follows `--tap-scrim` and the panel `--bg-raised`,\n * `--divider` and `--tap-radius`; the panel is at most 520 pixels wide. The\n * component keeps no open state of its own.\n *\n * @example\n * ```tsx\n * import { TapButton, TapDialog } from '@tappify/extension-sdk';\n * import { useState } from 'react';\n *\n * function ExportDialog() {\n * const [open, setOpen] = useState(false);\n * return (\n * <>\n * <TapButton onClick={() => setOpen(true)}>Export</TapButton>\n * <TapDialog\n * open={open}\n * title=\"Export keywords\"\n * onClose={() => setOpen(false)}\n * footer={<TapButton onClick={() => setOpen(false)}>Done</TapButton>}\n * >\n * <span>The report covers the range in the filter bar.</span>\n * </TapDialog>\n * </>\n * );\n * }\n * ```\n */\nexport function TapDialog({\n open,\n title,\n onClose,\n footer,\n children,\n labelledBy,\n}: TapDialogProps): ReactElement | null {\n const tap = useTap();\n const dialogRef = useRef<HTMLDivElement | null>(null);\n const titleId = useId();\n\n const focusables = useCallback((): HTMLElement[] => {\n const root = dialogRef.current;\n if (root === null) return [];\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE));\n }, []);\n\n useEffect(() => {\n if (!open) return;\n\n const previous = activeElementNear(dialogRef.current);\n focusables()[0]?.focus();\n\n return () => {\n if (previous instanceof HTMLElement) previous.focus();\n };\n }, [open, focusables]);\n\n useEffect(() => {\n const portal = tap.__portal;\n if (!open || portal === null) return;\n\n const close = (): void => {\n onClose();\n };\n\n portal.addEventListener(DIALOG_CLOSE_EVENT, close);\n return () => {\n portal.removeEventListener(DIALOG_CLOSE_EVENT, close);\n };\n }, [open, tap, onClose]);\n\n if (!open) return null;\n\n if (tap.__portal === null) {\n throw new TapError(\n 'TAP_NO_PORTAL',\n 'TapDialog needs the host portal node, which the mount sets inside its shadow root. Render this dialog from a contribution entry, or use renderWithTap() in tests.',\n );\n }\n\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === 'Escape') {\n event.stopPropagation();\n event.preventDefault();\n onClose();\n return;\n }\n\n if (event.key !== 'Tab') return;\n\n const items = focusables();\n if (items.length === 0) return;\n\n const first = items[0];\n const last = items[items.length - 1];\n const active = activeElementNear(dialogRef.current);\n\n if (event.shiftKey && active === first) {\n event.preventDefault();\n last.focus();\n return;\n }\n\n if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n };\n\n return createPortal(\n <div\n className=\"tap-dialog-scrim\"\n onKeyDown={onKeyDown}\n onMouseDown={event => {\n if (event.target === event.currentTarget) onClose();\n }}\n >\n <div\n ref={dialogRef}\n className=\"tap-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={labelledBy ?? titleId}\n >\n <header className=\"tap-dialog__head\" id={titleId}>\n {title}\n </header>\n <div className=\"tap-dialog__body\">{children}</div>\n {footer !== undefined && (\n <footer className=\"tap-dialog__foot\">{footer}</footer>\n )}\n </div>\n </div>,\n tap.__portal,\n );\n}\n","import {\n useMemo,\n useState,\n type ReactElement,\n type SubmitEventHandler,\n} from 'react';\nimport { z } from 'zod';\nimport type { JsonSchema } from '../../manifest/types';\nimport { TapButton, TapInput, TapSelect } from './primitives';\n\n/** The control a schema property maps to. */\nexport type TapFormFieldKind =\n | 'text'\n | 'textarea'\n | 'date'\n | 'number'\n | 'boolean'\n | 'enum';\n\n/** One control `TapForm` renders, as `formFields` reads it off a schema. */\nexport interface TapFormField {\n /** The property name in the schema, and the key in the submitted value. */\n name: string;\n /** The property's `title`, or the name spaced and capitalised. */\n label: string;\n kind: TapFormFieldKind;\n /** Whether the schema lists the property under `required`. */\n required: boolean;\n /** The property's `description`. */\n hint?: string;\n /** Set for an `enum` property: one option per string value. */\n options?: { value: string; label: string }[];\n}\n\nfunction humanise(name: string): string {\n const spaced = name\n .replace(/[_-]+/g, ' ')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .trim();\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n\nfunction readString(\n source: Record<string, unknown>,\n key: string,\n): string | undefined {\n const value = source[key];\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction kindOf(property: Record<string, unknown>): TapFormFieldKind {\n if (Array.isArray(property.enum)) return 'enum';\n\n const type = readString(property, 'type');\n if (type === 'boolean') return 'boolean';\n if (type === 'number' || type === 'integer') return 'number';\n\n const format = readString(property, 'format');\n if (format === 'textarea') return 'textarea';\n if (format === 'date') return 'date';\n return 'text';\n}\n\n/**\n * Reads a JSON Schema object and returns the list of controls `TapForm` renders\n * from it.\n *\n * @remarks\n * One field per declared property, in the order the schema lists them; a schema\n * with no `properties` object returns an empty list, and a property that is not an\n * object is skipped. An `enum` becomes a select and keeps only its string values;\n * otherwise the kind comes from `type` — `boolean`, `number` and `integer` — and\n * then from `format`, where `textarea` and `date` are recognised and anything else\n * falls back to a single-line text control. Nested objects and arrays get a text\n * control, so render those yourself. Call it when you want the fields without the\n * form.\n *\n * @example\n * ```ts\n * import { formFields } from '@tappify/extension-sdk';\n *\n * const fields = formFields({\n * type: 'object',\n * properties: { refreshMinutes: { type: 'integer', title: 'Refresh minutes' } },\n * required: ['refreshMinutes'],\n * });\n * const names = fields.map(field => field.name);\n * ```\n */\nexport function formFields(schema: JsonSchema): TapFormField[] {\n const properties = schema.properties;\n if (typeof properties !== 'object' || properties === null) return [];\n\n const required = new Set(\n Array.isArray(schema.required)\n ? schema.required.filter(\n (name): name is string => typeof name === 'string',\n )\n : [],\n );\n\n return Object.entries(properties).flatMap(([name, raw]) => {\n if (typeof raw !== 'object' || raw === null) return [];\n const property: Record<string, unknown> = { ...raw };\n const kind = kindOf(property);\n\n const field: TapFormField = {\n name,\n label: readString(property, 'title') ?? humanise(name),\n kind,\n required: required.has(name),\n };\n\n const hint = readString(property, 'description');\n if (hint !== undefined) field.hint = hint;\n\n if (kind === 'enum' && Array.isArray(property.enum)) {\n field.options = property.enum\n .filter((option): option is string => typeof option === 'string')\n .map(option => ({ value: option, label: option }));\n }\n\n return [field];\n });\n}\n\ntype Values = Record<string, unknown>;\n\nfunction requiredErrors(\n fields: TapFormField[],\n values: Values,\n): Record<string, string> {\n const errors: Record<string, string> = {};\n for (const field of fields) {\n const value = values[field.name];\n if (field.required && (value === undefined || value === '')) {\n errors[field.name] = `${field.label} is required.`;\n }\n }\n return errors;\n}\n\nfunction compile(schema: JsonSchema): z.ZodType | null {\n try {\n return z.fromJSONSchema(schema);\n } catch {\n return null;\n }\n}\n\nfunction validate(\n fields: TapFormField[],\n compiled: z.ZodType | null,\n values: Values,\n): Record<string, string> {\n const errors = requiredErrors(fields, values);\n if (compiled === null) return errors;\n\n const result = compiled.safeParse(values);\n if (result.success) return errors;\n\n for (const issue of result.error.issues) {\n const key = issue.path[0];\n if (typeof key === 'string' && errors[key] === undefined) {\n errors[key] = issue.message;\n }\n }\n return errors;\n}\n\nfunction clean(fields: TapFormField[], values: Values): Values {\n const output: Values = {};\n for (const field of fields) {\n const value = values[field.name];\n if (value === undefined || value === '') continue;\n output[field.name] = value;\n }\n return output;\n}\n\n/** The props `TapForm` takes. */\nexport interface TapFormProps<T> {\n /** The object schema to render, one control per declared property. */\n schema: JsonSchema;\n /** The value the fields start at; later changes to it do not reset the form. */\n value?: Partial<T>;\n /** Defaults to `Submit`. */\n submitLabel?: string;\n /** Defaults to `Cancel`. */\n cancelLabel?: string;\n /** Marks the submit button busy and disables it while a submit is in flight. */\n busy?: boolean;\n /** Called with the cleaned value once validation passes. */\n onSubmit: (value: T) => void | Promise<void>;\n /** Renders a cancel button next to submit, and is called by it. */\n onCancel?: () => void;\n}\n\n/**\n * Renders a JSON Schema as a form in the host's styling, validates it on submit\n * and hands back the value.\n *\n * @remarks\n * Point it at the same schema file the manifest references, so the form and the\n * handler change together. Submit drops the fields left blank, then checks what\n * remains against the whole schema, so a `minimum`, `pattern` or `maxLength`\n * lands under the control it belongs to; a required field left empty is reported\n * without reaching the schema. A schema this SDK's Zod version cannot compile\n * still renders and still enforces required fields. `onSubmit` runs only when\n * nothing failed, and the form holds its own values, so a change to `value` after\n * the first render does not reset it. A form with no `value` has nothing to infer\n * `T` from, so annotate the handler's parameter.\n *\n * @example\n * ```tsx\n * import { TapForm, type TapSettingsProps } from '@tappify/extension-sdk';\n *\n * const schema = {\n * type: 'object',\n * properties: { refreshMinutes: { type: 'integer', title: 'Refresh minutes' } },\n * required: ['refreshMinutes'],\n * };\n *\n * function Settings({ values, onChange }: TapSettingsProps) {\n * return (\n * <TapForm\n * schema={schema}\n * value={values}\n * submitLabel=\"Save\"\n * onSubmit={next => onChange(next)}\n * />\n * );\n * }\n * ```\n */\nexport function TapForm<T>({\n schema,\n value,\n submitLabel = 'Submit',\n cancelLabel = 'Cancel',\n busy = false,\n onSubmit,\n onCancel,\n}: TapFormProps<T>): ReactElement {\n const fields = useMemo(() => formFields(schema), [schema]);\n const compiled = useMemo(() => compile(schema), [schema]);\n const [values, setValues] = useState<Values>(() => ({ ...value }));\n const [errors, setErrors] = useState<Record<string, string>>({});\n\n const set = (name: string, next: unknown): void => {\n setValues(current => ({ ...current, [name]: next }));\n setErrors(current => {\n if (current[name] === undefined) return current;\n const { [name]: _cleared, ...rest } = current;\n return rest;\n });\n };\n\n const submit: SubmitEventHandler<HTMLFormElement> = event => {\n event.preventDefault();\n const candidate = clean(fields, values);\n const found = validate(fields, compiled, candidate);\n setErrors(found);\n if (Object.keys(found).length > 0) return;\n\n void onSubmit(candidate as T);\n };\n\n return (\n <form className=\"tap-form\" onSubmit={submit} noValidate>\n {fields.map(field => {\n const raw = values[field.name];\n\n if (field.kind === 'enum') {\n return (\n <TapSelect\n key={field.name}\n label={field.label}\n hint={field.hint}\n error={errors[field.name]}\n required={field.required}\n value={typeof raw === 'string' ? raw : ''}\n options={[\n { value: '', label: 'Choose one' },\n ...(field.options ?? []),\n ]}\n onChange={event => {\n set(field.name, event.target.value);\n }}\n />\n );\n }\n\n if (field.kind === 'boolean') {\n return (\n <TapInput\n key={field.name}\n type=\"checkbox\"\n label={field.label}\n hint={field.hint}\n error={errors[field.name]}\n className=\"tap-control--checkbox\"\n checked={raw === true}\n onChange={event => {\n set(field.name, event.target.checked);\n }}\n />\n );\n }\n\n if (field.kind === 'textarea') {\n return (\n <TapInput\n key={field.name}\n multiline\n label={field.label}\n hint={field.hint}\n error={errors[field.name]}\n required={field.required}\n value={typeof raw === 'string' ? raw : ''}\n onChange={event => {\n set(field.name, event.target.value);\n }}\n />\n );\n }\n\n return (\n <TapInput\n key={field.name}\n type={\n field.kind === 'number'\n ? 'number'\n : field.kind === 'date'\n ? 'date'\n : 'text'\n }\n label={field.label}\n hint={field.hint}\n error={errors[field.name]}\n required={field.required}\n value={\n typeof raw === 'string' || typeof raw === 'number'\n ? String(raw)\n : ''\n }\n onChange={event => {\n set(\n field.name,\n field.kind === 'number'\n ? event.target.value === ''\n ? ''\n : Number(event.target.value)\n : event.target.value,\n );\n }}\n />\n );\n })}\n\n <div className=\"tap-form__actions\">\n {onCancel !== undefined && (\n <TapButton variant=\"secondary\" onClick={onCancel}>\n {cancelLabel}\n </TapButton>\n )}\n <TapButton type=\"submit\" loading={busy}>\n {submitLabel}\n </TapButton>\n </div>\n </form>\n );\n}\n"],"mappings":"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","names":["_multiline","rest","useId","jsx","jsxs","useId","jsx","jsxs"]}
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/manifest.cjs
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
var _chunkNN263PGQcjs = require('./chunk-NN263PGQ.cjs');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
6
10
|
|
|
7
11
|
|
|
8
12
|
|
|
@@ -17,70 +21,10 @@
|
|
|
17
21
|
|
|
18
22
|
|
|
19
23
|
|
|
20
|
-
var _chunkPYJMJF44cjs = require('./chunk-PYJMJF44.cjs');
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
var
|
|
24
|
-
|
|
25
|
-
tab: "tabs",
|
|
26
|
-
widget: "widgets",
|
|
27
|
-
rowAction: "rowActions"
|
|
28
|
-
};
|
|
29
|
-
function defineManifest(manifest) {
|
|
30
|
-
return manifest;
|
|
31
|
-
}
|
|
32
|
-
function exposeName(kind, id) {
|
|
33
|
-
if (kind === "settings") return "./settings";
|
|
34
|
-
return `./${EXPOSE_FOLDER[kind]}/${_nullishCoalesce(id, () => ( ""))}`;
|
|
35
|
-
}
|
|
36
|
-
function remoteName(extensionId) {
|
|
37
|
-
return `ext_${extensionId.replace(/-/g, "_")}`;
|
|
38
|
-
}
|
|
39
|
-
function uiContributions(manifest) {
|
|
40
|
-
const contributes = manifest.contributes;
|
|
41
|
-
const refs = [];
|
|
42
|
-
for (const page of _nullishCoalesce(contributes.pages, () => ( []))) {
|
|
43
|
-
refs.push({
|
|
44
|
-
kind: "page",
|
|
45
|
-
id: page.id,
|
|
46
|
-
entry: page.entry,
|
|
47
|
-
expose: exposeName("page", page.id)
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
for (const tab of _nullishCoalesce(contributes.tabs, () => ( []))) {
|
|
51
|
-
refs.push({
|
|
52
|
-
kind: "tab",
|
|
53
|
-
id: tab.id,
|
|
54
|
-
entry: tab.entry,
|
|
55
|
-
expose: exposeName("tab", tab.id)
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
for (const widget of _nullishCoalesce(contributes.widgets, () => ( []))) {
|
|
59
|
-
refs.push({
|
|
60
|
-
kind: "widget",
|
|
61
|
-
id: widget.id,
|
|
62
|
-
entry: widget.entry,
|
|
63
|
-
expose: exposeName("widget", widget.id)
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
for (const rowAction of _nullishCoalesce(contributes.rowActions, () => ( []))) {
|
|
67
|
-
refs.push({
|
|
68
|
-
kind: "rowAction",
|
|
69
|
-
id: rowAction.id,
|
|
70
|
-
entry: rowAction.entry,
|
|
71
|
-
expose: exposeName("rowAction", rowAction.id)
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
if (contributes.settings) {
|
|
75
|
-
refs.push({
|
|
76
|
-
kind: "settings",
|
|
77
|
-
id: "settings",
|
|
78
|
-
entry: contributes.settings.entry,
|
|
79
|
-
expose: exposeName("settings")
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
return refs;
|
|
83
|
-
}
|
|
25
|
+
|
|
26
|
+
var _chunk7YKH4LZNcjs = require('./chunk-7YKH4LZN.cjs');
|
|
27
|
+
require('./chunk-Q7SFCCGT.cjs');
|
|
84
28
|
|
|
85
29
|
// src/manifest/schema.ts
|
|
86
30
|
var _zod = require('zod');
|
|
@@ -140,7 +84,7 @@ var contributionId = _zod.z.string().regex(CONTRIBUTION_ID).describe(
|
|
|
140
84
|
);
|
|
141
85
|
var title = _zod.z.string().min(1).max(60).describe("The label owners read on this contribution.");
|
|
142
86
|
var scopeEntry = _zod.z.object({
|
|
143
|
-
key: _zod.z.enum(
|
|
87
|
+
key: _zod.z.enum(_chunk7YKH4LZNcjs.SCOPE_KEYS, {
|
|
144
88
|
error: "Unknown scope key; see the Scopes reference."
|
|
145
89
|
}).describe("The scope key, from the scopes reference."),
|
|
146
90
|
justification: _zod.z.string().min(20).max(500).describe(
|
|
@@ -150,7 +94,7 @@ var scopeEntry = _zod.z.object({
|
|
|
150
94
|
var mobile = _zod.z.boolean().describe(
|
|
151
95
|
"Whether this surface is drawn on phone layouts. Defaults to true; set false to hide it there."
|
|
152
96
|
).optional();
|
|
153
|
-
var hostPage = (what) => _zod.z.enum(
|
|
97
|
+
var hostPage = (what) => _zod.z.enum(_chunk7YKH4LZNcjs.PAGE_KEYS).describe(what);
|
|
154
98
|
var pageContribution = _zod.z.object({
|
|
155
99
|
id: contributionId,
|
|
156
100
|
title,
|
|
@@ -172,7 +116,7 @@ var widgetContribution = _zod.z.object({
|
|
|
172
116
|
title,
|
|
173
117
|
entry: entryPath,
|
|
174
118
|
page: hostPage("The host page this widget renders on."),
|
|
175
|
-
slot: _zod.z.enum(
|
|
119
|
+
slot: _zod.z.enum(_chunk7YKH4LZNcjs.SLOTS).describe(
|
|
176
120
|
"The named anchor on that page. A slot that is not on the page fails validation."
|
|
177
121
|
),
|
|
178
122
|
size: _zod.z.enum(["1x1", "2x1", "2x2"]).describe(
|
|
@@ -185,7 +129,7 @@ var widgetContribution = _zod.z.object({
|
|
|
185
129
|
var seriesContribution = _zod.z.object({
|
|
186
130
|
id: contributionId,
|
|
187
131
|
label: title,
|
|
188
|
-
chart: _zod.z.enum(
|
|
132
|
+
chart: _zod.z.enum(_chunk7YKH4LZNcjs.CHARTS).describe("The host chart this line is drawn on."),
|
|
189
133
|
metric: _zod.z.string().min(1).max(60).describe(
|
|
190
134
|
"The connector metric this line plots. It has to be one the connector declares."
|
|
191
135
|
)
|
|
@@ -200,13 +144,13 @@ var bannerContribution = _zod.z.object({
|
|
|
200
144
|
var rowActionContribution = _zod.z.object({
|
|
201
145
|
id: contributionId,
|
|
202
146
|
title,
|
|
203
|
-
table: _zod.z.enum(
|
|
147
|
+
table: _zod.z.enum(_chunk7YKH4LZNcjs.TABLE_KEYS).describe("The host table whose row menu gains this entry."),
|
|
204
148
|
entry: entryPath
|
|
205
149
|
}).strict();
|
|
206
150
|
var markerContribution = _zod.z.object({
|
|
207
151
|
id: contributionId,
|
|
208
152
|
label: title,
|
|
209
|
-
chart: _zod.z.union([_zod.z.enum(
|
|
153
|
+
chart: _zod.z.union([_zod.z.enum(_chunk7YKH4LZNcjs.CHARTS), _zod.z.literal("*")]).describe(
|
|
210
154
|
"The host chart this marker is drawn on, or `*` for every chart."
|
|
211
155
|
),
|
|
212
156
|
event: _zod.z.string().regex(DOMAIN_EVENT).describe(
|
|
@@ -254,7 +198,10 @@ var connectorContribution = _zod.z.object({
|
|
|
254
198
|
),
|
|
255
199
|
scopes: _zod.z.array(_zod.z.string()).describe(
|
|
256
200
|
"The scopes asked of the outside service. These are that service's scopes, not Tappify's."
|
|
257
|
-
)
|
|
201
|
+
),
|
|
202
|
+
tokenRequestEncoding: _zod.z.enum(["form", "json"]).describe(
|
|
203
|
+
"The request body encoding for authorization-code and refresh token exchanges. When omitted, uses form encoding (application/x-www-form-urlencoded); json uses application/json."
|
|
204
|
+
).optional()
|
|
258
205
|
}).strict().describe('The endpoints, for `method: "oauth"`.').optional()
|
|
259
206
|
}).strict().describe("How owners authenticate this connector.").optional(),
|
|
260
207
|
sync: _zod.z.enum(["5m", "15m", "1h", "6h", "24h"]).describe("How often Tappify asks your server to sync.").optional(),
|
|
@@ -332,7 +279,7 @@ var aiContribution = _zod.z.object({
|
|
|
332
279
|
"Always true. An action goes through an owner approval; there is no way to opt out."
|
|
333
280
|
),
|
|
334
281
|
reversible: _zod.z.boolean().describe("Whether the owner can undo it after it runs."),
|
|
335
|
-
scope: _zod.z.enum(
|
|
282
|
+
scope: _zod.z.enum(_chunk7YKH4LZNcjs.SCOPE_KEYS).describe(
|
|
336
283
|
"The scope this action needs. Declare it under `scopes` as well."
|
|
337
284
|
),
|
|
338
285
|
estimatesCost: _zod.z.boolean().describe(
|
|
@@ -398,7 +345,7 @@ var aiContribution = _zod.z.object({
|
|
|
398
345
|
).optional()
|
|
399
346
|
}).strict();
|
|
400
347
|
var statusNames = (stage) => _zod.z.array(_zod.z.string()).describe(`Your own status names that mean ${stage}.`);
|
|
401
|
-
var
|
|
348
|
+
var legacyWorkContribution = _zod.z.object({
|
|
402
349
|
kind: _zod.z.enum(["tracker", "docs", "chat"]).describe("What kind of destination this is."),
|
|
403
350
|
container: _zod.z.object({
|
|
404
351
|
label: _zod.z.string().min(1).max(60).describe(
|
|
@@ -433,6 +380,55 @@ var workContribution = _zod.z.object({
|
|
|
433
380
|
"Whether items are created as the owner who asked (`user`) or as the install itself."
|
|
434
381
|
)
|
|
435
382
|
}).strict();
|
|
383
|
+
var workDirection = _zod.z.enum([
|
|
384
|
+
"none",
|
|
385
|
+
"to_provider",
|
|
386
|
+
"from_provider",
|
|
387
|
+
"two-way"
|
|
388
|
+
]);
|
|
389
|
+
var workAuth = _zod.z.discriminatedUnion("method", [
|
|
390
|
+
_zod.z.object({
|
|
391
|
+
method: _zod.z.literal("oauth"),
|
|
392
|
+
provider: _zod.z.string().min(1).describe("The OAuth provider identifier."),
|
|
393
|
+
scopes: _zod.z.array(_zod.z.string()).describe("The scopes requested from the provider.")
|
|
394
|
+
}).strict(),
|
|
395
|
+
_zod.z.object({
|
|
396
|
+
method: _zod.z.literal("api_key"),
|
|
397
|
+
fields: _zod.z.array(credentialField).max(20)
|
|
398
|
+
}).strict()
|
|
399
|
+
]);
|
|
400
|
+
var currentWorkContribution = legacyWorkContribution.extend({
|
|
401
|
+
container: legacyWorkContribution.shape.container.describe(
|
|
402
|
+
"Deprecated. Resolve containers dynamically with describe_container."
|
|
403
|
+
).optional(),
|
|
404
|
+
fields: legacyWorkContribution.shape.fields.describe("Deprecated. Resolve fields dynamically with describe_container.").optional(),
|
|
405
|
+
comments: _zod.z.union([
|
|
406
|
+
_zod.z.enum(["two-way", "none"]).describe("Deprecated. Declare a comments capability object."),
|
|
407
|
+
_zod.z.object({
|
|
408
|
+
direction: workDirection,
|
|
409
|
+
supportsEdits: _zod.z.literal(false),
|
|
410
|
+
supportsDeletes: _zod.z.literal(false)
|
|
411
|
+
}).strict()
|
|
412
|
+
]),
|
|
413
|
+
status: _zod.z.object({
|
|
414
|
+
direction: workDirection,
|
|
415
|
+
webhook: _zod.z.string().regex(DOMAIN_EVENT).optional(),
|
|
416
|
+
map: _zod.z.object({
|
|
417
|
+
open: statusNames("not started"),
|
|
418
|
+
in_progress: statusNames("under way"),
|
|
419
|
+
done: statusNames("finished")
|
|
420
|
+
}).strict().describe(
|
|
421
|
+
"Deprecated. Resolve statuses dynamically with describe_container."
|
|
422
|
+
).optional()
|
|
423
|
+
}).strict().optional(),
|
|
424
|
+
auth: workAuth.describe("How owners authenticate the work destination.")
|
|
425
|
+
});
|
|
426
|
+
var workContribution = _zod.z.union([
|
|
427
|
+
currentWorkContribution,
|
|
428
|
+
legacyWorkContribution.describe(
|
|
429
|
+
"Deprecated SDK v2 work declaration. Migrate to work capabilities and auth."
|
|
430
|
+
)
|
|
431
|
+
]);
|
|
436
432
|
var listing = _zod.z.object({
|
|
437
433
|
longDescription: _zod.z.string().min(1).max(5e3).describe("The body of your listing page, in markdown.").optional(),
|
|
438
434
|
screenshots: _zod.z.array(_zod.z.string().min(1)).min(1, "A listing shows 1 to 6 screenshots.").max(6, "A listing shows 1 to 6 screenshots.").describe(
|
|
@@ -479,7 +475,7 @@ var manifestObject = _zod.z.object({
|
|
|
479
475
|
icon: _zod.z.string().min(1).max(200).describe(
|
|
480
476
|
"The icon file carried in the bundle, as a path relative to the manifest. Square png or svg, at most 512 KB."
|
|
481
477
|
),
|
|
482
|
-
category: _zod.z.enum(
|
|
478
|
+
category: _zod.z.enum(_chunk7YKH4LZNcjs.CATEGORIES, {
|
|
483
479
|
error: "Choose a category from the Tap Store list (see the Categories reference)."
|
|
484
480
|
}).describe("The Tap Store section owners browse and filter by."),
|
|
485
481
|
sdk: _zod.z.string().min(1).max(60).describe(
|
|
@@ -491,7 +487,7 @@ var manifestObject = _zod.z.object({
|
|
|
491
487
|
installScope: _zod.z.enum(["project", "organization"]).describe(
|
|
492
488
|
"Whether one install belongs to a single project or to a whole organization. An organization install contributes no pages, tabs, widgets or series, because those render on a project."
|
|
493
489
|
),
|
|
494
|
-
scopes: _zod.z.array(scopeEntry).min(1).max(
|
|
490
|
+
scopes: _zod.z.array(scopeEntry).min(1).max(_chunk7YKH4LZNcjs.SCOPE_KEYS.length).describe(
|
|
495
491
|
"The permissions an owner grants at install. Declare only what your contributions use."
|
|
496
492
|
),
|
|
497
493
|
contributes: _zod.z.object({
|
|
@@ -524,7 +520,7 @@ var manifestObject = _zod.z.object({
|
|
|
524
520
|
"What this extension offers the assistant: tools, mentions, actions, knowledge, skills, prompts and context providers."
|
|
525
521
|
).optional(),
|
|
526
522
|
work: workContribution.describe(
|
|
527
|
-
"The work destination this extension creates issues, pages or messages in. Needs
|
|
523
|
+
"The work destination this extension creates issues, pages or messages in. Needs `review.threads:read` and `work:create`; active comment or status sync also needs `work:sync`."
|
|
528
524
|
).optional()
|
|
529
525
|
}).strict().describe(
|
|
530
526
|
"What this extension adds to the host. At most 20 UI contributions in total, and a page, tab, widget, row action, marker or settings panel needs the `ui:render` scope."
|
|
@@ -546,7 +542,7 @@ var manifestObject = _zod.z.object({
|
|
|
546
542
|
"The procedures the host may call on your server, by name, each with its input and output schema. At most 30."
|
|
547
543
|
).optional(),
|
|
548
544
|
events: _zod.z.array(
|
|
549
|
-
_zod.z.enum(
|
|
545
|
+
_zod.z.enum(_chunk7YKH4LZNcjs.EVENTS, {
|
|
550
546
|
error: (issue) => `"${String(issue.input)}" is not a Tappify event. Subscribe to a name from the event catalogue.`
|
|
551
547
|
})
|
|
552
548
|
).describe(
|
|
@@ -602,7 +598,7 @@ var manifestSchema = manifestObject.superRefine((manifest, ctx) => {
|
|
|
602
598
|
});
|
|
603
599
|
};
|
|
604
600
|
for (const [index, widget] of (_nullishCoalesce(contributes.widgets, () => ( []))).entries()) {
|
|
605
|
-
const slots =
|
|
601
|
+
const slots = _chunk7YKH4LZNcjs.slotsForPage.call(void 0, widget.page);
|
|
606
602
|
if (!slots.includes(widget.slot)) {
|
|
607
603
|
ctx.addIssue({
|
|
608
604
|
code: "custom",
|
|
@@ -745,16 +741,36 @@ var manifestSchema = manifestObject.superRefine((manifest, ctx) => {
|
|
|
745
741
|
}
|
|
746
742
|
}
|
|
747
743
|
if (contributes.work) {
|
|
744
|
+
const work = contributes.work;
|
|
745
|
+
const legacy = isLegacyWork(work);
|
|
746
|
+
if (!legacy || !granted.has("reviews:read")) {
|
|
747
|
+
requireScope(
|
|
748
|
+
"review.threads:read",
|
|
749
|
+
["scopes"],
|
|
750
|
+
"the work destination reads Review threads."
|
|
751
|
+
);
|
|
752
|
+
}
|
|
748
753
|
requireScope(
|
|
749
754
|
"work:create",
|
|
750
755
|
["scopes"],
|
|
751
756
|
"the work destination creates items."
|
|
752
757
|
);
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
+
const comments = typeof work.comments === "string" ? work.comments : work.comments.direction;
|
|
759
|
+
const status = work.status ? "direction" in work.status ? work.status.direction : "from_provider" : "none";
|
|
760
|
+
if (comments !== "none" || status !== "none") {
|
|
761
|
+
requireScope(
|
|
762
|
+
"work:sync",
|
|
763
|
+
["scopes"],
|
|
764
|
+
"the work destination syncs comments or status."
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
if (work.identity === "user") {
|
|
768
|
+
ctx.addIssue({
|
|
769
|
+
code: "custom",
|
|
770
|
+
path: ["contributes", "work", "identity"],
|
|
771
|
+
message: 'Per-user work identity is not supported. Set identity to "install".'
|
|
772
|
+
});
|
|
773
|
+
}
|
|
758
774
|
}
|
|
759
775
|
const needsServer = contributes.connector !== void 0 || (_nullishCoalesce(_optionalChain([contributes, 'access', _46 => _46.webhooks, 'optionalAccess', _47 => _47.length]), () => ( 0))) > 0 || ai !== void 0 || contributes.work !== void 0 || _optionalChain([manifest, 'access', _48 => _48.server, 'optionalAccess', _49 => _49.procedures]) !== void 0;
|
|
760
776
|
if (needsServer && !_optionalChain([manifest, 'access', _50 => _50.server, 'optionalAccess', _51 => _51.baseUrl])) {
|
|
@@ -773,7 +789,7 @@ var manifestSchema = manifestObject.superRefine((manifest, ctx) => {
|
|
|
773
789
|
});
|
|
774
790
|
}
|
|
775
791
|
for (const scope of manifest.scopes) {
|
|
776
|
-
const definition =
|
|
792
|
+
const definition = _chunk7YKH4LZNcjs.findScope.call(void 0, scope.key);
|
|
777
793
|
if (_optionalChain([definition, 'optionalAccess', _54 => _54.requiresJustification]) && !scope.justification) {
|
|
778
794
|
ctx.addIssue({
|
|
779
795
|
code: "custom",
|
|
@@ -806,11 +822,11 @@ var manifestSchema = manifestObject.superRefine((manifest, ctx) => {
|
|
|
806
822
|
});
|
|
807
823
|
}
|
|
808
824
|
}
|
|
809
|
-
if (!sdkMajors(manifest.sdk).some((major) =>
|
|
825
|
+
if (!sdkMajors(manifest.sdk).some((major) => _chunk7YKH4LZNcjs.SERVED_SDK_MAJORS.includes(major))) {
|
|
810
826
|
ctx.addIssue({
|
|
811
827
|
code: "custom",
|
|
812
828
|
path: ["sdk"],
|
|
813
|
-
message: `The host serves SDK major ${
|
|
829
|
+
message: `The host serves SDK major ${_chunk7YKH4LZNcjs.SERVED_SDK_MAJORS.join(", ")}. Set sdk to a range that includes it, such as "^2.0.0".`
|
|
814
830
|
});
|
|
815
831
|
}
|
|
816
832
|
});
|
|
@@ -841,6 +857,46 @@ function validateManifest(value) {
|
|
|
841
857
|
}));
|
|
842
858
|
return { ok: false, issues };
|
|
843
859
|
}
|
|
860
|
+
function isLegacyWork(work) {
|
|
861
|
+
return !("auth" in work);
|
|
862
|
+
}
|
|
863
|
+
function workManifestWarnings(manifest) {
|
|
864
|
+
const work = manifest.contributes.work;
|
|
865
|
+
if (!work) return [];
|
|
866
|
+
const warnings = [];
|
|
867
|
+
if (work.container) {
|
|
868
|
+
warnings.push({
|
|
869
|
+
path: "contributes.work.container",
|
|
870
|
+
message: "Static work containers are deprecated. Resolve containers dynamically with describe_container."
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
if (work.fields) {
|
|
874
|
+
warnings.push({
|
|
875
|
+
path: "contributes.work.fields",
|
|
876
|
+
message: "Static work fields are deprecated. Resolve fields dynamically with describe_container."
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
if (typeof work.comments === "string") {
|
|
880
|
+
warnings.push({
|
|
881
|
+
path: "contributes.work.comments",
|
|
882
|
+
message: "String work comments are deprecated. Declare direction, supportsEdits: false and supportsDeletes: false."
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
if (_optionalChain([work, 'access', _62 => _62.status, 'optionalAccess', _63 => _63.map])) {
|
|
886
|
+
warnings.push({
|
|
887
|
+
path: "contributes.work.status.map",
|
|
888
|
+
message: "Static work status maps are deprecated. Resolve statuses dynamically with describe_container."
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
if (isLegacyWork(work) && manifest.scopes.some((scope) => scope.key === "reviews:read") && !manifest.scopes.some((scope) => scope.key === "review.threads:read")) {
|
|
892
|
+
warnings.push({
|
|
893
|
+
path: "scopes",
|
|
894
|
+
message: "Using reviews:read for work is deprecated. Add review.threads:read for Review threads; reviews:read grants store-review access."
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
return warnings;
|
|
898
|
+
}
|
|
899
|
+
|
|
844
900
|
|
|
845
901
|
|
|
846
902
|
|
|
@@ -868,5 +924,5 @@ function validateManifest(value) {
|
|
|
868
924
|
|
|
869
925
|
|
|
870
926
|
|
|
871
|
-
exports.CARD_KEYS =
|
|
927
|
+
exports.CARD_KEYS = _chunk7YKH4LZNcjs.CARD_KEYS; exports.CATEGORIES = _chunk7YKH4LZNcjs.CATEGORIES; exports.CHARTS = _chunk7YKH4LZNcjs.CHARTS; exports.EVENTS = _chunk7YKH4LZNcjs.EVENTS; exports.KINDS = _chunk7YKH4LZNcjs.KINDS; exports.PAGES = _chunk7YKH4LZNcjs.PAGES; exports.PAGE_KEYS = _chunk7YKH4LZNcjs.PAGE_KEYS; exports.QUERY_SCOPES = _chunk7YKH4LZNcjs.QUERY_SCOPES; exports.SCOPES = _chunk7YKH4LZNcjs.SCOPES; exports.SCOPE_KEYS = _chunk7YKH4LZNcjs.SCOPE_KEYS; exports.SERVED_SDK_MAJORS = _chunk7YKH4LZNcjs.SERVED_SDK_MAJORS; exports.SLOTS = _chunk7YKH4LZNcjs.SLOTS; exports.TABLES = _chunk7YKH4LZNcjs.TABLES; exports.TABLE_KEYS = _chunk7YKH4LZNcjs.TABLE_KEYS; exports.defineManifest = _chunkNN263PGQcjs.defineManifest; exports.exposeName = _chunkNN263PGQcjs.exposeName; exports.findPage = _chunk7YKH4LZNcjs.findPage; exports.findScope = _chunk7YKH4LZNcjs.findScope; exports.findTable = _chunk7YKH4LZNcjs.findTable; exports.manifestJsonSchema = manifestJsonSchema; exports.manifestSchema = manifestSchema; exports.remoteName = _chunkNN263PGQcjs.remoteName; exports.sdkMajors = sdkMajors; exports.slotsForPage = _chunk7YKH4LZNcjs.slotsForPage; exports.uiContributions = _chunkNN263PGQcjs.uiContributions; exports.validateManifest = validateManifest; exports.workManifestWarnings = workManifestWarnings;
|
|
872
928
|
//# sourceMappingURL=manifest.cjs.map
|