@databricks/appkit-ui 0.61.1 → 0.62.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/CLAUDE.md +1 -0
- package/dist/cli/commands/codemod/index.js.map +1 -1
- package/dist/cli/commands/codemod/on-plugins-ready.js.map +1 -1
- package/dist/cli/commands/docs.js.map +1 -1
- package/dist/cli/commands/doctor/bundle.js.map +1 -1
- package/dist/cli/commands/doctor/index.js.map +1 -1
- package/dist/cli/commands/doctor/report.js.map +1 -1
- package/dist/cli/commands/doctor/resolve-targets.js.map +1 -1
- package/dist/cli/commands/doctor/run.js.map +1 -1
- package/dist/cli/commands/generate-types.js.map +1 -1
- package/dist/cli/commands/lint.js.map +1 -1
- package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -1
- package/dist/cli/commands/plugin/create/create.js.map +1 -1
- package/dist/cli/commands/plugin/create/prompt-resource.js.map +1 -1
- package/dist/cli/commands/plugin/create/scaffold.js.map +1 -1
- package/dist/cli/commands/plugin/index.js.map +1 -1
- package/dist/cli/commands/plugin/list/list.js.map +1 -1
- package/dist/cli/commands/plugin/promote/promote.js.map +1 -1
- package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
- package/dist/cli/commands/plugin/validate/validate-manifest.js.map +1 -1
- package/dist/cli/commands/plugin/validate/validate.js.map +1 -1
- package/dist/cli/commands/registry/add.js.map +1 -1
- package/dist/cli/commands/registry/client.js.map +1 -1
- package/dist/cli/commands/registry/config-writer.js.map +1 -1
- package/dist/cli/commands/registry/env-reconcile.js.map +1 -1
- package/dist/cli/commands/registry/env-writer.js.map +1 -1
- package/dist/cli/commands/registry/index.js.map +1 -1
- package/dist/cli/commands/registry/info.js.map +1 -1
- package/dist/cli/commands/registry/list.js.map +1 -1
- package/dist/cli/commands/registry/requirements.js.map +1 -1
- package/dist/cli/commands/registry/server-register.js.map +1 -1
- package/dist/cli/commands/registry/workspace-picker.js.map +1 -1
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/js/arrow/arrow-client.d.ts.map +1 -1
- package/dist/js/arrow/arrow-client.js.map +1 -1
- package/dist/react/charts/area/index.d.ts.map +1 -1
- package/dist/react/charts/area/index.js.map +1 -1
- package/dist/react/charts/bar/index.d.ts.map +1 -1
- package/dist/react/charts/bar/index.js.map +1 -1
- package/dist/react/charts/base.d.ts.map +1 -1
- package/dist/react/charts/base.js +1 -1
- package/dist/react/charts/base.js.map +1 -1
- package/dist/react/charts/heatmap/index.d.ts.map +1 -1
- package/dist/react/charts/heatmap/index.js.map +1 -1
- package/dist/react/charts/line/index.d.ts.map +1 -1
- package/dist/react/charts/line/index.js.map +1 -1
- package/dist/react/charts/normalize.d.ts.map +1 -1
- package/dist/react/charts/normalize.js.map +1 -1
- package/dist/react/charts/pie/index.d.ts.map +1 -1
- package/dist/react/charts/pie/index.js.map +1 -1
- package/dist/react/charts/radar/index.d.ts.map +1 -1
- package/dist/react/charts/radar/index.js.map +1 -1
- package/dist/react/charts/scatter/index.d.ts.map +1 -1
- package/dist/react/charts/scatter/index.js.map +1 -1
- package/dist/react/charts/theme.d.ts.map +1 -1
- package/dist/react/charts/theme.js.map +1 -1
- package/dist/react/charts/wrapper.d.ts.map +1 -1
- package/dist/react/charts/wrapper.js.map +1 -1
- package/dist/react/file-browser/directory-list.d.ts.map +1 -1
- package/dist/react/file-browser/directory-list.js.map +1 -1
- package/dist/react/file-browser/file-breadcrumb.d.ts.map +1 -1
- package/dist/react/file-browser/file-breadcrumb.js.map +1 -1
- package/dist/react/file-browser/file-entry.d.ts.map +1 -1
- package/dist/react/file-browser/file-entry.js.map +1 -1
- package/dist/react/file-browser/file-preview-panel.d.ts.map +1 -1
- package/dist/react/file-browser/file-preview-panel.js.map +1 -1
- package/dist/react/file-browser/new-folder-input.d.ts.map +1 -1
- package/dist/react/file-browser/new-folder-input.js.map +1 -1
- package/dist/react/genie/genie-chat-input.d.ts.map +1 -1
- package/dist/react/genie/genie-chat-input.js.map +1 -1
- package/dist/react/genie/genie-chat-message-list.d.ts.map +1 -1
- package/dist/react/genie/genie-chat-message-list.js.map +1 -1
- package/dist/react/genie/genie-chat-message.d.ts.map +1 -1
- package/dist/react/genie/genie-chat-message.js.map +1 -1
- package/dist/react/genie/genie-query-visualization.d.ts.map +1 -1
- package/dist/react/genie/genie-query-visualization.js.map +1 -1
- package/dist/react/genie/use-genie-chat.d.ts.map +1 -1
- package/dist/react/genie/use-genie-chat.js.map +1 -1
- package/dist/react/hooks/types.d.ts.map +1 -1
- package/dist/react/hooks/use-agent-chat.d.ts.map +1 -1
- package/dist/react/hooks/use-agent-chat.js.map +1 -1
- package/dist/react/hooks/use-ai-search-query.d.ts.map +1 -1
- package/dist/react/hooks/use-ai-search-query.js.map +1 -1
- package/dist/react/hooks/use-analytics-query.d.ts.map +1 -1
- package/dist/react/hooks/use-analytics-query.js.map +1 -1
- package/dist/react/hooks/use-analytics-warehouse-status.js.map +1 -1
- package/dist/react/hooks/use-chart-data.d.ts.map +1 -1
- package/dist/react/hooks/use-chart-data.js.map +1 -1
- package/dist/react/hooks/use-metric-view.d.ts.map +1 -1
- package/dist/react/hooks/use-metric-view.js.map +1 -1
- package/dist/react/hooks/use-plugin-config.d.ts.map +1 -1
- package/dist/react/hooks/use-plugin-config.js.map +1 -1
- package/dist/react/hooks/use-serving-invoke.d.ts.map +1 -1
- package/dist/react/hooks/use-serving-invoke.js.map +1 -1
- package/dist/react/hooks/use-serving-stream.d.ts.map +1 -1
- package/dist/react/hooks/use-serving-stream.js.map +1 -1
- package/dist/react/resource-status-indicator.d.ts.map +1 -1
- package/dist/react/resource-status-indicator.js.map +1 -1
- package/dist/react/table/data-table.d.ts.map +1 -1
- package/dist/react/table/data-table.js.map +1 -1
- package/dist/react/table/table-wrapper.js.map +1 -1
- package/dist/react/ui-variants/variants.d.ts.map +1 -1
- package/dist/react/ui-variants/variants.js.map +1 -1
- package/dist/schemas/manifest.d.ts.map +1 -1
- package/dist/schemas/manifest.js.map +1 -1
- package/dist/schemas/metric-fqn.js.map +1 -1
- package/dist/shared/src/plugin.d.ts.map +1 -1
- package/dist/styles.css +0 -3
- package/dist/workspace-client/legacy.js.map +1 -1
- package/dist/workspace-client/types.d.ts.map +1 -1
- package/docs/plugins/testing.md +245 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chart-data.js","names":[],"sources":["../../../src/react/hooks/use-chart-data.ts"],"sourcesContent":["import type { Table } from \"apache-arrow\";\nimport { useMemo } from \"react\";\nimport type { ChartData, DataFormat } from \"../charts/types\";\nimport type { WarehouseStatus } from \"./types\";\nimport { useAnalyticsQuery } from \"./use-analytics-query\";\n\n/** Threshold for auto-selecting Arrow format (row count hint) */\nconst ARROW_THRESHOLD = 500;\n\n// ============================================================================\n// Hook Options & Result Types\n// ============================================================================\n\nexport interface UseChartDataOptions {\n /** Analytics query key */\n queryKey: string;\n /** Query parameters */\n parameters?: Record<string, unknown>;\n /**\n * Data format preference\n * - \"json_array\": Force JSON format\n * - \"arrow_stream\": Force Arrow format\n * - \"auto\": Auto-select based on heuristics\n * @default \"auto\"\n */\n format?: DataFormat;\n /** Transform data after fetching */\n transformer?: <T>(data: T) => T;\n}\n\nexport interface UseChartDataResult {\n /** The fetched data (Arrow Table or JSON array) */\n data: ChartData | null;\n /** Whether the data is in Arrow format */\n isArrow: boolean;\n /** Loading state */\n loading: boolean;\n /** Error message if any */\n error: string | null;\n /** Whether the data is empty */\n isEmpty: boolean;\n /**\n * Latest warehouse readiness status from SSE. Retains the last value\n * (including `RUNNING`) until the next `start()`; `null` only before\n * the first event. Use with `loading` to distinguish warehouse warm-up\n * from in-flight SQL fetch.\n */\n warehouseStatus: WarehouseStatus | null;\n}\n\n// ============================================================================\n// Format Resolution\n// ============================================================================\n\n/**\n * Resolves the data format based on hints and preferences\n */\nfunction resolveFormat(\n format: DataFormat,\n parameters?: Record<string, unknown>,\n): \"JSON_ARRAY\" | \"ARROW_STREAM\" {\n // Explicit format selection (legacy \"json\"/\"arrow\" accepted for back-compat\n // with appkit-ui < 0.33.0 — see DataFormat in ../charts/types.ts).\n if (format === \"json_array\" || format === \"json\") return \"JSON_ARRAY\";\n if (format === \"arrow_stream\" || format === \"arrow\") return \"ARROW_STREAM\";\n\n // Auto-selection heuristics\n if (format === \"auto\") {\n // Check for explicit hint in parameters\n if (parameters?._preferArrow === true) return \"ARROW_STREAM\";\n if (parameters?._preferJson === true) return \"JSON_ARRAY\";\n\n // Check limit parameter as data size hint\n const limit = parameters?.limit;\n if (typeof limit === \"number\" && limit > ARROW_THRESHOLD) {\n return \"ARROW_STREAM\";\n }\n\n // Check for date range queries (often large)\n if (parameters?.startDate && parameters?.endDate) {\n return \"ARROW_STREAM\";\n }\n\n return \"JSON_ARRAY\";\n }\n\n return \"JSON_ARRAY\";\n}\n\n// ============================================================================\n// Main Hook\n// ============================================================================\n\n/**\n * Hook for fetching chart data in either JSON or Arrow format.\n * Automatically selects the best format based on query hints.\n *\n * @example\n * ```tsx\n * // Auto-select format\n * const { data, isArrow, loading } = useChartData({\n * queryKey: \"spend_data\",\n * parameters: { limit: 1000 }\n * });\n *\n * // Force Arrow format\n * const { data } = useChartData({\n * queryKey: \"big_query\",\n * format: \"arrow_stream\"\n * });\n * ```\n */\nexport function useChartData(options: UseChartDataOptions): UseChartDataResult {\n const { queryKey, parameters, format = \"auto\", transformer } = options;\n\n // Resolve the format to use\n const resolvedFormat = useMemo(\n () => resolveFormat(format, parameters),\n [format, parameters],\n );\n\n const isArrowFormat = resolvedFormat === \"ARROW_STREAM\";\n\n // Fetch data using the analytics query hook\n const {\n data: rawData,\n loading,\n error,\n warehouseStatus,\n } = useAnalyticsQuery(queryKey, parameters, {\n autoStart: true,\n format: resolvedFormat,\n });\n\n // Process and transform data\n const processedData = useMemo(() => {\n if (!rawData) return null;\n\n // Apply transformer if provided\n if (transformer) {\n try {\n return transformer(rawData);\n } catch (err) {\n console.error(\"[useChartData] Transformer error:\", err);\n return rawData;\n }\n }\n\n return rawData;\n }, [rawData, transformer]);\n\n // Determine if data is empty\n const isEmpty = useMemo(() => {\n if (!processedData) return true;\n\n // Arrow Table - check using duck typing\n if (\n typeof processedData === \"object\" &&\n \"numRows\" in processedData &&\n typeof (processedData as Table).numRows === \"number\"\n ) {\n return (processedData as Table).numRows === 0;\n }\n\n // JSON Array\n if (Array.isArray(processedData)) {\n return processedData.length === 0;\n }\n\n return true;\n }, [processedData]);\n\n // Detect actual data type (may differ from requested if server doesn't support format)\n const isArrow = useMemo(() => {\n if (!processedData) return isArrowFormat;\n // Duck type check for Arrow Table\n return (\n typeof processedData === \"object\" &&\n processedData !== null &&\n \"schema\" in processedData &&\n \"numRows\" in processedData &&\n typeof (processedData as Table).getChild === \"function\"\n );\n }, [processedData, isArrowFormat]);\n\n return {\n data: processedData as ChartData | null,\n isArrow,\n loading,\n error,\n isEmpty,\n warehouseStatus,\n };\n}\n"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"use-chart-data.js","names":[],"sources":["../../../src/react/hooks/use-chart-data.ts"],"sourcesContent":["import type { Table } from \"apache-arrow\";\nimport { useMemo } from \"react\";\n\nimport type { ChartData, DataFormat } from \"../charts/types\";\nimport type { WarehouseStatus } from \"./types\";\nimport { useAnalyticsQuery } from \"./use-analytics-query\";\n\n/** Threshold for auto-selecting Arrow format (row count hint) */\nconst ARROW_THRESHOLD = 500;\n\n// ============================================================================\n// Hook Options & Result Types\n// ============================================================================\n\nexport interface UseChartDataOptions {\n /** Analytics query key */\n queryKey: string;\n /** Query parameters */\n parameters?: Record<string, unknown>;\n /**\n * Data format preference\n * - \"json_array\": Force JSON format\n * - \"arrow_stream\": Force Arrow format\n * - \"auto\": Auto-select based on heuristics\n * @default \"auto\"\n */\n format?: DataFormat;\n /** Transform data after fetching */\n transformer?: <T>(data: T) => T;\n}\n\nexport interface UseChartDataResult {\n /** The fetched data (Arrow Table or JSON array) */\n data: ChartData | null;\n /** Whether the data is in Arrow format */\n isArrow: boolean;\n /** Loading state */\n loading: boolean;\n /** Error message if any */\n error: string | null;\n /** Whether the data is empty */\n isEmpty: boolean;\n /**\n * Latest warehouse readiness status from SSE. Retains the last value\n * (including `RUNNING`) until the next `start()`; `null` only before\n * the first event. Use with `loading` to distinguish warehouse warm-up\n * from in-flight SQL fetch.\n */\n warehouseStatus: WarehouseStatus | null;\n}\n\n// ============================================================================\n// Format Resolution\n// ============================================================================\n\n/**\n * Resolves the data format based on hints and preferences\n */\nfunction resolveFormat(\n format: DataFormat,\n parameters?: Record<string, unknown>,\n): \"JSON_ARRAY\" | \"ARROW_STREAM\" {\n // Explicit format selection (legacy \"json\"/\"arrow\" accepted for back-compat\n // with appkit-ui < 0.33.0 — see DataFormat in ../charts/types.ts).\n if (format === \"json_array\" || format === \"json\") return \"JSON_ARRAY\";\n if (format === \"arrow_stream\" || format === \"arrow\") return \"ARROW_STREAM\";\n\n // Auto-selection heuristics\n if (format === \"auto\") {\n // Check for explicit hint in parameters\n if (parameters?._preferArrow === true) return \"ARROW_STREAM\";\n if (parameters?._preferJson === true) return \"JSON_ARRAY\";\n\n // Check limit parameter as data size hint\n const limit = parameters?.limit;\n if (typeof limit === \"number\" && limit > ARROW_THRESHOLD) {\n return \"ARROW_STREAM\";\n }\n\n // Check for date range queries (often large)\n if (parameters?.startDate && parameters?.endDate) {\n return \"ARROW_STREAM\";\n }\n\n return \"JSON_ARRAY\";\n }\n\n return \"JSON_ARRAY\";\n}\n\n// ============================================================================\n// Main Hook\n// ============================================================================\n\n/**\n * Hook for fetching chart data in either JSON or Arrow format.\n * Automatically selects the best format based on query hints.\n *\n * @example\n * ```tsx\n * // Auto-select format\n * const { data, isArrow, loading } = useChartData({\n * queryKey: \"spend_data\",\n * parameters: { limit: 1000 }\n * });\n *\n * // Force Arrow format\n * const { data } = useChartData({\n * queryKey: \"big_query\",\n * format: \"arrow_stream\"\n * });\n * ```\n */\nexport function useChartData(options: UseChartDataOptions): UseChartDataResult {\n const { queryKey, parameters, format = \"auto\", transformer } = options;\n\n // Resolve the format to use\n const resolvedFormat = useMemo(\n () => resolveFormat(format, parameters),\n [format, parameters],\n );\n\n const isArrowFormat = resolvedFormat === \"ARROW_STREAM\";\n\n // Fetch data using the analytics query hook\n const {\n data: rawData,\n loading,\n error,\n warehouseStatus,\n } = useAnalyticsQuery(queryKey, parameters, {\n autoStart: true,\n format: resolvedFormat,\n });\n\n // Process and transform data\n const processedData = useMemo(() => {\n if (!rawData) return null;\n\n // Apply transformer if provided\n if (transformer) {\n try {\n return transformer(rawData);\n } catch (err) {\n console.error(\"[useChartData] Transformer error:\", err);\n return rawData;\n }\n }\n\n return rawData;\n }, [rawData, transformer]);\n\n // Determine if data is empty\n const isEmpty = useMemo(() => {\n if (!processedData) return true;\n\n // Arrow Table - check using duck typing\n if (\n typeof processedData === \"object\" &&\n \"numRows\" in processedData &&\n typeof (processedData as Table).numRows === \"number\"\n ) {\n return (processedData as Table).numRows === 0;\n }\n\n // JSON Array\n if (Array.isArray(processedData)) {\n return processedData.length === 0;\n }\n\n return true;\n }, [processedData]);\n\n // Detect actual data type (may differ from requested if server doesn't support format)\n const isArrow = useMemo(() => {\n if (!processedData) return isArrowFormat;\n // Duck type check for Arrow Table\n return (\n typeof processedData === \"object\" &&\n processedData !== null &&\n \"schema\" in processedData &&\n \"numRows\" in processedData &&\n typeof (processedData as Table).getChild === \"function\"\n );\n }, [processedData, isArrowFormat]);\n\n return {\n data: processedData as ChartData | null,\n isArrow,\n loading,\n error,\n isEmpty,\n warehouseStatus,\n };\n}\n"],"mappings":";;;;;AAQA,MAAM,kBAAkB;;;;AAkDxB,SAAS,cACP,QACA,YAC+B;AAG/B,KAAI,WAAW,gBAAgB,WAAW,OAAQ,QAAO;AACzD,KAAI,WAAW,kBAAkB,WAAW,QAAS,QAAO;AAG5D,KAAI,WAAW,QAAQ;AAErB,MAAI,YAAY,iBAAiB,KAAM,QAAO;AAC9C,MAAI,YAAY,gBAAgB,KAAM,QAAO;EAG7C,MAAM,QAAQ,YAAY;AAC1B,MAAI,OAAO,UAAU,YAAY,QAAQ,gBACvC,QAAO;AAIT,MAAI,YAAY,aAAa,YAAY,QACvC,QAAO;AAGT,SAAO;;AAGT,QAAO;;;;;;;;;;;;;;;;;;;;;AA0BT,SAAgB,aAAa,SAAkD;CAC7E,MAAM,EAAE,UAAU,YAAY,SAAS,QAAQ,gBAAgB;CAG/D,MAAM,iBAAiB,cACf,cAAc,QAAQ,WAAW,EACvC,CAAC,QAAQ,WAAW,CACrB;CAED,MAAM,gBAAgB,mBAAmB;CAGzC,MAAM,EACJ,MAAM,SACN,SACA,OACA,oBACE,kBAAkB,UAAU,YAAY;EAC1C,WAAW;EACX,QAAQ;EACT,CAAC;CAGF,MAAM,gBAAgB,cAAc;AAClC,MAAI,CAAC,QAAS,QAAO;AAGrB,MAAI,YACF,KAAI;AACF,UAAO,YAAY,QAAQ;WACpB,KAAK;AACZ,WAAQ,MAAM,qCAAqC,IAAI;AACvD,UAAO;;AAIX,SAAO;IACN,CAAC,SAAS,YAAY,CAAC;CAG1B,MAAM,UAAU,cAAc;AAC5B,MAAI,CAAC,cAAe,QAAO;AAG3B,MACE,OAAO,kBAAkB,YACzB,aAAa,iBACb,OAAQ,cAAwB,YAAY,SAE5C,QAAQ,cAAwB,YAAY;AAI9C,MAAI,MAAM,QAAQ,cAAc,CAC9B,QAAO,cAAc,WAAW;AAGlC,SAAO;IACN,CAAC,cAAc,CAAC;AAenB,QAAO;EACL,MAAM;EACN,SAdc,cAAc;AAC5B,OAAI,CAAC,cAAe,QAAO;AAE3B,UACE,OAAO,kBAAkB,YACzB,kBAAkB,QAClB,YAAY,iBACZ,aAAa,iBACb,OAAQ,cAAwB,aAAa;KAE9C,CAAC,eAAe,cAAc,CAAC;EAKhC;EACA;EACA;EACA;EACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-metric-view.d.ts","names":[],"sources":["../../../src/react/hooks/use-metric-view.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"use-metric-view.d.ts","names":[],"sources":["../../../src/react/hooks/use-metric-view.ts"],"mappings":";;;;;AAuEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,aAAA,WACJ,SAAA,GAAY,SAAA,kBACN,aAAA,CAAc,gBAAA,CAAiB,CAAA,KAAM,aAAA,CACnD,gBAAA,CAAiB,CAAA,oBAEH,aAAA,CAAc,kBAAA,CAAmB,CAAA,iBAAA,CAEjD,GAAA,EAAK,CAAA,EACL,OAAA,EAAS,oBAAA,CAAqB,CAAA,EAAG,CAAA,EAAG,CAAA,IACnC,mBAAA,CAAoB,aAAA,CAAc,CAAA,EAAG,CAAA,EAAG,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-metric-view.js","names":[],"sources":["../../../src/react/hooks/use-metric-view.ts"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { MetricViewColumnDisplay } from \"shared\";\nimport { connectSSE } from \"@/js\";\nimport {\n type AnalyticsSseHandlerContext,\n getDevMode,\n handleAnalyticsSseError,\n handleAnalyticsSseMessage,\n} from \"./analytics-sse\";\nimport type {\n InferDimensionKeys,\n InferMeasureKeys,\n MetricKey,\n PickMetricRow,\n UseMetricViewOptions,\n UseMetricViewResult,\n WarehouseStatus,\n} from \"./types\";\nimport { useAnalyticsWarehousePublisher } from \"./use-analytics-warehouse-status\";\nimport { useQueryHMR } from \"./use-query-hmr\";\n\nfunction asMetricMetadata(\n value: unknown,\n): Record<string, MetricViewColumnDisplay> | undefined {\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n return value as Record<string, MetricViewColumnDisplay>;\n }\n return undefined;\n}\n\n/**\n * Subscribe to a Unity Catalog Metric View and return its latest result.\n *\n * @param key - Metric view identifier\n * @param options - Measures (required) plus optional dimensions, filter,\n * orderBy, timeGrain/timeDimension, and limit\n * @returns Metric result state with typed rows, display metadata, and warehouse readiness\n *\n * @remarks\n * `orderBy` and `limit` interact. With `limit`, the route completes the ordering\n * with the remaining dimensions so the capped rows are the *same* rows on every\n * run — pass `orderBy` to choose WHICH rows (top-N), since the completion only\n * makes the result stable, not ranked. Without `limit`, `orderBy` is presentation\n * ordering over the full result and gets no completion.\n *\n * When a request refetches with the same metric key, measures, and dimensions,\n * the previous `data` and `metadata` remain available while `loading` is true.\n * Changing any of those result-shaping fields clears the previous result.\n *\n * @example\n * ```typescript\n * const { data, metadata } = useMetricView(\"orders\", {\n * measures: [\"revenue\"],\n * dimensions: [\"region\"],\n * filter: { member: \"region\", operator: \"in\", values: [\"EMEA\", \"APAC\"] },\n * orderBy: [{ field: \"revenue\", direction: \"DESC\" }],\n * limit: 10,\n * });\n * // JSON_ARRAY preserves SQL scalar cells as strings and nullable columns as null:\n * // data: Array<{ revenue: string | null; region: string | null }> | null\n * ```\n */\nexport function useMetricView<\n K extends MetricKey = MetricKey,\n const M extends ReadonlyArray<InferMeasureKeys<K>> = ReadonlyArray<\n InferMeasureKeys<K>\n >,\n const D extends ReadonlyArray<InferDimensionKeys<K>> = readonly [],\n>(\n key: K,\n options: UseMetricViewOptions<K, M, D>,\n): UseMetricViewResult<PickMetricRow<K, M, D>[]> {\n const autoStart = options.autoStart ?? true;\n const devMode = getDevMode();\n const urlSuffix = `/api/analytics/metric/${encodeURIComponent(key)}${devMode}`;\n\n type Rows = PickMetricRow<K, M, D>[];\n const [result, setResult] = useState<{\n shape: string;\n data: Rows;\n metadata: Record<string, MetricViewColumnDisplay> | undefined;\n } | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [errorCode, setErrorCode] = useState<string | null>(null);\n const [warehouseStatus, setWarehouseStatus] =\n useState<WarehouseStatus | null>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n const activeRequestKeyRef = useRef<string | null>(null);\n const effectLeaseRef = useRef(0);\n const requestedShapeRef = useRef<string | null>(null);\n\n const publisherId = useId();\n const {\n publish: publishWarehouseStatus,\n unpublish: unpublishWarehouseStatus,\n } = useAnalyticsWarehousePublisher(publisherId, key);\n\n if (!key || key.trim().length === 0) {\n throw new Error(\"useMetricView: 'key' must be a non-empty string.\");\n }\n\n // Stringify to compare request by value; prevent re-firing on every render.\n const payload = useMemo(() => {\n const body: {\n measures: ReadonlyArray<unknown>;\n dimensions?: ReadonlyArray<unknown>;\n filter?: unknown;\n timeGrain?: unknown;\n timeDimension?: unknown;\n orderBy?: ReadonlyArray<unknown>;\n limit?: number;\n } = { measures: options.measures };\n if (options.dimensions !== undefined) body.dimensions = options.dimensions;\n if (options.filter !== undefined) body.filter = options.filter;\n if (options.timeGrain !== undefined) body.timeGrain = options.timeGrain;\n if (options.timeDimension !== undefined)\n body.timeDimension = options.timeDimension;\n if (options.orderBy !== undefined) body.orderBy = options.orderBy;\n if (options.limit !== undefined) body.limit = options.limit;\n return JSON.stringify(body);\n }, [\n options.measures,\n options.dimensions,\n options.filter,\n options.timeGrain,\n options.timeDimension,\n options.orderBy,\n options.limit,\n ]);\n\n // Detect shape changes: clear rows when metric/measures/dimensions change,\n // but keep stale rows during filter/order/limit/time-grain revalidations.\n const resultShape = JSON.stringify({\n key,\n measures: options.measures,\n dimensions: options.dimensions ?? [],\n });\n const requestKey = `${urlSuffix}\\0${payload}`;\n\n // Return stale rows only if shape is current; hide during shape transitions.\n const isCurrentShape = result?.shape === resultShape;\n const data = isCurrentShape ? result.data : null;\n const metadata = isCurrentShape ? result.metadata : undefined;\n\n const start = useCallback(() => {\n abortControllerRef.current?.abort();\n\n setLoading(true);\n setError(null);\n setErrorCode(null);\n setWarehouseStatus(null);\n if (requestedShapeRef.current !== resultShape) {\n requestedShapeRef.current = resultShape;\n setResult(null);\n }\n // Register an empty slot to clear stale status from the prior run.\n publishWarehouseStatus(null);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n activeRequestKeyRef.current = requestKey;\n\n const sseContext: AnalyticsSseHandlerContext = {\n source: \"useMetricView\",\n resource: { key },\n defaultExecutionError: \"Unable to execute metric query\",\n unpublishOnMalformedMessage: true,\n signal: abortController.signal,\n abort: () => abortController.abort(),\n setLoading,\n setError,\n setErrorCode,\n onWarehouseStatus: (status) => {\n setWarehouseStatus(status);\n publishWarehouseStatus(status);\n },\n onResult: (message) => {\n setError(null);\n setErrorCode(null);\n setResult({\n shape: resultShape,\n data: message.data as Rows,\n metadata: asMetricMetadata(message.payload.metadata),\n });\n },\n unpublishWarehouseStatus,\n };\n\n connectSSE({\n url: urlSuffix,\n payload,\n signal: abortController.signal,\n onMessage: (message) =>\n handleAnalyticsSseMessage(message.data, sseContext),\n onError: (error) => handleAnalyticsSseError(error, sseContext),\n });\n }, [\n key,\n payload,\n requestKey,\n resultShape,\n urlSuffix,\n publishWarehouseStatus,\n unpublishWarehouseStatus,\n ]);\n\n useEffect(() => {\n const lease = ++effectLeaseRef.current;\n\n if (autoStart) {\n // React Strict Mode runs an effect's setup/cleanup/setup sequence on\n // mount. Keep the still-active identical stream for the second setup so\n // development sends one POST, not an immediately-aborted duplicate.\n // A real request change still starts synchronously and aborts the prior\n // controller in start().\n const activeController = abortControllerRef.current;\n if (\n activeRequestKeyRef.current !== requestKey ||\n !activeController ||\n activeController.signal.aborted\n ) {\n start();\n }\n } else {\n abortControllerRef.current?.abort();\n activeRequestKeyRef.current = null;\n unpublishWarehouseStatus();\n }\n\n return () => {\n // Defer teardown by one microtask so Strict Mode's immediate second\n // setup can claim the same request. On a genuine unmount there is no new\n // lease, so the stream is still cancelled before later async work runs.\n const controller = abortControllerRef.current;\n queueMicrotask(() => {\n if (effectLeaseRef.current !== lease) return;\n controller?.abort();\n if (abortControllerRef.current === controller) {\n activeRequestKeyRef.current = null;\n }\n unpublishWarehouseStatus();\n });\n };\n }, [start, autoStart, requestKey, unpublishWarehouseStatus]);\n\n useQueryHMR(key, start);\n\n return { data, loading, error, errorCode, metadata, warehouseStatus };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-metric-view.js","names":[],"sources":["../../../src/react/hooks/use-metric-view.ts"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { MetricViewColumnDisplay } from \"shared\";\n\nimport { connectSSE } from \"@/js\";\n\nimport {\n type AnalyticsSseHandlerContext,\n getDevMode,\n handleAnalyticsSseError,\n handleAnalyticsSseMessage,\n} from \"./analytics-sse\";\nimport type {\n InferDimensionKeys,\n InferMeasureKeys,\n MetricKey,\n PickMetricRow,\n UseMetricViewOptions,\n UseMetricViewResult,\n WarehouseStatus,\n} from \"./types\";\nimport { useAnalyticsWarehousePublisher } from \"./use-analytics-warehouse-status\";\nimport { useQueryHMR } from \"./use-query-hmr\";\n\nfunction asMetricMetadata(\n value: unknown,\n): Record<string, MetricViewColumnDisplay> | undefined {\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n return value as Record<string, MetricViewColumnDisplay>;\n }\n return undefined;\n}\n\n/**\n * Subscribe to a Unity Catalog Metric View and return its latest result.\n *\n * @param key - Metric view identifier\n * @param options - Measures (required) plus optional dimensions, filter,\n * orderBy, timeGrain/timeDimension, and limit\n * @returns Metric result state with typed rows, display metadata, and warehouse readiness\n *\n * @remarks\n * `orderBy` and `limit` interact. With `limit`, the route completes the ordering\n * with the remaining dimensions so the capped rows are the *same* rows on every\n * run — pass `orderBy` to choose WHICH rows (top-N), since the completion only\n * makes the result stable, not ranked. Without `limit`, `orderBy` is presentation\n * ordering over the full result and gets no completion.\n *\n * When a request refetches with the same metric key, measures, and dimensions,\n * the previous `data` and `metadata` remain available while `loading` is true.\n * Changing any of those result-shaping fields clears the previous result.\n *\n * @example\n * ```typescript\n * const { data, metadata } = useMetricView(\"orders\", {\n * measures: [\"revenue\"],\n * dimensions: [\"region\"],\n * filter: { member: \"region\", operator: \"in\", values: [\"EMEA\", \"APAC\"] },\n * orderBy: [{ field: \"revenue\", direction: \"DESC\" }],\n * limit: 10,\n * });\n * // JSON_ARRAY preserves SQL scalar cells as strings and nullable columns as null:\n * // data: Array<{ revenue: string | null; region: string | null }> | null\n * ```\n */\nexport function useMetricView<\n K extends MetricKey = MetricKey,\n const M extends ReadonlyArray<InferMeasureKeys<K>> = ReadonlyArray<\n InferMeasureKeys<K>\n >,\n const D extends ReadonlyArray<InferDimensionKeys<K>> = readonly [],\n>(\n key: K,\n options: UseMetricViewOptions<K, M, D>,\n): UseMetricViewResult<PickMetricRow<K, M, D>[]> {\n const autoStart = options.autoStart ?? true;\n const devMode = getDevMode();\n const urlSuffix = `/api/analytics/metric/${encodeURIComponent(key)}${devMode}`;\n\n type Rows = PickMetricRow<K, M, D>[];\n const [result, setResult] = useState<{\n shape: string;\n data: Rows;\n metadata: Record<string, MetricViewColumnDisplay> | undefined;\n } | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [errorCode, setErrorCode] = useState<string | null>(null);\n const [warehouseStatus, setWarehouseStatus] =\n useState<WarehouseStatus | null>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n const activeRequestKeyRef = useRef<string | null>(null);\n const effectLeaseRef = useRef(0);\n const requestedShapeRef = useRef<string | null>(null);\n\n const publisherId = useId();\n const {\n publish: publishWarehouseStatus,\n unpublish: unpublishWarehouseStatus,\n } = useAnalyticsWarehousePublisher(publisherId, key);\n\n if (!key || key.trim().length === 0) {\n throw new Error(\"useMetricView: 'key' must be a non-empty string.\");\n }\n\n // Stringify to compare request by value; prevent re-firing on every render.\n const payload = useMemo(() => {\n const body: {\n measures: ReadonlyArray<unknown>;\n dimensions?: ReadonlyArray<unknown>;\n filter?: unknown;\n timeGrain?: unknown;\n timeDimension?: unknown;\n orderBy?: ReadonlyArray<unknown>;\n limit?: number;\n } = { measures: options.measures };\n if (options.dimensions !== undefined) body.dimensions = options.dimensions;\n if (options.filter !== undefined) body.filter = options.filter;\n if (options.timeGrain !== undefined) body.timeGrain = options.timeGrain;\n if (options.timeDimension !== undefined)\n body.timeDimension = options.timeDimension;\n if (options.orderBy !== undefined) body.orderBy = options.orderBy;\n if (options.limit !== undefined) body.limit = options.limit;\n return JSON.stringify(body);\n }, [\n options.measures,\n options.dimensions,\n options.filter,\n options.timeGrain,\n options.timeDimension,\n options.orderBy,\n options.limit,\n ]);\n\n // Detect shape changes: clear rows when metric/measures/dimensions change,\n // but keep stale rows during filter/order/limit/time-grain revalidations.\n const resultShape = JSON.stringify({\n key,\n measures: options.measures,\n dimensions: options.dimensions ?? [],\n });\n const requestKey = `${urlSuffix}\\0${payload}`;\n\n // Return stale rows only if shape is current; hide during shape transitions.\n const isCurrentShape = result?.shape === resultShape;\n const data = isCurrentShape ? result.data : null;\n const metadata = isCurrentShape ? result.metadata : undefined;\n\n const start = useCallback(() => {\n abortControllerRef.current?.abort();\n\n setLoading(true);\n setError(null);\n setErrorCode(null);\n setWarehouseStatus(null);\n if (requestedShapeRef.current !== resultShape) {\n requestedShapeRef.current = resultShape;\n setResult(null);\n }\n // Register an empty slot to clear stale status from the prior run.\n publishWarehouseStatus(null);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n activeRequestKeyRef.current = requestKey;\n\n const sseContext: AnalyticsSseHandlerContext = {\n source: \"useMetricView\",\n resource: { key },\n defaultExecutionError: \"Unable to execute metric query\",\n unpublishOnMalformedMessage: true,\n signal: abortController.signal,\n abort: () => abortController.abort(),\n setLoading,\n setError,\n setErrorCode,\n onWarehouseStatus: (status) => {\n setWarehouseStatus(status);\n publishWarehouseStatus(status);\n },\n onResult: (message) => {\n setError(null);\n setErrorCode(null);\n setResult({\n shape: resultShape,\n data: message.data as Rows,\n metadata: asMetricMetadata(message.payload.metadata),\n });\n },\n unpublishWarehouseStatus,\n };\n\n connectSSE({\n url: urlSuffix,\n payload,\n signal: abortController.signal,\n onMessage: (message) =>\n handleAnalyticsSseMessage(message.data, sseContext),\n onError: (error) => handleAnalyticsSseError(error, sseContext),\n });\n }, [\n key,\n payload,\n requestKey,\n resultShape,\n urlSuffix,\n publishWarehouseStatus,\n unpublishWarehouseStatus,\n ]);\n\n useEffect(() => {\n const lease = ++effectLeaseRef.current;\n\n if (autoStart) {\n // React Strict Mode runs an effect's setup/cleanup/setup sequence on\n // mount. Keep the still-active identical stream for the second setup so\n // development sends one POST, not an immediately-aborted duplicate.\n // A real request change still starts synchronously and aborts the prior\n // controller in start().\n const activeController = abortControllerRef.current;\n if (\n activeRequestKeyRef.current !== requestKey ||\n !activeController ||\n activeController.signal.aborted\n ) {\n start();\n }\n } else {\n abortControllerRef.current?.abort();\n activeRequestKeyRef.current = null;\n unpublishWarehouseStatus();\n }\n\n return () => {\n // Defer teardown by one microtask so Strict Mode's immediate second\n // setup can claim the same request. On a genuine unmount there is no new\n // lease, so the stream is still cancelled before later async work runs.\n const controller = abortControllerRef.current;\n queueMicrotask(() => {\n if (effectLeaseRef.current !== lease) return;\n controller?.abort();\n if (abortControllerRef.current === controller) {\n activeRequestKeyRef.current = null;\n }\n unpublishWarehouseStatus();\n });\n };\n }, [start, autoStart, requestKey, unpublishWarehouseStatus]);\n\n useQueryHMR(key, start);\n\n return { data, loading, error, errorCode, metadata, warehouseStatus };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-plugin-config.d.ts","names":[],"sources":["../../../src/react/hooks/use-plugin-config.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"use-plugin-config.d.ts","names":[],"sources":["../../../src/react/hooks/use-plugin-config.ts"],"mappings":";;AAwBA;;;;;;;;;;;;;;;;;;;iBAAgB,qBAAA,KAA0B,MAAA,kBAAA,CACxC,UAAA,WACC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-plugin-config.js","names":[],"sources":["../../../src/react/hooks/use-plugin-config.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { getPluginClientConfig } from \"@/js\";\n\n/**\n * Returns the client-side config exposed by a plugin's `clientConfig()` method.\n *\n * The value is read once from the boot-time `<script id=\"__appkit__\">` payload\n * and cached for the lifetime of the component. Because `clientConfig()` runs\n * at server startup, the data is static and never changes during a session.\n *\n * @param pluginName - The plugin name as registered in AppKit (e.g. `\"files\"`)\n * @returns The plugin's client config object, typed via the generic parameter\n *\n * @example\n * ```tsx\n * interface FilesConfig { volumes: string[] }\n *\n * function MyComponent() {\n * const { volumes } = usePluginClientConfig<FilesConfig>(\"files\");\n * return <ul>{volumes.map(v => <li key={v}>{v}</li>)}</ul>;\n * }\n * ```\n */\nexport function usePluginClientConfig<T = Record<string, unknown>>(\n pluginName: string,\n): T {\n return useMemo(() => getPluginClientConfig<T>(pluginName), [pluginName]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"use-plugin-config.js","names":[],"sources":["../../../src/react/hooks/use-plugin-config.ts"],"sourcesContent":["import { useMemo } from \"react\";\n\nimport { getPluginClientConfig } from \"@/js\";\n\n/**\n * Returns the client-side config exposed by a plugin's `clientConfig()` method.\n *\n * The value is read once from the boot-time `<script id=\"__appkit__\">` payload\n * and cached for the lifetime of the component. Because `clientConfig()` runs\n * at server startup, the data is static and never changes during a session.\n *\n * @param pluginName - The plugin name as registered in AppKit (e.g. `\"files\"`)\n * @returns The plugin's client config object, typed via the generic parameter\n *\n * @example\n * ```tsx\n * interface FilesConfig { volumes: string[] }\n *\n * function MyComponent() {\n * const { volumes } = usePluginClientConfig<FilesConfig>(\"files\");\n * return <ul>{volumes.map(v => <li key={v}>{v}</li>)}</ul>;\n * }\n * ```\n */\nexport function usePluginClientConfig<T = Record<string, unknown>>(\n pluginName: string,\n): T {\n return useMemo(() => getPluginClientConfig<T>(pluginName), [pluginName]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,sBACd,YACG;AACH,QAAO,cAAc,sBAAyB,WAAW,EAAE,CAAC,WAAW,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-serving-invoke.d.ts","names":[],"sources":["../../../src/react/hooks/use-serving-invoke.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"use-serving-invoke.d.ts","names":[],"sources":["../../../src/react/hooks/use-serving-invoke.ts"],"mappings":";;;UAUiB,uBAAA,WACL,YAAA,GAAe,YAAA;;EAGzB,KAAA,GAAQ,CAAA;EAJ8B;EAMtC,SAAA;AAAA;AAAA,UAGe,sBAAA,sBAEP,MAAA;EAPA;EAUR,MAAA,GAAS,YAAA,GAAe,KAAA,KAAU,OAAA,CAAQ,CAAA;EAVjC;EAYT,IAAA,EAAM,CAAA;EAfI;EAiBV,OAAA;EAdA;EAgBA,KAAA;AAAA;;;AAXF;;;;;iBAqBgB,gBAAA,WAA2B,YAAA,GAAe,YAAA,CAAA,CACxD,IAAA,EAAM,mBAAA,CAAoB,CAAA,GAC1B,OAAA,GAAS,uBAAA,CAAwB,CAAA,IAChC,sBAAA,CAAuB,oBAAA,CAAqB,CAAA,GAAI,mBAAA,CAAoB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-serving-invoke.js","names":[],"sources":["../../../src/react/hooks/use-serving-invoke.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n InferServingRequest,\n InferServingResponse,\n ServingAlias,\n ServingClientConfig,\n} from \"./types\";\nimport { usePluginClientConfig } from \"./use-plugin-config\";\n\nexport interface UseServingInvokeOptions<\n K extends ServingAlias = ServingAlias,\n> {\n /** Endpoint alias for named mode. Omit for default mode. */\n alias?: K;\n /** If false, does not invoke automatically on mount. Default: false */\n autoStart?: boolean;\n}\n\nexport interface UseServingInvokeResult<\n T = unknown,\n TBody = Record<string, unknown>,\n> {\n /** Trigger the invocation. Pass an optional body override for this invocation. */\n invoke: (overrideBody?: TBody) => Promise<T | null>;\n /** Response data, null until loaded. */\n data: T | null;\n /** Whether a request is in progress. */\n loading: boolean;\n /** Error message, if any. */\n error: string | null;\n}\n\n/**\n * Hook for non-streaming invocation of a serving endpoint.\n * Calls `POST /api/serving/invoke` (default) or `POST /api/serving/{alias}/invoke` (named).\n *\n * When the type generator has populated `ServingEndpointRegistry`, the response type\n * is automatically inferred from the endpoint's OpenAPI schema.\n */\nexport function useServingInvoke<K extends ServingAlias = ServingAlias>(\n body: InferServingRequest<K>,\n options: UseServingInvokeOptions<K> = {} as UseServingInvokeOptions<K>,\n): UseServingInvokeResult<InferServingResponse<K>, InferServingRequest<K>> {\n type TResponse = InferServingResponse<K>;\n const { alias, autoStart = false } = options;\n\n const config = usePluginClientConfig<ServingClientConfig>(\"serving\");\n\n const aliasError = useMemo(() => {\n if (!alias || !config.aliases) return null;\n const aliasStr = String(alias);\n if (!config.aliases.includes(aliasStr)) {\n return `Unknown serving alias \"${aliasStr}\". Available: ${config.aliases.join(\", \")}`;\n }\n return null;\n }, [alias, config.aliases]);\n\n const [data, setData] = useState<TResponse | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(aliasError);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const urlSuffix = alias\n ? `/api/serving/${encodeURIComponent(String(alias))}/invoke`\n : \"/api/serving/invoke\";\n\n const bodyJson = JSON.stringify(body);\n\n const invoke = useCallback(\n (overrideBody?: InferServingRequest<K>): Promise<TResponse | null> => {\n if (aliasError) {\n setError(aliasError);\n return Promise.resolve(null);\n }\n\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n setLoading(true);\n setError(null);\n setData(null);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n const payload = overrideBody ? JSON.stringify(overrideBody) : bodyJson;\n\n return fetch(urlSuffix, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: payload,\n signal: abortController.signal,\n })\n .then(async (res) => {\n if (!res.ok) {\n const errorBody = await res.json().catch(() => null);\n throw new Error(errorBody?.error || `HTTP ${res.status}`);\n }\n return res.json();\n })\n .then((result: TResponse) => {\n if (abortController.signal.aborted) return null;\n setData(result);\n setLoading(false);\n return result;\n })\n .catch((err: Error) => {\n if (abortController.signal.aborted) return null;\n setError(err.message || \"Request failed\");\n setLoading(false);\n return null;\n });\n },\n [urlSuffix, bodyJson, aliasError],\n );\n\n useEffect(() => {\n if (autoStart) {\n invoke();\n }\n\n return () => {\n abortControllerRef.current?.abort();\n };\n }, [invoke, autoStart]);\n\n return { invoke, data, loading, error };\n}\n"],"mappings":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"use-serving-invoke.js","names":[],"sources":["../../../src/react/hooks/use-serving-invoke.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport type {\n InferServingRequest,\n InferServingResponse,\n ServingAlias,\n ServingClientConfig,\n} from \"./types\";\nimport { usePluginClientConfig } from \"./use-plugin-config\";\n\nexport interface UseServingInvokeOptions<\n K extends ServingAlias = ServingAlias,\n> {\n /** Endpoint alias for named mode. Omit for default mode. */\n alias?: K;\n /** If false, does not invoke automatically on mount. Default: false */\n autoStart?: boolean;\n}\n\nexport interface UseServingInvokeResult<\n T = unknown,\n TBody = Record<string, unknown>,\n> {\n /** Trigger the invocation. Pass an optional body override for this invocation. */\n invoke: (overrideBody?: TBody) => Promise<T | null>;\n /** Response data, null until loaded. */\n data: T | null;\n /** Whether a request is in progress. */\n loading: boolean;\n /** Error message, if any. */\n error: string | null;\n}\n\n/**\n * Hook for non-streaming invocation of a serving endpoint.\n * Calls `POST /api/serving/invoke` (default) or `POST /api/serving/{alias}/invoke` (named).\n *\n * When the type generator has populated `ServingEndpointRegistry`, the response type\n * is automatically inferred from the endpoint's OpenAPI schema.\n */\nexport function useServingInvoke<K extends ServingAlias = ServingAlias>(\n body: InferServingRequest<K>,\n options: UseServingInvokeOptions<K> = {} as UseServingInvokeOptions<K>,\n): UseServingInvokeResult<InferServingResponse<K>, InferServingRequest<K>> {\n type TResponse = InferServingResponse<K>;\n const { alias, autoStart = false } = options;\n\n const config = usePluginClientConfig<ServingClientConfig>(\"serving\");\n\n const aliasError = useMemo(() => {\n if (!alias || !config.aliases) return null;\n const aliasStr = String(alias);\n if (!config.aliases.includes(aliasStr)) {\n return `Unknown serving alias \"${aliasStr}\". Available: ${config.aliases.join(\", \")}`;\n }\n return null;\n }, [alias, config.aliases]);\n\n const [data, setData] = useState<TResponse | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(aliasError);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const urlSuffix = alias\n ? `/api/serving/${encodeURIComponent(String(alias))}/invoke`\n : \"/api/serving/invoke\";\n\n const bodyJson = JSON.stringify(body);\n\n const invoke = useCallback(\n (overrideBody?: InferServingRequest<K>): Promise<TResponse | null> => {\n if (aliasError) {\n setError(aliasError);\n return Promise.resolve(null);\n }\n\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n setLoading(true);\n setError(null);\n setData(null);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n const payload = overrideBody ? JSON.stringify(overrideBody) : bodyJson;\n\n return fetch(urlSuffix, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: payload,\n signal: abortController.signal,\n })\n .then(async (res) => {\n if (!res.ok) {\n const errorBody = await res.json().catch(() => null);\n throw new Error(errorBody?.error || `HTTP ${res.status}`);\n }\n return res.json();\n })\n .then((result: TResponse) => {\n if (abortController.signal.aborted) return null;\n setData(result);\n setLoading(false);\n return result;\n })\n .catch((err: Error) => {\n if (abortController.signal.aborted) return null;\n setError(err.message || \"Request failed\");\n setLoading(false);\n return null;\n });\n },\n [urlSuffix, bodyJson, aliasError],\n );\n\n useEffect(() => {\n if (autoStart) {\n invoke();\n }\n\n return () => {\n abortControllerRef.current?.abort();\n };\n }, [invoke, autoStart]);\n\n return { invoke, data, loading, error };\n}\n"],"mappings":";;;;;;;;;;;AAwCA,SAAgB,iBACd,MACA,UAAsC,EAAE,EACiC;CAEzE,MAAM,EAAE,OAAO,YAAY,UAAU;CAErC,MAAM,SAAS,sBAA2C,UAAU;CAEpE,MAAM,aAAa,cAAc;AAC/B,MAAI,CAAC,SAAS,CAAC,OAAO,QAAS,QAAO;EACtC,MAAM,WAAW,OAAO,MAAM;AAC9B,MAAI,CAAC,OAAO,QAAQ,SAAS,SAAS,CACpC,QAAO,0BAA0B,SAAS,gBAAgB,OAAO,QAAQ,KAAK,KAAK;AAErF,SAAO;IACN,CAAC,OAAO,OAAO,QAAQ,CAAC;CAE3B,MAAM,CAAC,MAAM,WAAW,SAA2B,KAAK;CACxD,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,OAAO,YAAY,SAAwB,WAAW;CAC7D,MAAM,qBAAqB,OAA+B,KAAK;CAE/D,MAAM,YAAY,QACd,gBAAgB,mBAAmB,OAAO,MAAM,CAAC,CAAC,WAClD;CAEJ,MAAM,WAAW,KAAK,UAAU,KAAK;CAErC,MAAM,SAAS,aACZ,iBAAqE;AACpE,MAAI,YAAY;AACd,YAAS,WAAW;AACpB,UAAO,QAAQ,QAAQ,KAAK;;AAG9B,MAAI,mBAAmB,QACrB,oBAAmB,QAAQ,OAAO;AAGpC,aAAW,KAAK;AAChB,WAAS,KAAK;AACd,UAAQ,KAAK;EAEb,MAAM,kBAAkB,IAAI,iBAAiB;AAC7C,qBAAmB,UAAU;EAE7B,MAAM,UAAU,eAAe,KAAK,UAAU,aAAa,GAAG;AAE9D,SAAO,MAAM,WAAW;GACtB,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM;GACN,QAAQ,gBAAgB;GACzB,CAAC,CACC,KAAK,OAAO,QAAQ;AACnB,OAAI,CAAC,IAAI,IAAI;IACX,MAAM,YAAY,MAAM,IAAI,MAAM,CAAC,YAAY,KAAK;AACpD,UAAM,IAAI,MAAM,WAAW,SAAS,QAAQ,IAAI,SAAS;;AAE3D,UAAO,IAAI,MAAM;IACjB,CACD,MAAM,WAAsB;AAC3B,OAAI,gBAAgB,OAAO,QAAS,QAAO;AAC3C,WAAQ,OAAO;AACf,cAAW,MAAM;AACjB,UAAO;IACP,CACD,OAAO,QAAe;AACrB,OAAI,gBAAgB,OAAO,QAAS,QAAO;AAC3C,YAAS,IAAI,WAAW,iBAAiB;AACzC,cAAW,MAAM;AACjB,UAAO;IACP;IAEN;EAAC;EAAW;EAAU;EAAW,CAClC;AAED,iBAAgB;AACd,MAAI,UACF,SAAQ;AAGV,eAAa;AACX,sBAAmB,SAAS,OAAO;;IAEpC,CAAC,QAAQ,UAAU,CAAC;AAEvB,QAAO;EAAE;EAAQ;EAAM;EAAS;EAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-serving-stream.d.ts","names":[],"sources":["../../../src/react/hooks/use-serving-stream.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"use-serving-stream.d.ts","names":[],"sources":["../../../src/react/hooks/use-serving-stream.ts"],"mappings":";;;UAYiB,uBAAA,WACL,YAAA,GAAe,YAAA,MACrB,iBAAA,CAAkB,CAAA;;EAGtB,KAAA,GAAQ,CAAA;EAL8B;EAOtC,SAAA;EANU;EAQV,UAAA,IAAc,MAAA,EAAQ,CAAA;AAAA;AAAA,UAGP,sBAAA,sBAEP,MAAA;EATA;EAYR,MAAA,GAAS,YAAA,GAAe,KAAA;EARD;EAUvB,MAAA,EAAQ,CAAA;EAlBR;EAoBA,SAAA;EApByB;EAsBzB,KAAA;EArBI;EAuBJ,KAAA;AAAA;;;;;;;;AAbF;iBAwBgB,gBAAA,WAA2B,YAAA,GAAe,YAAA,CAAA,CACxD,IAAA,EAAM,mBAAA,CAAoB,CAAA,GAC1B,OAAA,GAAS,uBAAA,CAAwB,CAAA,IAChC,sBAAA,CAAuB,iBAAA,CAAkB,CAAA,GAAI,mBAAA,CAAoB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-serving-stream.js","names":[],"sources":["../../../src/react/hooks/use-serving-stream.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { connectSSE } from \"@/js\";\nimport type {\n InferServingChunk,\n InferServingRequest,\n ServingAlias,\n ServingClientConfig,\n} from \"./types\";\nimport { usePluginClientConfig } from \"./use-plugin-config\";\n\nexport interface UseServingStreamOptions<\n K extends ServingAlias = ServingAlias,\n T = InferServingChunk<K>,\n> {\n /** Endpoint alias for named mode. Omit for default mode. */\n alias?: K;\n /** If true, starts streaming automatically on mount. Default: false */\n autoStart?: boolean;\n /** Called with accumulated chunks when the stream completes successfully. */\n onComplete?: (chunks: T[]) => void;\n}\n\nexport interface UseServingStreamResult<\n T = unknown,\n TBody = Record<string, unknown>,\n> {\n /** Trigger the streaming invocation. Pass an optional body override for this invocation. */\n stream: (overrideBody?: TBody) => void;\n /** Accumulated chunks received so far. */\n chunks: T[];\n /** Whether streaming is in progress. */\n streaming: boolean;\n /** Error message, if any. */\n error: string | null;\n /** Reset chunks and abort any active stream. */\n reset: () => void;\n}\n\n/**\n * Hook for streaming invocation of a serving endpoint via SSE.\n * Calls `POST /api/serving/stream` (default) or `POST /api/serving/{alias}/stream` (named).\n * Accumulates parsed chunks in state.\n *\n * When the type generator has populated `ServingEndpointRegistry`, the chunk type\n * is automatically inferred from the endpoint's OpenAPI schema.\n */\nexport function useServingStream<K extends ServingAlias = ServingAlias>(\n body: InferServingRequest<K>,\n options: UseServingStreamOptions<K> = {} as UseServingStreamOptions<K>,\n): UseServingStreamResult<InferServingChunk<K>, InferServingRequest<K>> {\n type TChunk = InferServingChunk<K>;\n const { alias, autoStart = false, onComplete } = options;\n\n const config = usePluginClientConfig<ServingClientConfig>(\"serving\");\n\n const aliasError = useMemo(() => {\n if (!alias || !config.aliases) return null;\n const aliasStr = String(alias);\n if (!config.aliases.includes(aliasStr)) {\n return `Unknown serving alias \"${aliasStr}\". Available: ${config.aliases.join(\", \")}`;\n }\n return null;\n }, [alias, config.aliases]);\n\n const [chunks, setChunks] = useState<TChunk[]>([]);\n const [streaming, setStreaming] = useState(false);\n const [error, setError] = useState<string | null>(aliasError);\n const abortControllerRef = useRef<AbortController | null>(null);\n const chunksRef = useRef<TChunk[]>([]);\n const onCompleteRef = useRef(onComplete);\n onCompleteRef.current = onComplete;\n\n const urlSuffix = alias\n ? `/api/serving/${encodeURIComponent(String(alias))}/stream`\n : \"/api/serving/stream\";\n\n const reset = useCallback(() => {\n abortControllerRef.current?.abort();\n abortControllerRef.current = null;\n chunksRef.current = [];\n setChunks([]);\n setStreaming(false);\n setError(null);\n }, []);\n\n const bodyJson = JSON.stringify(body);\n\n const stream = useCallback(\n (overrideBody?: InferServingRequest<K>) => {\n if (aliasError) {\n setError(aliasError);\n return;\n }\n\n // Abort any existing stream\n abortControllerRef.current?.abort();\n\n setStreaming(true);\n setError(null);\n setChunks([]);\n chunksRef.current = [];\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n const payload = overrideBody ? JSON.stringify(overrideBody) : bodyJson;\n\n connectSSE({\n url: urlSuffix,\n payload,\n signal: abortController.signal,\n onMessage: async (message) => {\n if (abortController.signal.aborted) return;\n try {\n const parsed = JSON.parse(message.data);\n\n chunksRef.current = [...chunksRef.current, parsed as TChunk];\n setChunks(chunksRef.current);\n } catch {\n // Skip malformed messages\n }\n },\n onError: (err) => {\n if (abortController.signal.aborted) return;\n setStreaming(false);\n setError(err instanceof Error ? err.message : \"Streaming failed\");\n },\n })\n .then(() => {\n if (abortController.signal.aborted) return;\n // Stream completed\n setStreaming(false);\n onCompleteRef.current?.(chunksRef.current);\n })\n .catch(() => {\n if (abortController.signal.aborted) return;\n setStreaming(false);\n setError(\"Connection error\");\n });\n },\n [urlSuffix, bodyJson, aliasError],\n );\n\n useEffect(() => {\n if (autoStart) {\n stream();\n }\n\n return () => {\n abortControllerRef.current?.abort();\n };\n }, [stream, autoStart]);\n\n return { stream, chunks, streaming, error, reset };\n}\n"],"mappings":";;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"use-serving-stream.js","names":[],"sources":["../../../src/react/hooks/use-serving-stream.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { connectSSE } from \"@/js\";\n\nimport type {\n InferServingChunk,\n InferServingRequest,\n ServingAlias,\n ServingClientConfig,\n} from \"./types\";\nimport { usePluginClientConfig } from \"./use-plugin-config\";\n\nexport interface UseServingStreamOptions<\n K extends ServingAlias = ServingAlias,\n T = InferServingChunk<K>,\n> {\n /** Endpoint alias for named mode. Omit for default mode. */\n alias?: K;\n /** If true, starts streaming automatically on mount. Default: false */\n autoStart?: boolean;\n /** Called with accumulated chunks when the stream completes successfully. */\n onComplete?: (chunks: T[]) => void;\n}\n\nexport interface UseServingStreamResult<\n T = unknown,\n TBody = Record<string, unknown>,\n> {\n /** Trigger the streaming invocation. Pass an optional body override for this invocation. */\n stream: (overrideBody?: TBody) => void;\n /** Accumulated chunks received so far. */\n chunks: T[];\n /** Whether streaming is in progress. */\n streaming: boolean;\n /** Error message, if any. */\n error: string | null;\n /** Reset chunks and abort any active stream. */\n reset: () => void;\n}\n\n/**\n * Hook for streaming invocation of a serving endpoint via SSE.\n * Calls `POST /api/serving/stream` (default) or `POST /api/serving/{alias}/stream` (named).\n * Accumulates parsed chunks in state.\n *\n * When the type generator has populated `ServingEndpointRegistry`, the chunk type\n * is automatically inferred from the endpoint's OpenAPI schema.\n */\nexport function useServingStream<K extends ServingAlias = ServingAlias>(\n body: InferServingRequest<K>,\n options: UseServingStreamOptions<K> = {} as UseServingStreamOptions<K>,\n): UseServingStreamResult<InferServingChunk<K>, InferServingRequest<K>> {\n type TChunk = InferServingChunk<K>;\n const { alias, autoStart = false, onComplete } = options;\n\n const config = usePluginClientConfig<ServingClientConfig>(\"serving\");\n\n const aliasError = useMemo(() => {\n if (!alias || !config.aliases) return null;\n const aliasStr = String(alias);\n if (!config.aliases.includes(aliasStr)) {\n return `Unknown serving alias \"${aliasStr}\". Available: ${config.aliases.join(\", \")}`;\n }\n return null;\n }, [alias, config.aliases]);\n\n const [chunks, setChunks] = useState<TChunk[]>([]);\n const [streaming, setStreaming] = useState(false);\n const [error, setError] = useState<string | null>(aliasError);\n const abortControllerRef = useRef<AbortController | null>(null);\n const chunksRef = useRef<TChunk[]>([]);\n const onCompleteRef = useRef(onComplete);\n onCompleteRef.current = onComplete;\n\n const urlSuffix = alias\n ? `/api/serving/${encodeURIComponent(String(alias))}/stream`\n : \"/api/serving/stream\";\n\n const reset = useCallback(() => {\n abortControllerRef.current?.abort();\n abortControllerRef.current = null;\n chunksRef.current = [];\n setChunks([]);\n setStreaming(false);\n setError(null);\n }, []);\n\n const bodyJson = JSON.stringify(body);\n\n const stream = useCallback(\n (overrideBody?: InferServingRequest<K>) => {\n if (aliasError) {\n setError(aliasError);\n return;\n }\n\n // Abort any existing stream\n abortControllerRef.current?.abort();\n\n setStreaming(true);\n setError(null);\n setChunks([]);\n chunksRef.current = [];\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n const payload = overrideBody ? JSON.stringify(overrideBody) : bodyJson;\n\n connectSSE({\n url: urlSuffix,\n payload,\n signal: abortController.signal,\n onMessage: async (message) => {\n if (abortController.signal.aborted) return;\n try {\n const parsed = JSON.parse(message.data);\n\n chunksRef.current = [...chunksRef.current, parsed as TChunk];\n setChunks(chunksRef.current);\n } catch {\n // Skip malformed messages\n }\n },\n onError: (err) => {\n if (abortController.signal.aborted) return;\n setStreaming(false);\n setError(err instanceof Error ? err.message : \"Streaming failed\");\n },\n })\n .then(() => {\n if (abortController.signal.aborted) return;\n // Stream completed\n setStreaming(false);\n onCompleteRef.current?.(chunksRef.current);\n })\n .catch(() => {\n if (abortController.signal.aborted) return;\n setStreaming(false);\n setError(\"Connection error\");\n });\n },\n [urlSuffix, bodyJson, aliasError],\n );\n\n useEffect(() => {\n if (autoStart) {\n stream();\n }\n\n return () => {\n abortControllerRef.current?.abort();\n };\n }, [stream, autoStart]);\n\n return { stream, chunks, streaming, error, reset };\n}\n"],"mappings":";;;;;;;;;;;;;AAgDA,SAAgB,iBACd,MACA,UAAsC,EAAE,EAC8B;CAEtE,MAAM,EAAE,OAAO,YAAY,OAAO,eAAe;CAEjD,MAAM,SAAS,sBAA2C,UAAU;CAEpE,MAAM,aAAa,cAAc;AAC/B,MAAI,CAAC,SAAS,CAAC,OAAO,QAAS,QAAO;EACtC,MAAM,WAAW,OAAO,MAAM;AAC9B,MAAI,CAAC,OAAO,QAAQ,SAAS,SAAS,CACpC,QAAO,0BAA0B,SAAS,gBAAgB,OAAO,QAAQ,KAAK,KAAK;AAErF,SAAO;IACN,CAAC,OAAO,OAAO,QAAQ,CAAC;CAE3B,MAAM,CAAC,QAAQ,aAAa,SAAmB,EAAE,CAAC;CAClD,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CACjD,MAAM,CAAC,OAAO,YAAY,SAAwB,WAAW;CAC7D,MAAM,qBAAqB,OAA+B,KAAK;CAC/D,MAAM,YAAY,OAAiB,EAAE,CAAC;CACtC,MAAM,gBAAgB,OAAO,WAAW;AACxC,eAAc,UAAU;CAExB,MAAM,YAAY,QACd,gBAAgB,mBAAmB,OAAO,MAAM,CAAC,CAAC,WAClD;CAEJ,MAAM,QAAQ,kBAAkB;AAC9B,qBAAmB,SAAS,OAAO;AACnC,qBAAmB,UAAU;AAC7B,YAAU,UAAU,EAAE;AACtB,YAAU,EAAE,CAAC;AACb,eAAa,MAAM;AACnB,WAAS,KAAK;IACb,EAAE,CAAC;CAEN,MAAM,WAAW,KAAK,UAAU,KAAK;CAErC,MAAM,SAAS,aACZ,iBAA0C;AACzC,MAAI,YAAY;AACd,YAAS,WAAW;AACpB;;AAIF,qBAAmB,SAAS,OAAO;AAEnC,eAAa,KAAK;AAClB,WAAS,KAAK;AACd,YAAU,EAAE,CAAC;AACb,YAAU,UAAU,EAAE;EAEtB,MAAM,kBAAkB,IAAI,iBAAiB;AAC7C,qBAAmB,UAAU;AAI7B,aAAW;GACT,KAAK;GACL,SAJc,eAAe,KAAK,UAAU,aAAa,GAAG;GAK5D,QAAQ,gBAAgB;GACxB,WAAW,OAAO,YAAY;AAC5B,QAAI,gBAAgB,OAAO,QAAS;AACpC,QAAI;KACF,MAAM,SAAS,KAAK,MAAM,QAAQ,KAAK;AAEvC,eAAU,UAAU,CAAC,GAAG,UAAU,SAAS,OAAiB;AAC5D,eAAU,UAAU,QAAQ;YACtB;;GAIV,UAAU,QAAQ;AAChB,QAAI,gBAAgB,OAAO,QAAS;AACpC,iBAAa,MAAM;AACnB,aAAS,eAAe,QAAQ,IAAI,UAAU,mBAAmB;;GAEpE,CAAC,CACC,WAAW;AACV,OAAI,gBAAgB,OAAO,QAAS;AAEpC,gBAAa,MAAM;AACnB,iBAAc,UAAU,UAAU,QAAQ;IAC1C,CACD,YAAY;AACX,OAAI,gBAAgB,OAAO,QAAS;AACpC,gBAAa,MAAM;AACnB,YAAS,mBAAmB;IAC5B;IAEN;EAAC;EAAW;EAAU;EAAW,CAClC;AAED,iBAAgB;AACd,MAAI,UACF,SAAQ;AAGV,eAAa;AACX,sBAAmB,SAAS,OAAO;;IAEpC,CAAC,QAAQ,UAAU,CAAC;AAEvB,QAAO;EAAE;EAAQ;EAAQ;EAAW;EAAO;EAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-status-indicator.d.ts","names":[],"sources":["../../src/react/resource-status-indicator.tsx"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"resource-status-indicator.d.ts","names":[],"sources":["../../src/react/resource-status-indicator.tsx"],"mappings":";;;;;;;UAeiB,oBAAA;EACf,KAAA,GAAQ,MAAA,EAAQ,cAAA;EAChB,WAAA,GACE,MAAA,EAAQ,cAAA,EACR,SAAA,EAAW,wBAAA;EAJsB;EAOnC,IAAA,GAAO,UAAA;AAAA;;UAIQ,4BAAA;EAJR;EAMP,IAAA;EANiB;EAQjB,SAAA,GAAY,MAAA,SAAe,oBAAA;EAdX;EAgBhB,cAAA;EAfA;EAiBA,MAAA,IAAU,SAAA,EAAW,wBAAA,KAA6B,KAAA,CAAM,SAAA;AAAA;;;;;;;iBA+B1C,wBAAA,CACd,OAAA,GAAS,4BAAA;AAAA,UAsFM,4BAAA,SACP,4BAAA,EAA8B,IAAA,CAAK,YAAA;;EAE3C,SAAA;AAAA;;;;;;;;;;;;;;;;;AA1FF;;;;;AAuFA;;;;;;;;;;;iBAsCgB,uBAAA,CAAA;EACd,IAAA;EACA,SAAA;EACA,cAAA;EACA,MAAA;EACA,QAAA;EAAA,GACG;AAAA,IACF,4BAAA,GAAiC,kBAAA,CAAA,GAAA,CAAA,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-status-indicator.js","names":["Toaster"],"sources":["../../src/react/resource-status-indicator.tsx"],"sourcesContent":["import type { LucideIcon } from \"lucide-react\";\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport { type ToasterProps, toast } from \"sonner\";\nimport {\n type AggregatedResourceStatus,\n type ResourceStatus,\n useResourceStatus,\n} from \"./hooks/use-resource-status\";\nimport { Toaster } from \"./ui/sonner\";\n\n/** ~1Hz tick driving the elapsed-time display between store events. */\nconst ELAPSED_TICK_MS = 1000;\n\n/** Per-kind copy + icon overrides for {@link ResourceStatusIndicator}. */\nexport interface ResourceKindRenderer {\n title: (status: ResourceStatus) => string;\n description: (\n status: ResourceStatus,\n aggregate: AggregatedResourceStatus,\n ) => string;\n /** Defaults to sonner's built-in loading/error icon. */\n icon?: LucideIcon;\n}\n\n/** Options shared by {@link ResourceStatusIndicator} and {@link useResourceStatusToaster}. */\nexport interface ResourceStatusToasterOptions {\n /** Restrict to a single resource kind. Otherwise shows the worst across all kinds. */\n kind?: string;\n /** Per-kind copy + icon overrides. */\n renderers?: Record<string, ResourceKindRenderer>;\n /** Class name applied to the toast (not the Toaster wrapper). */\n toastClassName?: string;\n /** Full custom render override, rendered inside `toast.custom`. */\n render?: (aggregate: AggregatedResourceStatus) => React.ReactNode;\n}\n\nconst DEFAULT_KIND_RENDERERS: Record<string, ResourceKindRenderer> = {\n warehouse: {\n title: (s) =>\n s.severity === \"error\"\n ? \"SQL warehouse unavailable\"\n : \"SQL warehouse warming up\",\n description: (s, agg) => {\n if (s.severity === \"error\") {\n return (\n s.summary ??\n \"The configured SQL warehouse is unavailable. Update DATABRICKS_WAREHOUSE_ID and reload.\"\n );\n }\n const labels = agg.affectedLabels.length;\n if (labels === 0) {\n return `Waiting for the warehouse to reach RUNNING · ${formatElapsed(agg.elapsedMs)}`;\n }\n return `${labels} ${labels === 1 ? \"query\" : \"queries\"} waiting · ${formatElapsed(agg.elapsedMs)}`;\n },\n },\n};\n\n/**\n * Drives a sticky sonner toast that mirrors the worst pending resource\n * status. Does not render anything — supply your own `<Toaster />`. Most\n * apps should prefer {@link ResourceStatusIndicator}; use this hook only\n * to share an existing Toaster with unrelated app toasts.\n */\nexport function useResourceStatusToaster(\n options: ResourceStatusToasterOptions = {},\n): void {\n const { kind, renderers, toastClassName, render } = options;\n const aggregate = useResourceStatus(kind ? { kind } : undefined);\n const worst = aggregate.worst;\n // Per-instance + per-kind toast id: instance scoping isolates multiple\n // indicators in the same tree; kind scoping forces dismiss-and-recreate\n // when severity flips between toast types (sonner can't morph\n // jsx/description cleanly across `custom` ↔ `loading`/`error`).\n const instanceId = useId();\n\n // The store is event-driven, so re-render at ~1Hz to keep the elapsed\n // counter advancing between status emissions.\n const [, forceTick] = useState(0);\n useEffect(() => {\n if (!worst) return;\n const id = setInterval(() => forceTick((n) => n + 1), ELAPSED_TICK_MS);\n return () => clearInterval(id);\n }, [worst]);\n\n const liveIdRef = useRef<string | null>(null);\n\n // Runs every render: cheap because sonner patches the same id in place.\n useEffect(() => {\n if (!worst) {\n if (liveIdRef.current) {\n toast.dismiss(liveIdRef.current);\n liveIdRef.current = null;\n }\n return;\n }\n\n // Live elapsed; the snapshot only refreshes on store events.\n const liveAggregate: AggregatedResourceStatus = {\n ...aggregate,\n elapsedMs: Math.max(0, Date.now() - worst.startedAt),\n };\n\n const nextId = `appkit-resource:${instanceId}:${worst.kind}:${worst.severity}`;\n if (liveIdRef.current && liveIdRef.current !== nextId) {\n toast.dismiss(liveIdRef.current);\n }\n liveIdRef.current = nextId;\n\n if (render) {\n const node = render(liveAggregate);\n toast.custom(() => <>{node}</>, {\n id: nextId,\n duration: Number.POSITIVE_INFINITY,\n className: toastClassName,\n });\n return;\n }\n\n const merged = { ...DEFAULT_KIND_RENDERERS, ...renderers };\n const renderer = merged[worst.kind];\n const title = renderer?.title(worst) ?? defaultTitle(worst);\n const description =\n renderer?.description(worst, liveAggregate) ?? defaultDescription(worst);\n const Icon = renderer?.icon;\n const opts = {\n id: nextId,\n description,\n duration: Number.POSITIVE_INFINITY,\n className: toastClassName,\n ...(Icon ? { icon: <Icon className=\"size-4\" /> } : {}),\n };\n\n if (worst.severity === \"error\") {\n toast.error(title, opts);\n } else {\n toast.loading(title, opts);\n }\n });\n\n // Dismiss on unmount so the toast doesn't outlive its mount point.\n useEffect(() => {\n return () => {\n if (liveIdRef.current) {\n toast.dismiss(liveIdRef.current);\n liveIdRef.current = null;\n }\n };\n }, []);\n}\n\nexport interface ResourceStatusIndicatorProps\n extends ResourceStatusToasterOptions,\n Omit<ToasterProps, \"className\"> {\n /** Class name applied to the Toaster wrapper. */\n className?: string;\n}\n\n/**\n * Drop-in indicator that mounts a `<Toaster />` and surfaces the worst\n * pending {@link ResourceStatus} across every plugin/component publishing\n * into the nearest {@link ResourceStatusProvider} as a sonner toast\n * (`toast.loading` for cold starts, `toast.error` for unrecoverable\n * states), keyed by the worst kind so only one indicator toast is on\n * screen at a time.\n *\n * Forwards `Toaster` props (`position` defaults to `top-right`, plus\n * `theme`, `richColors`, …). Apps that already mount their own\n * `<Toaster />` should drop this component and call\n * {@link useResourceStatusToaster} instead.\n *\n * @example\n * ```tsx\n * <ResourceStatusProvider>\n * <ResourceStatusIndicator />\n * <App />\n * </ResourceStatusProvider>\n * ```\n *\n * @example Custom render\n * ```tsx\n * <ResourceStatusIndicator\n * render={(agg) => (\n * <div className=\"my-card\">\n * {agg.worst?.kind} {agg.worst?.state.toLowerCase()}\n * </div>\n * )}\n * />\n * ```\n */\nexport function ResourceStatusIndicator({\n kind,\n renderers,\n toastClassName,\n render,\n position = \"top-right\",\n ...toasterProps\n}: ResourceStatusIndicatorProps = {}) {\n useResourceStatusToaster({ kind, renderers, toastClassName, render });\n return <Toaster position={position} {...toasterProps} />;\n}\n\nfunction defaultTitle(status: ResourceStatus): string {\n switch (status.severity) {\n case \"error\":\n return `${humanizeKind(status.kind)} unavailable`;\n case \"warning\":\n return `${humanizeKind(status.kind)} degraded`;\n default:\n return `${humanizeKind(status.kind)} not ready`;\n }\n}\n\nfunction defaultDescription(status: ResourceStatus): string {\n return status.summary ?? `Current state: ${status.state}.`;\n}\n\nfunction humanizeKind(kind: string): string {\n if (!kind) return \"Resource\";\n return kind\n .split(/[-_]/g)\n .filter(Boolean)\n .map((part) => part[0].toUpperCase() + part.slice(1))\n .join(\" \");\n}\n\nfunction formatElapsed(ms: number): string {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000));\n if (totalSeconds < 60) return `${totalSeconds}s`;\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = totalSeconds % 60;\n return seconds === 0 ? `${minutes}m` : `${minutes}m ${seconds}s`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resource-status-indicator.js","names":["Toaster"],"sources":["../../src/react/resource-status-indicator.tsx"],"sourcesContent":["import type { LucideIcon } from \"lucide-react\";\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport { type ToasterProps, toast } from \"sonner\";\n\nimport {\n type AggregatedResourceStatus,\n type ResourceStatus,\n useResourceStatus,\n} from \"./hooks/use-resource-status\";\nimport { Toaster } from \"./ui/sonner\";\n\n/** ~1Hz tick driving the elapsed-time display between store events. */\nconst ELAPSED_TICK_MS = 1000;\n\n/** Per-kind copy + icon overrides for {@link ResourceStatusIndicator}. */\nexport interface ResourceKindRenderer {\n title: (status: ResourceStatus) => string;\n description: (\n status: ResourceStatus,\n aggregate: AggregatedResourceStatus,\n ) => string;\n /** Defaults to sonner's built-in loading/error icon. */\n icon?: LucideIcon;\n}\n\n/** Options shared by {@link ResourceStatusIndicator} and {@link useResourceStatusToaster}. */\nexport interface ResourceStatusToasterOptions {\n /** Restrict to a single resource kind. Otherwise shows the worst across all kinds. */\n kind?: string;\n /** Per-kind copy + icon overrides. */\n renderers?: Record<string, ResourceKindRenderer>;\n /** Class name applied to the toast (not the Toaster wrapper). */\n toastClassName?: string;\n /** Full custom render override, rendered inside `toast.custom`. */\n render?: (aggregate: AggregatedResourceStatus) => React.ReactNode;\n}\n\nconst DEFAULT_KIND_RENDERERS: Record<string, ResourceKindRenderer> = {\n warehouse: {\n title: (s) =>\n s.severity === \"error\"\n ? \"SQL warehouse unavailable\"\n : \"SQL warehouse warming up\",\n description: (s, agg) => {\n if (s.severity === \"error\") {\n return (\n s.summary ??\n \"The configured SQL warehouse is unavailable. Update DATABRICKS_WAREHOUSE_ID and reload.\"\n );\n }\n const labels = agg.affectedLabels.length;\n if (labels === 0) {\n return `Waiting for the warehouse to reach RUNNING · ${formatElapsed(agg.elapsedMs)}`;\n }\n return `${labels} ${labels === 1 ? \"query\" : \"queries\"} waiting · ${formatElapsed(agg.elapsedMs)}`;\n },\n },\n};\n\n/**\n * Drives a sticky sonner toast that mirrors the worst pending resource\n * status. Does not render anything — supply your own `<Toaster />`. Most\n * apps should prefer {@link ResourceStatusIndicator}; use this hook only\n * to share an existing Toaster with unrelated app toasts.\n */\nexport function useResourceStatusToaster(\n options: ResourceStatusToasterOptions = {},\n): void {\n const { kind, renderers, toastClassName, render } = options;\n const aggregate = useResourceStatus(kind ? { kind } : undefined);\n const worst = aggregate.worst;\n // Per-instance + per-kind toast id: instance scoping isolates multiple\n // indicators in the same tree; kind scoping forces dismiss-and-recreate\n // when severity flips between toast types (sonner can't morph\n // jsx/description cleanly across `custom` ↔ `loading`/`error`).\n const instanceId = useId();\n\n // The store is event-driven, so re-render at ~1Hz to keep the elapsed\n // counter advancing between status emissions.\n const [, forceTick] = useState(0);\n useEffect(() => {\n if (!worst) return;\n const id = setInterval(() => forceTick((n) => n + 1), ELAPSED_TICK_MS);\n return () => clearInterval(id);\n }, [worst]);\n\n const liveIdRef = useRef<string | null>(null);\n\n // Runs every render: cheap because sonner patches the same id in place.\n useEffect(() => {\n if (!worst) {\n if (liveIdRef.current) {\n toast.dismiss(liveIdRef.current);\n liveIdRef.current = null;\n }\n return;\n }\n\n // Live elapsed; the snapshot only refreshes on store events.\n const liveAggregate: AggregatedResourceStatus = {\n ...aggregate,\n elapsedMs: Math.max(0, Date.now() - worst.startedAt),\n };\n\n const nextId = `appkit-resource:${instanceId}:${worst.kind}:${worst.severity}`;\n if (liveIdRef.current && liveIdRef.current !== nextId) {\n toast.dismiss(liveIdRef.current);\n }\n liveIdRef.current = nextId;\n\n if (render) {\n const node = render(liveAggregate);\n toast.custom(() => <>{node}</>, {\n id: nextId,\n duration: Number.POSITIVE_INFINITY,\n className: toastClassName,\n });\n return;\n }\n\n const merged = { ...DEFAULT_KIND_RENDERERS, ...renderers };\n const renderer = merged[worst.kind];\n const title = renderer?.title(worst) ?? defaultTitle(worst);\n const description =\n renderer?.description(worst, liveAggregate) ?? defaultDescription(worst);\n const Icon = renderer?.icon;\n const opts = {\n id: nextId,\n description,\n duration: Number.POSITIVE_INFINITY,\n className: toastClassName,\n ...(Icon ? { icon: <Icon className=\"size-4\" /> } : {}),\n };\n\n if (worst.severity === \"error\") {\n toast.error(title, opts);\n } else {\n toast.loading(title, opts);\n }\n });\n\n // Dismiss on unmount so the toast doesn't outlive its mount point.\n useEffect(() => {\n return () => {\n if (liveIdRef.current) {\n toast.dismiss(liveIdRef.current);\n liveIdRef.current = null;\n }\n };\n }, []);\n}\n\nexport interface ResourceStatusIndicatorProps\n extends ResourceStatusToasterOptions, Omit<ToasterProps, \"className\"> {\n /** Class name applied to the Toaster wrapper. */\n className?: string;\n}\n\n/**\n * Drop-in indicator that mounts a `<Toaster />` and surfaces the worst\n * pending {@link ResourceStatus} across every plugin/component publishing\n * into the nearest {@link ResourceStatusProvider} as a sonner toast\n * (`toast.loading` for cold starts, `toast.error` for unrecoverable\n * states), keyed by the worst kind so only one indicator toast is on\n * screen at a time.\n *\n * Forwards `Toaster` props (`position` defaults to `top-right`, plus\n * `theme`, `richColors`, …). Apps that already mount their own\n * `<Toaster />` should drop this component and call\n * {@link useResourceStatusToaster} instead.\n *\n * @example\n * ```tsx\n * <ResourceStatusProvider>\n * <ResourceStatusIndicator />\n * <App />\n * </ResourceStatusProvider>\n * ```\n *\n * @example Custom render\n * ```tsx\n * <ResourceStatusIndicator\n * render={(agg) => (\n * <div className=\"my-card\">\n * {agg.worst?.kind} {agg.worst?.state.toLowerCase()}\n * </div>\n * )}\n * />\n * ```\n */\nexport function ResourceStatusIndicator({\n kind,\n renderers,\n toastClassName,\n render,\n position = \"top-right\",\n ...toasterProps\n}: ResourceStatusIndicatorProps = {}) {\n useResourceStatusToaster({ kind, renderers, toastClassName, render });\n return <Toaster position={position} {...toasterProps} />;\n}\n\nfunction defaultTitle(status: ResourceStatus): string {\n switch (status.severity) {\n case \"error\":\n return `${humanizeKind(status.kind)} unavailable`;\n case \"warning\":\n return `${humanizeKind(status.kind)} degraded`;\n default:\n return `${humanizeKind(status.kind)} not ready`;\n }\n}\n\nfunction defaultDescription(status: ResourceStatus): string {\n return status.summary ?? `Current state: ${status.state}.`;\n}\n\nfunction humanizeKind(kind: string): string {\n if (!kind) return \"Resource\";\n return kind\n .split(/[-_]/g)\n .filter(Boolean)\n .map((part) => part[0].toUpperCase() + part.slice(1))\n .join(\" \");\n}\n\nfunction formatElapsed(ms: number): string {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000));\n if (totalSeconds < 60) return `${totalSeconds}s`;\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = totalSeconds % 60;\n return seconds === 0 ? `${minutes}m` : `${minutes}m ${seconds}s`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-table.d.ts","names":[],"sources":["../../../src/react/table/data-table.tsx"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"data-table.d.ts","names":[],"sources":["../../../src/react/table/data-table.tsx"],"mappings":";;;;;;;AAuEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,SAAA,CAAU,KAAA,EAAO,cAAA,GAAc,kBAAA,CAAA,GAAA,CAAA,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-table.js","names":["Table"],"sources":["../../../src/react/table/data-table.tsx"],"sourcesContent":["import { flexRender } from \"@tanstack/react-table\";\nimport { ChevronDown } from \"lucide-react\";\nimport { formatFieldLabel } from \"../lib/format\";\nimport { Button } from \"../ui/button\";\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from \"../ui/dropdown-menu\";\nimport { Input } from \"../ui/input\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"../ui/select\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"../ui/table\";\nimport { TableWrapper } from \"./table-wrapper\";\nimport type { DataTableLabels, DataTableProps } from \"./types\";\n\n/**\n * Production-ready data table with automatic data fetching and state management\n *\n * Features:\n * - Automatic column generation from data structure\n * - Integrated with useAnalyticsQuery for data fetching\n * - Built-in loading, error, and empty states\n * - Dynamic filtering, sorting and pagination\n * - Column visibility controls\n * - Responsive design\n * - Supports opinionated mode (auto columns) and full-control mode (`children(table)`)\n *\n * @param props - Props for the DataTable component\n * @param props.queryKey - The query key to fetch the data\n * @param props.parameters - The parameters to pass to the query. Required - use `{}` if none.\n * @param props.filterColumn - The column to filter by\n * @param props.filterPlaceholder - The placeholder for the filter input\n * @param props.children - Optional children for full control mode\n * @returns - The rendered data table component\n *\n * @example\n * // Opinionated mode\n * <DataTable\n * queryKey=\"users-list\"\n * parameters={{ status: \"active\" }}\n * filterColumn=\"email\"\n * filterPlaceholder=\"Filter by email...\"\n * />\n * @example\n * // full control mode\n * <DataTable queryKey=\"users-list\" parameters={{ status: \"active\" }}>\n * {(table) => (\n * <div>\n * <h2>Custom Table UI</h2>\n * {table.getRowModel().rows.map(row => (\n * <div key={row.id}>{row.original.name}</div>\n * ))}\n * </div>\n * )}\n * </DataTable>\n */\nexport function DataTable(props: DataTableProps) {\n const {\n parameters,\n queryKey,\n filterColumn,\n filterPlaceholder,\n transform,\n labels,\n ariaLabel,\n testId,\n className,\n enableRowSelection,\n onRowSelectionChange,\n children,\n pageSize = 10,\n pageSizeOptions = [10, 25, 50, 100],\n } = props;\n\n const defaultLabels: Required<DataTableLabels> = {\n columnsButton: \"Columns\",\n noResults: \"No results found.\",\n rowsFound: `\\${count} row(s) found`,\n previousButton: \"Previous\",\n nextButton: \"Next\",\n rowsPerPage: \"Rows per page\",\n showing: `Showing \\${from} to \\${to} of \\${total}`,\n };\n\n const finalLabels = { ...defaultLabels, ...labels };\n\n return (\n <TableWrapper\n queryKey={queryKey}\n parameters={parameters}\n ariaLabel={ariaLabel}\n testId={testId}\n className={className}\n transformer={transform}\n enableRowSelection={enableRowSelection}\n onRowSelectionChange={onRowSelectionChange}\n pageSize={pageSize}\n >\n {(table) => {\n if (children) {\n return children(table);\n }\n\n const data = table.options.data;\n\n const defaultFilterColumn =\n filterColumn ||\n (data && data.length > 0\n ? Object.keys(data[0] as Record<string, any>).find(\n (key) =>\n typeof (data[0] as Record<string, any>)[key] === \"string\",\n )\n : null);\n\n const totalRows = table.getFilteredRowModel().rows.length;\n const currentPage = table.getState().pagination.pageIndex + 1;\n const currentPageSize = table.getState().pagination.pageSize;\n const fromRow =\n totalRows === 0 ? 0 : (currentPage - 1) * currentPageSize + 1;\n const toRow = Math.min(currentPage * currentPageSize, totalRows);\n\n return (\n <div className=\"w-full\">\n <div className=\"flex items-center py-4 gap-2\">\n {defaultFilterColumn && (\n <Input\n placeholder={\n filterPlaceholder ||\n `Filter by ${formatFieldLabel(defaultFilterColumn)}...`\n }\n value={\n (table\n .getColumn(defaultFilterColumn)\n ?.getFilterValue() as string) ?? \"\"\n }\n onChange={(event) =>\n table\n .getColumn(defaultFilterColumn)\n ?.setFilterValue(event.target.value)\n }\n className=\"max-w-sm\"\n />\n )}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" className=\"ml-auto\">\n {finalLabels.columnsButton}{\" \"}\n <ChevronDown className=\"ml-2 h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {table\n .getAllColumns()\n .filter((column) => column.getCanHide())\n .map((column) => {\n return (\n <DropdownMenuCheckboxItem\n key={column.id}\n className=\"capitalize\"\n checked={column.getIsVisible()}\n onCheckedChange={(value) =>\n column.toggleVisibility(!!value)\n }\n >\n {formatFieldLabel(column.id)}\n </DropdownMenuCheckboxItem>\n );\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n <div className=\"overflow-hidden rounded-md border border-border\">\n <div className=\"max-h-[600px] overflow-y-auto\">\n <Table>\n <TableHeader className=\"sticky top-0 bg-background z-10\">\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {headerGroup.headers.map((header) => {\n const isSelectColumn = header.column.id === \"select\";\n return (\n <TableHead\n key={header.id}\n style={{\n width: header.getSize(),\n position: \"relative\",\n }}\n className={\n isSelectColumn ? \"w-[40px] p-0\" : undefined\n }\n >\n {header.isPlaceholder\n ? null\n : flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </TableHead>\n );\n })}\n </TableRow>\n ))}\n </TableHeader>\n <TableBody>\n {table.getRowModel().rows?.length ? (\n table.getRowModel().rows.map((row) => (\n <TableRow\n key={row.id}\n data-state={row.getIsSelected() && \"selected\"}\n >\n {row.getVisibleCells().map((cell) => {\n const isSelectColumn = cell.column.id === \"select\";\n return (\n <TableCell\n key={cell.id}\n style={{\n width: cell.column.getSize(),\n }}\n className={\n isSelectColumn ? \"w-[40px] p-0\" : undefined\n }\n >\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </TableCell>\n );\n })}\n </TableRow>\n ))\n ) : (\n <TableRow>\n <TableCell\n colSpan={table.getAllColumns().length}\n className=\"h-24 text-center\"\n >\n {finalLabels.noResults}\n </TableCell>\n </TableRow>\n )}\n </TableBody>\n </Table>\n </div>\n </div>\n {/* On mobile, flex-col-reverse places pagination buttons above the info row for easier thumb access */}\n <div className=\"flex flex-col-reverse sm:flex-row items-start sm:items-center justify-between gap-3 py-4\">\n <div className=\"flex flex-wrap items-center gap-3 sm:gap-6\">\n <div className=\"text-foreground text-sm\">\n {totalRows > 0\n ? finalLabels.showing\n .replace(`\\${from}`, fromRow.toString())\n .replace(`\\${to}`, toRow.toString())\n .replace(`\\${total}`, totalRows.toString())\n : finalLabels.noResults}\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-foreground\">\n {finalLabels.rowsPerPage}\n </span>\n <Select\n value={currentPageSize.toString()}\n onValueChange={(value) => {\n table.setPageSize(Number(value));\n }}\n >\n <SelectTrigger className=\"w-[70px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {pageSizeOptions.map((size) => (\n <SelectItem key={size} value={size.toString()}>\n {size}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n <div className=\"space-x-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => table.previousPage()}\n disabled={!table.getCanPreviousPage()}\n >\n {finalLabels.previousButton}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => table.nextPage()}\n disabled={!table.getCanNextPage()}\n >\n {finalLabels.nextButton}\n </Button>\n </div>\n </div>\n </div>\n );\n }}\n </TableWrapper>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"data-table.js","names":["Table"],"sources":["../../../src/react/table/data-table.tsx"],"sourcesContent":["import { flexRender } from \"@tanstack/react-table\";\nimport { ChevronDown } from \"lucide-react\";\n\nimport { formatFieldLabel } from \"../lib/format\";\nimport { Button } from \"../ui/button\";\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from \"../ui/dropdown-menu\";\nimport { Input } from \"../ui/input\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"../ui/select\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"../ui/table\";\nimport { TableWrapper } from \"./table-wrapper\";\nimport type { DataTableLabels, DataTableProps } from \"./types\";\n\n/**\n * Production-ready data table with automatic data fetching and state management\n *\n * Features:\n * - Automatic column generation from data structure\n * - Integrated with useAnalyticsQuery for data fetching\n * - Built-in loading, error, and empty states\n * - Dynamic filtering, sorting and pagination\n * - Column visibility controls\n * - Responsive design\n * - Supports opinionated mode (auto columns) and full-control mode (`children(table)`)\n *\n * @param props - Props for the DataTable component\n * @param props.queryKey - The query key to fetch the data\n * @param props.parameters - The parameters to pass to the query. Required - use `{}` if none.\n * @param props.filterColumn - The column to filter by\n * @param props.filterPlaceholder - The placeholder for the filter input\n * @param props.children - Optional children for full control mode\n * @returns - The rendered data table component\n *\n * @example\n * // Opinionated mode\n * <DataTable\n * queryKey=\"users-list\"\n * parameters={{ status: \"active\" }}\n * filterColumn=\"email\"\n * filterPlaceholder=\"Filter by email...\"\n * />\n * @example\n * // full control mode\n * <DataTable queryKey=\"users-list\" parameters={{ status: \"active\" }}>\n * {(table) => (\n * <div>\n * <h2>Custom Table UI</h2>\n * {table.getRowModel().rows.map(row => (\n * <div key={row.id}>{row.original.name}</div>\n * ))}\n * </div>\n * )}\n * </DataTable>\n */\nexport function DataTable(props: DataTableProps) {\n const {\n parameters,\n queryKey,\n filterColumn,\n filterPlaceholder,\n transform,\n labels,\n ariaLabel,\n testId,\n className,\n enableRowSelection,\n onRowSelectionChange,\n children,\n pageSize = 10,\n pageSizeOptions = [10, 25, 50, 100],\n } = props;\n\n const defaultLabels: Required<DataTableLabels> = {\n columnsButton: \"Columns\",\n noResults: \"No results found.\",\n rowsFound: `\\${count} row(s) found`,\n previousButton: \"Previous\",\n nextButton: \"Next\",\n rowsPerPage: \"Rows per page\",\n showing: `Showing \\${from} to \\${to} of \\${total}`,\n };\n\n const finalLabels = { ...defaultLabels, ...labels };\n\n return (\n <TableWrapper\n queryKey={queryKey}\n parameters={parameters}\n ariaLabel={ariaLabel}\n testId={testId}\n className={className}\n transformer={transform}\n enableRowSelection={enableRowSelection}\n onRowSelectionChange={onRowSelectionChange}\n pageSize={pageSize}\n >\n {(table) => {\n if (children) {\n return children(table);\n }\n\n const data = table.options.data;\n\n const defaultFilterColumn =\n filterColumn ||\n (data && data.length > 0\n ? Object.keys(data[0] as Record<string, any>).find(\n (key) =>\n typeof (data[0] as Record<string, any>)[key] === \"string\",\n )\n : null);\n\n const totalRows = table.getFilteredRowModel().rows.length;\n const currentPage = table.getState().pagination.pageIndex + 1;\n const currentPageSize = table.getState().pagination.pageSize;\n const fromRow =\n totalRows === 0 ? 0 : (currentPage - 1) * currentPageSize + 1;\n const toRow = Math.min(currentPage * currentPageSize, totalRows);\n\n return (\n <div className=\"w-full\">\n <div className=\"flex items-center py-4 gap-2\">\n {defaultFilterColumn && (\n <Input\n placeholder={\n filterPlaceholder ||\n `Filter by ${formatFieldLabel(defaultFilterColumn)}...`\n }\n value={\n (table\n .getColumn(defaultFilterColumn)\n ?.getFilterValue() as string) ?? \"\"\n }\n onChange={(event) =>\n table\n .getColumn(defaultFilterColumn)\n ?.setFilterValue(event.target.value)\n }\n className=\"max-w-sm\"\n />\n )}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" className=\"ml-auto\">\n {finalLabels.columnsButton}{\" \"}\n <ChevronDown className=\"ml-2 h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {table\n .getAllColumns()\n .filter((column) => column.getCanHide())\n .map((column) => {\n return (\n <DropdownMenuCheckboxItem\n key={column.id}\n className=\"capitalize\"\n checked={column.getIsVisible()}\n onCheckedChange={(value) =>\n column.toggleVisibility(!!value)\n }\n >\n {formatFieldLabel(column.id)}\n </DropdownMenuCheckboxItem>\n );\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n <div className=\"overflow-hidden rounded-md border border-border\">\n <div className=\"max-h-[600px] overflow-y-auto\">\n <Table>\n <TableHeader className=\"sticky top-0 bg-background z-10\">\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {headerGroup.headers.map((header) => {\n const isSelectColumn = header.column.id === \"select\";\n return (\n <TableHead\n key={header.id}\n style={{\n width: header.getSize(),\n position: \"relative\",\n }}\n className={\n isSelectColumn ? \"w-[40px] p-0\" : undefined\n }\n >\n {header.isPlaceholder\n ? null\n : flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </TableHead>\n );\n })}\n </TableRow>\n ))}\n </TableHeader>\n <TableBody>\n {table.getRowModel().rows?.length ? (\n table.getRowModel().rows.map((row) => (\n <TableRow\n key={row.id}\n data-state={row.getIsSelected() && \"selected\"}\n >\n {row.getVisibleCells().map((cell) => {\n const isSelectColumn = cell.column.id === \"select\";\n return (\n <TableCell\n key={cell.id}\n style={{\n width: cell.column.getSize(),\n }}\n className={\n isSelectColumn ? \"w-[40px] p-0\" : undefined\n }\n >\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </TableCell>\n );\n })}\n </TableRow>\n ))\n ) : (\n <TableRow>\n <TableCell\n colSpan={table.getAllColumns().length}\n className=\"h-24 text-center\"\n >\n {finalLabels.noResults}\n </TableCell>\n </TableRow>\n )}\n </TableBody>\n </Table>\n </div>\n </div>\n {/* On mobile, flex-col-reverse places pagination buttons above the info row for easier thumb access */}\n <div className=\"flex flex-col-reverse sm:flex-row items-start sm:items-center justify-between gap-3 py-4\">\n <div className=\"flex flex-wrap items-center gap-3 sm:gap-6\">\n <div className=\"text-foreground text-sm\">\n {totalRows > 0\n ? finalLabels.showing\n .replace(`\\${from}`, fromRow.toString())\n .replace(`\\${to}`, toRow.toString())\n .replace(`\\${total}`, totalRows.toString())\n : finalLabels.noResults}\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-foreground\">\n {finalLabels.rowsPerPage}\n </span>\n <Select\n value={currentPageSize.toString()}\n onValueChange={(value) => {\n table.setPageSize(Number(value));\n }}\n >\n <SelectTrigger className=\"w-[70px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {pageSizeOptions.map((size) => (\n <SelectItem key={size} value={size.toString()}>\n {size}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n <div className=\"space-x-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => table.previousPage()}\n disabled={!table.getCanPreviousPage()}\n >\n {finalLabels.previousButton}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => table.nextPage()}\n disabled={!table.getCanNextPage()}\n >\n {finalLabels.nextButton}\n </Button>\n </div>\n </div>\n </div>\n );\n }}\n </TableWrapper>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table-wrapper.js","names":[],"sources":["../../../src/react/table/table-wrapper.tsx"],"sourcesContent":["import {\n type Column,\n type ColumnFiltersState,\n getCoreRowModel,\n getFilteredRowModel,\n getPaginationRowModel,\n getSortedRowModel,\n type Row,\n type RowSelectionState,\n type SortingState,\n type Table,\n useReactTable,\n type VisibilityState,\n} from \"@tanstack/react-table\";\nimport { ArrowUpDown } from \"lucide-react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useAnalyticsQuery } from \"..\";\nimport {\n formatChartValue,\n formatFieldLabel,\n SAFE_KEY_REGEX,\n} from \"../lib/format\";\nimport { Button } from \"../ui/button\";\nimport { Checkbox } from \"../ui/checkbox\";\nimport { EmptyState } from \"./empty\";\nimport { ErrorState } from \"./error\";\nimport { LoadingSkeleton } from \"./loading\";\nimport type { TableWrapperProps } from \"./types\";\n\nconst CHECKBOX_COLUMN_WIDTH = 40;\n\n/**\n * Wrapper component for tables with automatic data fetching and state management\n * This component handles:\n * - Data fetching via useAnalyticsQuery\n * - Loading, error, and empty states with proper UI components\n * - Data transformation (optional)\n * - Dynamic column generation from data structure\n * - TanStack Table instance creation with all features (sorting, filtering, pagination, etc.)\n *\n * @template TRaw - The raw data type returned by the analytics query\n * @template TProcessed - The processed data type after transformation\n *\n * @param props - Props for the TableWrapper component\n * @param props.queryKey - The query key to fetch the data\n * @param props.parameters - The parameters to pass to the query\n * @param props.transformer - Optional function to transform raw data before creating table\n * @param props.children - Render function that receives the TanStack Table instance\n * @param props.className - Optional CSS class name for the wrapper\n * @param props.ariaLabel - Optional accessibility label\n * @param props.testId - Optional test ID for testing\n *\n * @returns The rendered table with state management\n */\nexport function TableWrapper<TRaw = any, TProcessed = any>(\n props: TableWrapperProps<TRaw, TProcessed>,\n) {\n const {\n queryKey,\n parameters,\n transformer,\n children,\n className,\n ariaLabel,\n testId,\n enableRowSelection = false,\n onRowSelectionChange,\n pageSize = 10,\n } = props;\n\n const [sorting, setSorting] = useState<SortingState>([]);\n const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);\n const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});\n const [rowSelection, setRowSelection] = useState<RowSelectionState>({});\n\n const { data, loading, error } = useAnalyticsQuery<TRaw[]>(\n queryKey,\n parameters,\n );\n\n useEffect(() => {\n if (onRowSelectionChange && enableRowSelection) {\n onRowSelectionChange(rowSelection);\n }\n }, [rowSelection, onRowSelectionChange, enableRowSelection]);\n\n const hasData = data && data.length > 0;\n\n const processedData = hasData\n ? transformer\n ? transformer(data)\n : (data as unknown as TProcessed[])\n : [];\n\n const tableColumns = useMemo(() => {\n if (!hasData) return [];\n\n if (!processedData[0] || typeof processedData[0] !== \"object\") {\n console.warn(\"Invalid data format for DataTable\");\n return [];\n }\n\n const dataColumns = Object.keys(processedData[0] as object)\n .filter((key) => SAFE_KEY_REGEX.test(key))\n .map((key) => {\n const formattedLabel = formatFieldLabel(key);\n return {\n accessorKey: key,\n header: ({ column }: { column: Column<TProcessed> }) => {\n return (\n <Button\n variant=\"ghost\"\n onClick={() =>\n column.toggleSorting(column.getIsSorted() === \"asc\")\n }\n className=\"h-8 px-2\"\n >\n {formattedLabel}\n <ArrowUpDown className=\"ml-2 h-4 w-4\" />\n </Button>\n );\n },\n cell: ({ row }: { row: Row<TProcessed> }) => {\n const value = row.getValue(key);\n if (typeof value === \"number\" || Number.isFinite(Number(value))) {\n return (\n <div className=\"text-right font-mono\">\n {formatChartValue(Number(value), key)}\n </div>\n );\n }\n return <div>{String(value)}</div>;\n },\n };\n });\n\n if (enableRowSelection) {\n return [\n {\n id: \"select\",\n maxSize: CHECKBOX_COLUMN_WIDTH,\n minSize: CHECKBOX_COLUMN_WIDTH,\n header: ({ table }: { table: Table<TProcessed> }) => (\n <div className=\"flex items-center justify-center\">\n <Checkbox\n checked={\n table.getIsAllPageRowsSelected() ||\n (table.getIsSomePageRowsSelected() && \"indeterminate\")\n }\n onCheckedChange={(value) =>\n table.toggleAllPageRowsSelected(!!value)\n }\n aria-label=\"Select all\"\n />\n </div>\n ),\n cell: ({ row }: { row: Row<TProcessed> }) => (\n <div className=\"flex items-center justify-center\">\n <Checkbox\n checked={row.getIsSelected()}\n onCheckedChange={(value) => row.toggleSelected(!!value)}\n aria-label=\"Select row\"\n />\n </div>\n ),\n enableSorting: false,\n enableHiding: false,\n },\n ...dataColumns,\n ];\n }\n\n return dataColumns;\n }, [hasData, processedData, enableRowSelection]);\n\n const table = useReactTable({\n data: processedData,\n columns: tableColumns,\n onSortingChange: setSorting,\n onColumnFiltersChange: setColumnFilters,\n getCoreRowModel: getCoreRowModel(),\n getPaginationRowModel: getPaginationRowModel(),\n getSortedRowModel: getSortedRowModel(),\n getFilteredRowModel: getFilteredRowModel(),\n onColumnVisibilityChange: setColumnVisibility,\n onRowSelectionChange: setRowSelection,\n state: {\n sorting,\n columnFilters,\n columnVisibility,\n rowSelection,\n },\n initialState: {\n pagination: {\n pageSize: pageSize,\n },\n },\n });\n\n if (loading) return <LoadingSkeleton />;\n if (error)\n return (\n <ErrorState error={typeof error === \"string\" ? error : \"Unknown error\"} />\n );\n\n if (!hasData) return <EmptyState />;\n\n return (\n <section className={className} aria-label={ariaLabel} data-testid={testId}>\n {children(table)}\n </section>\n );\n}\n"],"mappings":";;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"table-wrapper.js","names":[],"sources":["../../../src/react/table/table-wrapper.tsx"],"sourcesContent":["import {\n type Column,\n type ColumnFiltersState,\n getCoreRowModel,\n getFilteredRowModel,\n getPaginationRowModel,\n getSortedRowModel,\n type Row,\n type RowSelectionState,\n type SortingState,\n type Table,\n useReactTable,\n type VisibilityState,\n} from \"@tanstack/react-table\";\nimport { ArrowUpDown } from \"lucide-react\";\nimport { useEffect, useMemo, useState } from \"react\";\n\nimport { useAnalyticsQuery } from \"..\";\nimport {\n formatChartValue,\n formatFieldLabel,\n SAFE_KEY_REGEX,\n} from \"../lib/format\";\nimport { Button } from \"../ui/button\";\nimport { Checkbox } from \"../ui/checkbox\";\nimport { EmptyState } from \"./empty\";\nimport { ErrorState } from \"./error\";\nimport { LoadingSkeleton } from \"./loading\";\nimport type { TableWrapperProps } from \"./types\";\n\nconst CHECKBOX_COLUMN_WIDTH = 40;\n\n/**\n * Wrapper component for tables with automatic data fetching and state management\n * This component handles:\n * - Data fetching via useAnalyticsQuery\n * - Loading, error, and empty states with proper UI components\n * - Data transformation (optional)\n * - Dynamic column generation from data structure\n * - TanStack Table instance creation with all features (sorting, filtering, pagination, etc.)\n *\n * @template TRaw - The raw data type returned by the analytics query\n * @template TProcessed - The processed data type after transformation\n *\n * @param props - Props for the TableWrapper component\n * @param props.queryKey - The query key to fetch the data\n * @param props.parameters - The parameters to pass to the query\n * @param props.transformer - Optional function to transform raw data before creating table\n * @param props.children - Render function that receives the TanStack Table instance\n * @param props.className - Optional CSS class name for the wrapper\n * @param props.ariaLabel - Optional accessibility label\n * @param props.testId - Optional test ID for testing\n *\n * @returns The rendered table with state management\n */\nexport function TableWrapper<TRaw = any, TProcessed = any>(\n props: TableWrapperProps<TRaw, TProcessed>,\n) {\n const {\n queryKey,\n parameters,\n transformer,\n children,\n className,\n ariaLabel,\n testId,\n enableRowSelection = false,\n onRowSelectionChange,\n pageSize = 10,\n } = props;\n\n const [sorting, setSorting] = useState<SortingState>([]);\n const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);\n const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});\n const [rowSelection, setRowSelection] = useState<RowSelectionState>({});\n\n const { data, loading, error } = useAnalyticsQuery<TRaw[]>(\n queryKey,\n parameters,\n );\n\n useEffect(() => {\n if (onRowSelectionChange && enableRowSelection) {\n onRowSelectionChange(rowSelection);\n }\n }, [rowSelection, onRowSelectionChange, enableRowSelection]);\n\n const hasData = data && data.length > 0;\n\n const processedData = hasData\n ? transformer\n ? transformer(data)\n : (data as unknown as TProcessed[])\n : [];\n\n const tableColumns = useMemo(() => {\n if (!hasData) return [];\n\n if (!processedData[0] || typeof processedData[0] !== \"object\") {\n console.warn(\"Invalid data format for DataTable\");\n return [];\n }\n\n const dataColumns = Object.keys(processedData[0] as object)\n .filter((key) => SAFE_KEY_REGEX.test(key))\n .map((key) => {\n const formattedLabel = formatFieldLabel(key);\n return {\n accessorKey: key,\n header: ({ column }: { column: Column<TProcessed> }) => {\n return (\n <Button\n variant=\"ghost\"\n onClick={() =>\n column.toggleSorting(column.getIsSorted() === \"asc\")\n }\n className=\"h-8 px-2\"\n >\n {formattedLabel}\n <ArrowUpDown className=\"ml-2 h-4 w-4\" />\n </Button>\n );\n },\n cell: ({ row }: { row: Row<TProcessed> }) => {\n const value = row.getValue(key);\n if (typeof value === \"number\" || Number.isFinite(Number(value))) {\n return (\n <div className=\"text-right font-mono\">\n {formatChartValue(Number(value), key)}\n </div>\n );\n }\n return <div>{String(value)}</div>;\n },\n };\n });\n\n if (enableRowSelection) {\n return [\n {\n id: \"select\",\n maxSize: CHECKBOX_COLUMN_WIDTH,\n minSize: CHECKBOX_COLUMN_WIDTH,\n header: ({ table }: { table: Table<TProcessed> }) => (\n <div className=\"flex items-center justify-center\">\n <Checkbox\n checked={\n table.getIsAllPageRowsSelected() ||\n (table.getIsSomePageRowsSelected() && \"indeterminate\")\n }\n onCheckedChange={(value) =>\n table.toggleAllPageRowsSelected(!!value)\n }\n aria-label=\"Select all\"\n />\n </div>\n ),\n cell: ({ row }: { row: Row<TProcessed> }) => (\n <div className=\"flex items-center justify-center\">\n <Checkbox\n checked={row.getIsSelected()}\n onCheckedChange={(value) => row.toggleSelected(!!value)}\n aria-label=\"Select row\"\n />\n </div>\n ),\n enableSorting: false,\n enableHiding: false,\n },\n ...dataColumns,\n ];\n }\n\n return dataColumns;\n }, [hasData, processedData, enableRowSelection]);\n\n const table = useReactTable({\n data: processedData,\n columns: tableColumns,\n onSortingChange: setSorting,\n onColumnFiltersChange: setColumnFilters,\n getCoreRowModel: getCoreRowModel(),\n getPaginationRowModel: getPaginationRowModel(),\n getSortedRowModel: getSortedRowModel(),\n getFilteredRowModel: getFilteredRowModel(),\n onColumnVisibilityChange: setColumnVisibility,\n onRowSelectionChange: setRowSelection,\n state: {\n sorting,\n columnFilters,\n columnVisibility,\n rowSelection,\n },\n initialState: {\n pagination: {\n pageSize: pageSize,\n },\n },\n });\n\n if (loading) return <LoadingSkeleton />;\n if (error)\n return (\n <ErrorState error={typeof error === \"string\" ? error : \"Unknown error\"} />\n );\n\n if (!hasData) return <EmptyState />;\n\n return (\n <section className={className} aria-label={ariaLabel} data-testid={testId}>\n {children(table)}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"variants.d.ts","names":[],"sources":["../../../src/react/ui-variants/variants.tsx"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"variants.d.ts","names":[],"sources":["../../../src/react/ui-variants/variants.tsx"],"mappings":";;;;;UAqCiB,aAAA;;AAAjB;;;;;EAOE,OAAA;EAEU;EAAV,QAAA,EAAU,SAAA;EAUV;;;;AA0DF;;;EA5DE,MAAA;EA8DA;EA5DA,SAAA;EA8DA;;;;;;;;EArDA,MAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiDc,QAAA,CAAA;EACd,OAAA;EACA,QAAA;EACA,MAAA;EACA,SAAA;EACA;AAAA,GACC,aAAA,GAAa,kBAAA,CAAA,GAAA,CAAA,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"variants.js","names":[],"sources":["../../../src/react/ui-variants/variants.tsx"],"sourcesContent":["import {\n Check,\n CheckCheck,\n ChevronLeft,\n ChevronRight,\n Loader2,\n} from \"lucide-react\";\nimport {\n Children,\n type ComponentProps,\n type CSSProperties,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useMemo,\n useState,\n} from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Variant, type VariantProps } from \"./variant\";\n\n/** Endpoint the recorder plugin (`uiVariants()`) mounts in development. */\nconst CONFIRM_ENDPOINT = \"/api/ui-variants/confirm\";\n\n/** Abort the confirm request after this long so the button can't hang. */\nconst CONFIRM_TIMEOUT_MS = 5000;\n\n/**\n * Default switcher accent — a violet chosen to stand apart from most apps'\n * `primary`, so the dev-only picker chrome doesn't blend into the content it\n * wraps. Override per-block via the `accent` prop.\n */\nconst DEFAULT_ACCENT = \"#8b5cf6\";\n\ntype ConfirmState = \"idle\" | \"saving\" | \"recorded\" | \"unavailable\" | \"error\";\n\n/** Props for {@link Variants}. */\nexport interface VariantsProps {\n /**\n * Stable, unique identifier for this block. The variant-picking agent matches\n * on it to finalize the chosen variant into source, so it must be unique per\n * file. (Named `blockId`, not `id`, because it is a semantic key — not a DOM\n * element id.)\n */\n blockId: string;\n /** `<Variant>` children — one candidate UI each. */\n children: ReactNode;\n /**\n * How the wrapper flows in the layout. Defaults to `\"block\"` (full width,\n * variants stack vertically) — the right choice for page sections, heroes,\n * and other block-level regions, which is the common case. Use `\"inline\"`\n * when wrapping a small inline element such as a single button, so the\n * wrapper hugs its content instead of spanning the row.\n */\n layout?: \"block\" | \"inline\";\n /** Class name applied to the block wrapper (merged after `layout`). */\n className?: string;\n /**\n * Any CSS color for the switcher chrome (dot, ring, control cluster, count\n * pill, confirm tick). Every accent element derives from this one value.\n * Defaults to {@link DEFAULT_ACCENT}. The error state stays red regardless.\n *\n * @example accent=\"var(--primary)\" // match the app theme\n * @example accent=\"#f43f5e\" // rose\n */\n accent?: string;\n}\n\nfunction isVariantElement(node: ReactNode): node is ReactElement<VariantProps> {\n return isValidElement(node) && node.type === Variant;\n}\n\n/**\n * Dev-time UI picker: renders several `<Variant>` candidates one at a time.\n *\n * At rest the block shows a small, slowly-pulsing corner dot so it's clear\n * it's \"in variant state\" without hovering — the dot pulses while a choice is\n * still pending and goes calm once recorded. On hover (or focus) an accent\n * ring outlines the block and the dot gives way to a compact control cluster\n * (‹ index/total › ✓) that scales out of the corner. Confirming POSTs the\n * choice to the `uiVariants()` recorder plugin; a coding agent then finalizes\n * the chosen variant into source, removing this wrapper.\n *\n * All switcher chrome uses the `accent` color (default {@link DEFAULT_ACCENT},\n * a violet distinct from most apps' `primary`) so it doesn't blend into the\n * content it wraps.\n *\n * Degrades gracefully. In a production build the picker chrome is dropped\n * entirely and only the first variant renders as plain content — a safety net\n * for a block that shipped without being finalized. In dev, if the recorder\n * endpoint is absent (feature off) the switcher still works as a viewer and\n * Confirm reports that recording is unavailable.\n *\n * @example Page section (default layout — full-width, stacks vertically)\n * ```tsx\n * <Variants blockId=\"hero\">\n * <Variant label=\"Split\"><section>…</section></Variant>\n * <Variant label=\"Centered\"><section>…</section></Variant>\n * </Variants>\n * ```\n *\n * @example Small inline element (hug the content)\n * ```tsx\n * <Variants blockId=\"hero-cta\" layout=\"inline\">\n * <Variant label=\"Ghost\"><Button variant=\"ghost\">Get started</Button></Variant>\n * <Variant label=\"Solid\"><Button>Get started</Button></Variant>\n * </Variants>\n * ```\n *\n * @example Custom accent\n * ```tsx\n * <Variants blockId=\"hero\" accent=\"#f43f5e\">…</Variants>\n * ```\n */\nexport function Variants({\n blockId,\n children,\n layout = \"block\",\n className,\n accent = DEFAULT_ACCENT,\n}: VariantsProps) {\n const variants = useMemo(\n () => Children.toArray(children).filter(isVariantElement),\n [children],\n );\n const [active, setActive] = useState(0);\n const [confirmState, setConfirmState] = useState<ConfirmState>(\"idle\");\n\n if (variants.length === 0) return null;\n if (!import.meta.env.DEV) return <>{variants[0]}</>;\n\n const clampedActive = Math.min(active, variants.length - 1);\n const current = variants[clampedActive];\n const label = current.props.label ?? `Variant ${clampedActive + 1}`;\n const atStart = clampedActive === 0;\n const atEnd = clampedActive === variants.length - 1;\n\n const go = (delta: number) => {\n // No wrap-around: clamp within bounds.\n setActive((i) => Math.max(0, Math.min(variants.length - 1, i + delta)));\n setConfirmState(\"idle\");\n };\n\n const confirm = async () => {\n if (confirmState === \"saving\") return;\n setConfirmState(\"saving\");\n\n // Bound the request so a hung recorder can't leave the button stuck in\n // \"saving\" forever.\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), CONFIRM_TIMEOUT_MS);\n try {\n const res = await fetch(CONFIRM_ENDPOINT, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ blockId, chosenIndex: clampedActive, label }),\n signal: controller.signal,\n });\n // 404/403: the recorder isn't mounted (feature off or a production\n // build). Degrade to a plain viewer.\n if (res.status === 404 || res.status === 403) {\n setConfirmState(\"unavailable\");\n return;\n }\n // Any other non-OK status (e.g. a 500) is a failure the user can retry.\n setConfirmState(res.ok ? \"recorded\" : \"error\");\n } catch {\n // Network failure or timeout abort — the request never completed.\n setConfirmState(\"error\");\n } finally {\n clearTimeout(timeout);\n }\n };\n\n return (\n <div\n data-slot=\"variants\"\n // All accent chrome derives from this one variable; children read it via\n // the cascade, so the `accent` prop flows everywhere without prop-drilling.\n style={{ \"--variants-accent\": accent } as CSSProperties}\n className={cn(\n // Ring appears only on hover/focus so blocks stay calm at rest; the\n // corner dot is the resting \"in variant state\" indicator.\n \"group relative rounded-md ring-[color:var(--variants-accent)] transition-[box-shadow] duration-200\",\n layout === \"inline\" ? \"inline-block\" : \"block w-full\",\n \"hover:ring-1 focus-within:ring-1\",\n className,\n )}\n >\n {current}\n\n <StateDot\n state={confirmState}\n className=\"group-hover:pointer-events-none group-hover:opacity-0 group-focus-within:pointer-events-none group-focus-within:opacity-0\"\n />\n\n <div\n data-slot=\"variants-switcher\"\n className={cn(\n \"absolute -top-3 right-0 z-20 flex items-center gap-0.5 rounded-full border bg-background/95 px-1 py-0.5 shadow-md backdrop-blur\",\n \"border-[color-mix(in_srgb,var(--variants-accent)_40%,transparent)]\",\n \"pointer-events-none origin-top-right scale-90 opacity-0 transition-all duration-150\",\n \"group-hover:pointer-events-auto group-hover:scale-100 group-hover:opacity-100\",\n \"group-focus-within:pointer-events-auto group-focus-within:scale-100 group-focus-within:opacity-100\",\n )}\n >\n <RoundButton\n aria-label=\"Previous variant\"\n disabled={atStart}\n onClick={() => go(-1)}\n >\n <ChevronLeft className=\"size-3.5\" />\n </RoundButton>\n\n <span className=\"select-none rounded-full bg-[color-mix(in_srgb,var(--variants-accent)_15%,transparent)] px-1 text-[10px] font-semibold text-[color:var(--variants-accent)] tabular-nums\">\n {clampedActive + 1}/{variants.length}\n </span>\n\n <RoundButton\n aria-label=\"Next variant\"\n disabled={atEnd}\n onClick={() => go(1)}\n >\n <ChevronRight className=\"size-3.5\" />\n </RoundButton>\n\n <ConfirmButton state={confirmState} label={label} onClick={confirm} />\n </div>\n </div>\n );\n}\n\nfunction dotColorFor(state: ConfirmState): string {\n switch (state) {\n case \"error\":\n return \"bg-destructive\";\n case \"unavailable\":\n return \"bg-muted-foreground/40\";\n default:\n return \"bg-[color:var(--variants-accent)]\";\n }\n}\n\n/**\n * Resting corner indicator: pulses while a choice is pending, calm once\n * recorded — so a block's picked/unpicked state reads at a glance without\n * hovering.\n */\nfunction StateDot({\n state,\n className,\n}: {\n state: ConfirmState;\n className?: string;\n}) {\n const pulsing = state === \"idle\" || state === \"saving\";\n const color = dotColorFor(state);\n\n return (\n <span\n aria-hidden\n data-slot=\"variants-dot\"\n className={cn(\n \"absolute -top-1 -right-1 z-10 flex size-3 items-center justify-center transition-opacity duration-150\",\n className,\n )}\n >\n {pulsing && (\n <span\n className={cn(\n // Slow, gentle pulse (~2.5s) so it reads as an ambient hint,\n // not an alarm. Overrides tailwind's default 1s ping cadence.\n \"absolute inline-flex size-full animate-ping rounded-full opacity-50 [animation-duration:2.5s]\",\n color,\n )}\n />\n )}\n <span\n className={cn(\n \"relative inline-flex size-2.5 rounded-full ring-2 ring-background\",\n color,\n )}\n />\n </span>\n );\n}\n\nfunction RoundButton({\n children,\n disabled,\n className,\n ...props\n}: ComponentProps<\"button\">) {\n return (\n <button\n type=\"button\"\n disabled={disabled}\n className={cn(\n \"inline-flex size-6 items-center justify-center rounded-full text-muted-foreground transition-colors\",\n \"hover:bg-accent hover:text-accent-foreground\",\n \"disabled:pointer-events-none disabled:opacity-30\",\n className,\n )}\n {...props}\n >\n {children}\n </button>\n );\n}\n\n/**\n * Round tick that confirms the active variant. State is conveyed by icon +\n * color only (no text). The chosen `label` rides along in the title/aria for\n * accessibility and hover context.\n */\nfunction ConfirmButton({\n state,\n label,\n onClick,\n}: {\n state: ConfirmState;\n label: string;\n onClick: () => void;\n}) {\n const title =\n state === \"recorded\"\n ? `Recorded \"${label}\" — ask the agent to finalize`\n : state === \"unavailable\"\n ? \"Recorder unavailable\"\n : state === \"error\"\n ? \"Failed — click to retry\"\n : `Confirm \"${label}\"`;\n\n return (\n <RoundButton\n aria-label={title}\n title={title}\n disabled={state === \"saving\" || state === \"unavailable\"}\n onClick={onClick}\n className={cn(\n state === \"recorded\" &&\n \"text-[color:var(--variants-accent)] hover:bg-[color-mix(in_srgb,var(--variants-accent)_12%,transparent)] hover:text-[color:var(--variants-accent)]\",\n state === \"error\" &&\n \"text-destructive hover:bg-destructive/10 hover:text-destructive\",\n (state === \"idle\" || state === \"saving\") &&\n \"hover:bg-[color-mix(in_srgb,var(--variants-accent)_12%,transparent)] hover:text-[color:var(--variants-accent)]\",\n )}\n >\n {state === \"saving\" ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : state === \"recorded\" ? (\n <CheckCheck className=\"size-3.5\" />\n ) : (\n <Check className=\"size-3.5\" />\n )}\n </RoundButton>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"variants.js","names":[],"sources":["../../../src/react/ui-variants/variants.tsx"],"sourcesContent":["import {\n Check,\n CheckCheck,\n ChevronLeft,\n ChevronRight,\n Loader2,\n} from \"lucide-react\";\nimport {\n Children,\n type ComponentProps,\n type CSSProperties,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useMemo,\n useState,\n} from \"react\";\n\nimport { cn } from \"../lib/utils\";\nimport { Variant, type VariantProps } from \"./variant\";\n\n/** Endpoint the recorder plugin (`uiVariants()`) mounts in development. */\nconst CONFIRM_ENDPOINT = \"/api/ui-variants/confirm\";\n\n/** Abort the confirm request after this long so the button can't hang. */\nconst CONFIRM_TIMEOUT_MS = 5000;\n\n/**\n * Default switcher accent — a violet chosen to stand apart from most apps'\n * `primary`, so the dev-only picker chrome doesn't blend into the content it\n * wraps. Override per-block via the `accent` prop.\n */\nconst DEFAULT_ACCENT = \"#8b5cf6\";\n\ntype ConfirmState = \"idle\" | \"saving\" | \"recorded\" | \"unavailable\" | \"error\";\n\n/** Props for {@link Variants}. */\nexport interface VariantsProps {\n /**\n * Stable, unique identifier for this block. The variant-picking agent matches\n * on it to finalize the chosen variant into source, so it must be unique per\n * file. (Named `blockId`, not `id`, because it is a semantic key — not a DOM\n * element id.)\n */\n blockId: string;\n /** `<Variant>` children — one candidate UI each. */\n children: ReactNode;\n /**\n * How the wrapper flows in the layout. Defaults to `\"block\"` (full width,\n * variants stack vertically) — the right choice for page sections, heroes,\n * and other block-level regions, which is the common case. Use `\"inline\"`\n * when wrapping a small inline element such as a single button, so the\n * wrapper hugs its content instead of spanning the row.\n */\n layout?: \"block\" | \"inline\";\n /** Class name applied to the block wrapper (merged after `layout`). */\n className?: string;\n /**\n * Any CSS color for the switcher chrome (dot, ring, control cluster, count\n * pill, confirm tick). Every accent element derives from this one value.\n * Defaults to {@link DEFAULT_ACCENT}. The error state stays red regardless.\n *\n * @example accent=\"var(--primary)\" // match the app theme\n * @example accent=\"#f43f5e\" // rose\n */\n accent?: string;\n}\n\nfunction isVariantElement(node: ReactNode): node is ReactElement<VariantProps> {\n return isValidElement(node) && node.type === Variant;\n}\n\n/**\n * Dev-time UI picker: renders several `<Variant>` candidates one at a time.\n *\n * At rest the block shows a small, slowly-pulsing corner dot so it's clear\n * it's \"in variant state\" without hovering — the dot pulses while a choice is\n * still pending and goes calm once recorded. On hover (or focus) an accent\n * ring outlines the block and the dot gives way to a compact control cluster\n * (‹ index/total › ✓) that scales out of the corner. Confirming POSTs the\n * choice to the `uiVariants()` recorder plugin; a coding agent then finalizes\n * the chosen variant into source, removing this wrapper.\n *\n * All switcher chrome uses the `accent` color (default {@link DEFAULT_ACCENT},\n * a violet distinct from most apps' `primary`) so it doesn't blend into the\n * content it wraps.\n *\n * Degrades gracefully. In a production build the picker chrome is dropped\n * entirely and only the first variant renders as plain content — a safety net\n * for a block that shipped without being finalized. In dev, if the recorder\n * endpoint is absent (feature off) the switcher still works as a viewer and\n * Confirm reports that recording is unavailable.\n *\n * @example Page section (default layout — full-width, stacks vertically)\n * ```tsx\n * <Variants blockId=\"hero\">\n * <Variant label=\"Split\"><section>…</section></Variant>\n * <Variant label=\"Centered\"><section>…</section></Variant>\n * </Variants>\n * ```\n *\n * @example Small inline element (hug the content)\n * ```tsx\n * <Variants blockId=\"hero-cta\" layout=\"inline\">\n * <Variant label=\"Ghost\"><Button variant=\"ghost\">Get started</Button></Variant>\n * <Variant label=\"Solid\"><Button>Get started</Button></Variant>\n * </Variants>\n * ```\n *\n * @example Custom accent\n * ```tsx\n * <Variants blockId=\"hero\" accent=\"#f43f5e\">…</Variants>\n * ```\n */\nexport function Variants({\n blockId,\n children,\n layout = \"block\",\n className,\n accent = DEFAULT_ACCENT,\n}: VariantsProps) {\n const variants = useMemo(\n () => Children.toArray(children).filter(isVariantElement),\n [children],\n );\n const [active, setActive] = useState(0);\n const [confirmState, setConfirmState] = useState<ConfirmState>(\"idle\");\n\n if (variants.length === 0) return null;\n if (!import.meta.env.DEV) return <>{variants[0]}</>;\n\n const clampedActive = Math.min(active, variants.length - 1);\n const current = variants[clampedActive];\n const label = current.props.label ?? `Variant ${clampedActive + 1}`;\n const atStart = clampedActive === 0;\n const atEnd = clampedActive === variants.length - 1;\n\n const go = (delta: number) => {\n // No wrap-around: clamp within bounds.\n setActive((i) => Math.max(0, Math.min(variants.length - 1, i + delta)));\n setConfirmState(\"idle\");\n };\n\n const confirm = async () => {\n if (confirmState === \"saving\") return;\n setConfirmState(\"saving\");\n\n // Bound the request so a hung recorder can't leave the button stuck in\n // \"saving\" forever.\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), CONFIRM_TIMEOUT_MS);\n try {\n const res = await fetch(CONFIRM_ENDPOINT, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ blockId, chosenIndex: clampedActive, label }),\n signal: controller.signal,\n });\n // 404/403: the recorder isn't mounted (feature off or a production\n // build). Degrade to a plain viewer.\n if (res.status === 404 || res.status === 403) {\n setConfirmState(\"unavailable\");\n return;\n }\n // Any other non-OK status (e.g. a 500) is a failure the user can retry.\n setConfirmState(res.ok ? \"recorded\" : \"error\");\n } catch {\n // Network failure or timeout abort — the request never completed.\n setConfirmState(\"error\");\n } finally {\n clearTimeout(timeout);\n }\n };\n\n return (\n <div\n data-slot=\"variants\"\n // All accent chrome derives from this one variable; children read it via\n // the cascade, so the `accent` prop flows everywhere without prop-drilling.\n style={{ \"--variants-accent\": accent } as CSSProperties}\n className={cn(\n // Ring appears only on hover/focus so blocks stay calm at rest; the\n // corner dot is the resting \"in variant state\" indicator.\n \"group relative rounded-md ring-[color:var(--variants-accent)] transition-[box-shadow] duration-200\",\n layout === \"inline\" ? \"inline-block\" : \"block w-full\",\n \"hover:ring-1 focus-within:ring-1\",\n className,\n )}\n >\n {current}\n\n <StateDot\n state={confirmState}\n className=\"group-hover:pointer-events-none group-hover:opacity-0 group-focus-within:pointer-events-none group-focus-within:opacity-0\"\n />\n\n <div\n data-slot=\"variants-switcher\"\n className={cn(\n \"absolute -top-3 right-0 z-20 flex items-center gap-0.5 rounded-full border bg-background/95 px-1 py-0.5 shadow-md backdrop-blur\",\n \"border-[color-mix(in_srgb,var(--variants-accent)_40%,transparent)]\",\n \"pointer-events-none origin-top-right scale-90 opacity-0 transition-all duration-150\",\n \"group-hover:pointer-events-auto group-hover:scale-100 group-hover:opacity-100\",\n \"group-focus-within:pointer-events-auto group-focus-within:scale-100 group-focus-within:opacity-100\",\n )}\n >\n <RoundButton\n aria-label=\"Previous variant\"\n disabled={atStart}\n onClick={() => go(-1)}\n >\n <ChevronLeft className=\"size-3.5\" />\n </RoundButton>\n\n <span className=\"select-none rounded-full bg-[color-mix(in_srgb,var(--variants-accent)_15%,transparent)] px-1 text-[10px] font-semibold text-[color:var(--variants-accent)] tabular-nums\">\n {clampedActive + 1}/{variants.length}\n </span>\n\n <RoundButton\n aria-label=\"Next variant\"\n disabled={atEnd}\n onClick={() => go(1)}\n >\n <ChevronRight className=\"size-3.5\" />\n </RoundButton>\n\n <ConfirmButton state={confirmState} label={label} onClick={confirm} />\n </div>\n </div>\n );\n}\n\nfunction dotColorFor(state: ConfirmState): string {\n switch (state) {\n case \"error\":\n return \"bg-destructive\";\n case \"unavailable\":\n return \"bg-muted-foreground/40\";\n default:\n return \"bg-[color:var(--variants-accent)]\";\n }\n}\n\n/**\n * Resting corner indicator: pulses while a choice is pending, calm once\n * recorded — so a block's picked/unpicked state reads at a glance without\n * hovering.\n */\nfunction StateDot({\n state,\n className,\n}: {\n state: ConfirmState;\n className?: string;\n}) {\n const pulsing = state === \"idle\" || state === \"saving\";\n const color = dotColorFor(state);\n\n return (\n <span\n aria-hidden\n data-slot=\"variants-dot\"\n className={cn(\n \"absolute -top-1 -right-1 z-10 flex size-3 items-center justify-center transition-opacity duration-150\",\n className,\n )}\n >\n {pulsing && (\n <span\n className={cn(\n // Slow, gentle pulse (~2.5s) so it reads as an ambient hint,\n // not an alarm. Overrides tailwind's default 1s ping cadence.\n \"absolute inline-flex size-full animate-ping rounded-full opacity-50 [animation-duration:2.5s]\",\n color,\n )}\n />\n )}\n <span\n className={cn(\n \"relative inline-flex size-2.5 rounded-full ring-2 ring-background\",\n color,\n )}\n />\n </span>\n );\n}\n\nfunction RoundButton({\n children,\n disabled,\n className,\n ...props\n}: ComponentProps<\"button\">) {\n return (\n <button\n type=\"button\"\n disabled={disabled}\n className={cn(\n \"inline-flex size-6 items-center justify-center rounded-full text-muted-foreground transition-colors\",\n \"hover:bg-accent hover:text-accent-foreground\",\n \"disabled:pointer-events-none disabled:opacity-30\",\n className,\n )}\n {...props}\n >\n {children}\n </button>\n );\n}\n\n/**\n * Round tick that confirms the active variant. State is conveyed by icon +\n * color only (no text). The chosen `label` rides along in the title/aria for\n * accessibility and hover context.\n */\nfunction ConfirmButton({\n state,\n label,\n onClick,\n}: {\n state: ConfirmState;\n label: string;\n onClick: () => void;\n}) {\n const title =\n state === \"recorded\"\n ? `Recorded \"${label}\" — ask the agent to finalize`\n : state === \"unavailable\"\n ? \"Recorder unavailable\"\n : state === \"error\"\n ? \"Failed — click to retry\"\n : `Confirm \"${label}\"`;\n\n return (\n <RoundButton\n aria-label={title}\n title={title}\n disabled={state === \"saving\" || state === \"unavailable\"}\n onClick={onClick}\n className={cn(\n state === \"recorded\" &&\n \"text-[color:var(--variants-accent)] hover:bg-[color-mix(in_srgb,var(--variants-accent)_12%,transparent)] hover:text-[color:var(--variants-accent)]\",\n state === \"error\" &&\n \"text-destructive hover:bg-destructive/10 hover:text-destructive\",\n (state === \"idle\" || state === \"saving\") &&\n \"hover:bg-[color-mix(in_srgb,var(--variants-accent)_12%,transparent)] hover:text-[color:var(--variants-accent)]\",\n )}\n >\n {state === \"saving\" ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : state === \"recorded\" ? (\n <CheckCheck className=\"size-3.5\" />\n ) : (\n <Check className=\"size-3.5\" />\n )}\n </RoundButton>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","names":[],"sources":["../../src/schemas/manifest.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","names":[],"sources":["../../src/schemas/manifest.ts"],"mappings":";;;cAwPa,yBAAA,EAAyB,CAAA,CAAA,qBAAA,EAAA,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;cA8FzB,wBAAA,EAAwB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkQxB,4BAAA,EAA4B,CAAA,CAAA,SAAA;;;;;cAoE5B,oBAAA,EAAoB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAqgBrB,mBAAA,GAAsB,CAAA,CAAE,KAAA,QAAa,yBAAA;AAAA,KACrC,kBAAA,GAAqB,CAAA,CAAE,KAAA,QAAa,wBAAA;AAAA,KAIpC,sBAAA,GAAyB,CAAA,CAAE,KAAA,QAC9B,4BAAA;AAAA,KAEG,cAAA,GAAiB,CAAA,CAAE,KAAA,QAAa,oBAAA"}
|