@altertable/data-app 0.65.0 → 0.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/AGENTS.md +38 -1
  2. package/CONTRIBUTING.md +37 -3
  3. package/README.md +1 -1
  4. package/dist/chunks/{contract-sm7j7k95.js → contract-13j5zb3c.js} +2 -144
  5. package/dist/chunks/contract-13j5zb3c.js.map +13 -0
  6. package/dist/chunks/contract-19ckn3n7.js +78 -0
  7. package/dist/chunks/contract-19ckn3n7.js.map +10 -0
  8. package/dist/chunks/{contract-a061yj6r.js → contract-e11m4y7f.js} +34 -70
  9. package/dist/chunks/contract-e11m4y7f.js.map +12 -0
  10. package/dist/chunks/{contract-g6hky7x6.js → contract-h8a6f559.js} +8 -4
  11. package/dist/chunks/contract-h8a6f559.js.map +12 -0
  12. package/dist/chunks/contract-havnjmdr.js +13011 -0
  13. package/dist/chunks/contract-havnjmdr.js.map +99 -0
  14. package/dist/chunks/{contract-txjy0en2.js → contract-m6n8ctfc.js} +64 -72
  15. package/dist/chunks/contract-m6n8ctfc.js.map +10 -0
  16. package/dist/chunks/contract-pk603qj7.js +146 -0
  17. package/dist/chunks/contract-pk603qj7.js.map +10 -0
  18. package/dist/chunks/{contract-yxbjea23.js → contract-rfbakka4.js} +179 -2
  19. package/dist/chunks/contract-rfbakka4.js.map +13 -0
  20. package/dist/client/index.js +11 -6
  21. package/dist/client/index.js.map +1 -1
  22. package/dist/core/appearance.js +1 -1
  23. package/dist/core/contract.js +13 -3
  24. package/dist/core/contract.js.map +1 -1
  25. package/dist/embed/index.js +9 -7
  26. package/dist/embed/index.js.map +2 -2
  27. package/dist/local.js +1 -1
  28. package/dist/local.js.map +2 -2
  29. package/dist/react/embed/index.js +1 -1
  30. package/dist/react/index.js +2532 -11545
  31. package/dist/react/index.js.map +16 -91
  32. package/dist/react/ui/index.js +1614 -0
  33. package/dist/react/ui/index.js.map +22 -0
  34. package/dist/server.js +1 -1
  35. package/dist/server.js.map +2 -2
  36. package/dist/types/client/annotations.d.ts +23 -0
  37. package/dist/types/client/index.d.ts +1 -0
  38. package/dist/types/core/annotations.d.ts +92 -0
  39. package/dist/types/core/appearance.d.ts +2 -2
  40. package/dist/types/core/contract.d.ts +2 -0
  41. package/dist/types/core/presentation.d.ts +2 -0
  42. package/dist/types/core/variables.d.ts +2 -5
  43. package/dist/types/react/annotations/AnnotationBar.d.ts +24 -0
  44. package/dist/types/react/annotations/AnnotationControls.d.ts +10 -0
  45. package/dist/types/react/annotations/AnnotationEditor.d.ts +15 -0
  46. package/dist/types/react/annotations/AnnotationMarkers.d.ts +19 -0
  47. package/dist/types/react/annotations/AnnotationSelectionLayer.d.ts +12 -0
  48. package/dist/types/react/annotations/AnnotationTarget.d.ts +9 -0
  49. package/dist/types/react/annotations/AnnotationTooltip.d.ts +9 -0
  50. package/dist/types/react/annotations/AnnotationTrigger.d.ts +10 -0
  51. package/dist/types/react/annotations/annotation-editor-state.d.ts +56 -0
  52. package/dist/types/react/annotations/annotation-screenshot.d.ts +4 -0
  53. package/dist/types/react/annotations/annotation-targets.d.ts +33 -0
  54. package/dist/types/react/annotations/getAnnotationProps.d.ts +15 -0
  55. package/dist/types/react/annotations/styles.d.ts +3 -0
  56. package/dist/types/react/annotations/useAnnotationGeometry.d.ts +22 -0
  57. package/dist/types/react/annotations/useDataAppAnnotations.d.ts +19 -0
  58. package/dist/types/react/bindings.d.ts +110 -0
  59. package/dist/types/react/content.d.ts +24 -11
  60. package/dist/types/react/hooks.d.ts +29 -787
  61. package/dist/types/react/index.d.ts +30 -105
  62. package/dist/types/react/source-owner.d.ts +2 -0
  63. package/dist/types/react/style-contract.d.ts +60 -0
  64. package/dist/types/react/style-validation.d.ts +2 -0
  65. package/dist/types/react/ui/AboutData.d.ts +9 -1
  66. package/dist/types/react/ui/AppHeader.d.ts +1 -2
  67. package/dist/types/react/ui/AppLayout.d.ts +3 -9
  68. package/dist/types/react/ui/AppToolbar.d.ts +5 -8
  69. package/dist/types/react/ui/AreaChart.d.ts +7 -0
  70. package/dist/types/react/ui/BarChart.d.ts +6 -0
  71. package/dist/types/react/ui/{ComparisonVisual.d.ts → Comparison.d.ts} +3 -3
  72. package/dist/types/react/ui/ContentSkeleton.d.ts +2 -0
  73. package/dist/types/react/ui/DataApp.d.ts +17 -58
  74. package/dist/types/react/ui/DataAppFrame.d.ts +42 -0
  75. package/dist/types/react/ui/DataBoundary.d.ts +4 -5
  76. package/dist/types/react/ui/DataSection.d.ts +6 -18
  77. package/dist/types/react/ui/DataSectionBoundary.d.ts +30 -0
  78. package/dist/types/react/ui/DataValue.d.ts +9 -0
  79. package/dist/types/react/ui/DataWidget.d.ts +2 -1
  80. package/dist/types/react/ui/DateTimeTooltip.d.ts +1 -1
  81. package/dist/types/react/ui/ExportControl.d.ts +1 -1
  82. package/dist/types/react/ui/HelpPopover.d.ts +3 -4
  83. package/dist/types/react/ui/InspectionContext.d.ts +2 -0
  84. package/dist/types/react/ui/InspectionProvider.d.ts +29 -0
  85. package/dist/types/react/ui/LineChart.d.ts +7 -0
  86. package/dist/types/react/ui/MetricWidget.d.ts +4 -3
  87. package/dist/types/react/ui/PieChart.d.ts +8 -0
  88. package/dist/types/react/ui/PresentStory.d.ts +2 -5
  89. package/dist/types/react/ui/RefreshControl.d.ts +1 -2
  90. package/dist/types/react/ui/ScatterChart.d.ts +17 -0
  91. package/dist/types/react/ui/SearchField.d.ts +1 -2
  92. package/dist/types/react/ui/SearchInput.d.ts +3 -1
  93. package/dist/types/react/ui/Sheet.d.ts +2 -1
  94. package/dist/types/react/ui/Skeleton.d.ts +5 -2
  95. package/dist/types/react/ui/StaticDataApp.d.ts +4 -0
  96. package/dist/types/react/ui/TableWidget.d.ts +19 -16
  97. package/dist/types/react/ui/Tabs.d.ts +6 -2
  98. package/dist/types/react/ui/TextWidget.d.ts +2 -1
  99. package/dist/types/react/ui/Tooltip.d.ts +4 -3
  100. package/dist/types/react/ui/TrendChart.d.ts +5 -0
  101. package/dist/types/react/ui/UpdatedAt.d.ts +1 -1
  102. package/dist/types/react/ui/VisualizationWidget.d.ts +12 -13
  103. package/dist/types/react/ui/WidgetViewTabs.d.ts +1 -1
  104. package/dist/types/react/ui/chart-data.d.ts +25 -0
  105. package/dist/types/react/ui/data-context.d.ts +18 -7
  106. package/dist/types/react/ui/icons.d.ts +1 -0
  107. package/dist/types/react/ui/index.d.ts +74 -0
  108. package/dist/types/react/ui/metric.d.ts +1 -1
  109. package/dist/types/react/ui/presentation.d.ts +5 -1
  110. package/dist/types/react/ui/shortcuts.d.ts +7 -1
  111. package/dist/types/react/view-runtime.d.ts +29 -0
  112. package/dist/types/react/view.d.ts +21 -4
  113. package/dist/types/react/widgets.d.ts +79 -0
  114. package/docs/app-authoring.md +58 -50
  115. package/docs/data-context.md +89 -0
  116. package/docs/formatting-and-appearance.md +52 -0
  117. package/docs/hosted-apps.md +0 -13
  118. package/docs/layout.md +4 -2
  119. package/docs/react-embed.md +2 -2
  120. package/docs/react.md +20 -316
  121. package/docs/stories-and-export.md +52 -0
  122. package/docs/styling.md +55 -0
  123. package/docs/ui-quality.md +20 -0
  124. package/docs/ui.md +41 -0
  125. package/docs/variables.md +98 -0
  126. package/docs/views.md +127 -0
  127. package/docs/widgets.md +142 -0
  128. package/examples/starter-data-app/index.tsx +82 -55
  129. package/package.json +12 -4
  130. package/dist/chunks/contract-a061yj6r.js.map +0 -12
  131. package/dist/chunks/contract-g6hky7x6.js.map +0 -12
  132. package/dist/chunks/contract-sm7j7k95.js.map +0 -14
  133. package/dist/chunks/contract-txjy0en2.js.map +0 -10
  134. package/dist/chunks/contract-yxbjea23.js.map +0 -12
  135. package/dist/types/react/ui/RefreshRegion.d.ts +0 -9
  136. package/dist/types/react/ui/SelectableBarChart.d.ts +0 -15
  137. package/docs/releasing.md +0 -79
  138. /package/dist/types/react/ui/{comparison.d.ts → metric-comparison.d.ts} +0 -0
@@ -69,89 +69,81 @@ function normalizeAppearance2(value = {}) {
69
69
  }
70
70
  };
71
71
  }
72
- var palettes = {
73
- neutral: {
74
- light: ["#ffffff", "#ffffff", "#f6f7f7", "#202124", "#687078", "#e3e6e8"],
75
- dark: ["#151719", "#202326", "#292d31", "#f2f3f4", "#aeb5bc", "#3b4248"]
76
- },
77
- slate: {
78
- light: ["#f8fafc", "#ffffff", "#f1f5f9", "#17212f", "#607083", "#dce3ea"],
79
- dark: ["#111820", "#1b2530", "#263340", "#f0f4f8", "#a8b5c3", "#3a4857"]
80
- },
81
- warm: {
82
- light: ["#fbfaf8", "#ffffff", "#f5f2ed", "#292723", "#736e66", "#e8e2da"],
83
- dark: ["#1b1916", "#25221e", "#302c27", "#f5f1eb", "#bcb3a8", "#494239"]
84
- }
85
- };
86
- var spaces = {
87
- compact: ["4px", "8px", "12px", "18px", "24px"],
88
- comfortable: ["5px", "10px", "16px", "24px", "32px"],
89
- spacious: ["6px", "12px", "20px", "30px", "40px"]
90
- };
91
- var radii = {
92
- none: ["0px", "0px", "0px"],
93
- small: ["4px", "7px", "9px"],
94
- medium: ["7px", "12px", "16px"],
95
- large: ["10px", "18px", "24px"]
96
- };
97
72
  function fontStack(family) {
98
73
  return family === "system" ? "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, ui-sans-serif, sans-serif" : `${JSON.stringify(family)}, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, ui-sans-serif, sans-serif`;
99
74
  }
75
+ function accentForeground(settings, dark) {
76
+ const accent = getComputedStyle(document.documentElement).getPropertyValue("--atbl-accent") || (dark ? settings.darkAccentColor ?? `color-mix(in srgb, ${settings.accentColor} 50%, white)` : settings.accentColor);
77
+ const canvas = document.createElement("canvas");
78
+ canvas.width = canvas.height = 1;
79
+ const context = canvas.getContext("2d");
80
+ context.fillStyle = accent;
81
+ context.fillRect(0, 0, 1, 1);
82
+ const channels = [...context.getImageData(0, 0, 1, 1).data].slice(0, 3).map((value) => {
83
+ const channel = value / 255;
84
+ return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
85
+ });
86
+ const luminance = channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722;
87
+ return (luminance + 0.05) / 0.05 > 1.05 / (luminance + 0.05) ? "#000000" : "#ffffff";
88
+ }
100
89
  function applyAppearance2(value) {
101
90
  const settings = normalizeAppearance2(value);
102
91
  const root = document.documentElement;
103
92
  const preference = window.matchMedia("(prefers-color-scheme: dark)");
104
- const [xs, sm, md, lg, xl] = spaces[settings.density];
105
- const [control, surface, overlay] = radii[settings.cornerRadius];
106
- const tokens = {
107
- "--at-font": fontStack(settings.typography.body),
108
- "--at-font-heading": fontStack(settings.typography.heading),
109
- "--at-space-xs": xs,
110
- "--at-space-sm": sm,
111
- "--at-space-md": md,
112
- "--at-space-lg": lg,
113
- "--at-space-xl": xl,
114
- "--at-layout-gap": `clamp(${md}, 2.5vw, ${lg})`,
115
- "--at-radius-control": control,
116
- "--at-radius-surface": surface,
117
- "--at-radius-overlay": overlay,
118
- "--at-shadow-overlay": settings.elevation === "flat" ? "none" : settings.elevation === "raised" ? "0 24px 72px rgb(0 0 0 / 24%)" : "0 18px 50px rgb(0 0 0 / 16%)",
119
- "--at-shadow-surface": settings.elevation === "flat" ? "none" : settings.elevation === "raised" ? "0 8px 28px rgb(20 28 40 / 10%)" : "0 3px 16px rgb(20 28 40 / 5%)"
93
+ const attributes = {
94
+ "data-atbl-appearance": "",
95
+ "data-atbl-base-color": settings.baseColor,
96
+ "data-atbl-density": settings.density,
97
+ "data-atbl-radius": settings.cornerRadius,
98
+ "data-atbl-elevation": settings.elevation,
99
+ "data-atbl-theme": ""
120
100
  };
121
- function applyColors() {
122
- const dark = settings.theme === "dark" || settings.theme === "system" && preference.matches;
123
- const [background, surfaceColor, subtle, text, muted, border] = palettes[settings.baseColor][dark ? "dark" : "light"];
124
- const accent = dark ? settings.darkAccentColor ?? `color-mix(in srgb, ${settings.accentColor} 50%, white)` : settings.accentColor;
125
- settings.chartColors.forEach((chartColor, index) => {
126
- tokens[`--at-chart-${index + 1}`] = dark ? `color-mix(in srgb, ${chartColor} 60%, white)` : chartColor;
127
- });
128
- Object.assign(tokens, {
129
- "--at-background": background,
130
- "--at-surface": surfaceColor,
131
- "--at-subtle": subtle,
132
- "--at-text": text,
133
- "--at-muted": muted,
134
- "--at-border": border,
135
- "--at-accent": accent,
136
- "--at-focus-color": muted,
137
- "--at-control-hover-border": `color-mix(in srgb, ${muted} 45%, ${border})`,
138
- "--at-accent-hover": `color-mix(in srgb, ${accent} 80%, ${dark ? "white" : "black"})`,
139
- "--at-accent-subtle": `color-mix(in srgb, ${accent} ${dark ? 22 : 12}%, ${surfaceColor})`,
140
- "--at-on-accent": dark ? background : "#ffffff",
141
- "--at-danger": dark ? "#f97066" : "#b42318",
142
- "--at-backdrop": dark ? "rgb(0 0 0 / 55%)" : "rgb(15 23 30 / 22%)",
143
- "--at-code-surface": dark ? "#242a31" : "#f4f6f9",
144
- "--at-code-text": dark ? "#e5e9ef" : "#273242"
145
- });
146
- root.style.colorScheme = dark ? "dark" : "light";
147
- for (const [name, token] of Object.entries(tokens))
101
+ const inputs = {
102
+ "--atbl-input-font": fontStack(settings.typography.body),
103
+ "--atbl-input-font-heading": fontStack(settings.typography.heading),
104
+ "--atbl-input-accent": settings.accentColor,
105
+ "--atbl-input-dark-accent": settings.darkAccentColor ?? "",
106
+ "--atbl-input-on-accent": ""
107
+ };
108
+ const chartColors = settings.chartColors.length ? settings.chartColors : defaults.chartColors;
109
+ for (let index = 0;index < 8; index++)
110
+ inputs[`--atbl-input-chart-${index + 1}`] = chartColors[index % chartColors.length];
111
+ const previousAttributes = Object.keys(attributes).map((name) => [name, root.getAttribute(name)]);
112
+ const previousInputs = Object.keys(inputs).map((name) => [
113
+ name,
114
+ root.style.getPropertyValue(name),
115
+ root.style.getPropertyPriority(name)
116
+ ]);
117
+ for (const [name, token] of Object.entries(inputs)) {
118
+ if (token)
148
119
  root.style.setProperty(name, token);
120
+ else
121
+ root.style.removeProperty(name);
122
+ }
123
+ for (const [name, attribute] of Object.entries(attributes))
124
+ root.setAttribute(name, attribute);
125
+ function applyTheme() {
126
+ const dark = settings.theme === "dark" || settings.theme === "system" && preference.matches;
127
+ root.setAttribute("data-atbl-theme", dark ? "dark" : "light");
128
+ root.style.setProperty("--atbl-input-on-accent", accentForeground(settings, dark));
149
129
  }
150
- applyColors();
130
+ applyTheme();
151
131
  if (settings.theme === "system")
152
- preference.addEventListener("change", applyColors);
132
+ preference.addEventListener("change", applyTheme);
153
133
  return () => {
154
- preference.removeEventListener("change", applyColors);
134
+ preference.removeEventListener("change", applyTheme);
135
+ for (const [name, attribute] of previousAttributes) {
136
+ if (attribute === null)
137
+ root.removeAttribute(name);
138
+ else
139
+ root.setAttribute(name, attribute);
140
+ }
141
+ for (const [name, token, priority] of previousInputs) {
142
+ if (token)
143
+ root.style.setProperty(name, token, priority);
144
+ else
145
+ root.style.removeProperty(name);
146
+ }
155
147
  };
156
148
  }
157
149
  function createThemeController2(initialTheme = "light") {
@@ -185,4 +177,4 @@ function createThemeController2(initialTheme = "light") {
185
177
 
186
178
  export { parseAppearance2, normalizeAppearance2, applyAppearance2, createThemeController2 };
187
179
 
188
- //# debugId=E968EAFDC43C9C1764756E2164756E21
180
+ //# debugId=312AAB5702AE519564756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/core/appearance.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * Brand settings; parsing is portable, while theme application requires the browser.\n * @module @altertable/data-app/appearance\n */\nexport type Theme = 'light' | 'dark';\nexport type ThemePreference = Theme | 'system';\n\nexport type AppearanceSettings = {\n theme: ThemePreference;\n baseColor: 'neutral' | 'slate' | 'warm';\n accentColor: string;\n darkAccentColor?: string;\n chartColors: string[];\n density: 'compact' | 'comfortable' | 'spacious';\n cornerRadius: 'none' | 'small' | 'medium' | 'large';\n elevation: 'flat' | 'subtle' | 'raised';\n typography: { body: string; heading: string };\n};\n\n/** App-authored overrides; omitted fields use the package defaults. */\nexport type AppearanceOptions = Partial<\n Omit<AppearanceSettings, 'typography'>\n> & { typography?: Partial<AppearanceSettings['typography']> };\n\nexport type ThemeController = {\n getTheme: () => ThemePreference;\n setTheme: (theme: ThemePreference) => void;\n subscribe: (listener: () => void) => () => void;\n};\n\nconst defaults: AppearanceSettings = {\n theme: 'light',\n baseColor: 'neutral',\n accentColor: '#405d47',\n darkAccentColor: '#a6c4ad',\n chartColors: [\n '#285fc0',\n '#a95319',\n '#147862',\n '#7243aa',\n '#aa3958',\n '#475569',\n ],\n density: 'comfortable',\n cornerRadius: 'medium',\n elevation: 'subtle',\n typography: { body: 'system', heading: 'system' },\n};\n\nfunction record(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction oneOf<T extends string>(\n value: unknown,\n options: readonly T[]\n): value is T {\n return typeof value === 'string' && options.includes(value as T);\n}\n\nfunction color(value: unknown): value is string {\n return typeof value === 'string' && /^#[0-9a-fA-F]{6}$/.test(value);\n}\n\nfunction font(value: unknown): value is string {\n return (\n typeof value === 'string' &&\n value.length > 0 &&\n value.length <= 120 &&\n !/[;{}()]/.test(value)\n );\n}\n\n/** Preserve supported external settings and use defaults for invalid fields. */\nexport function parseAppearance(value: unknown): AppearanceSettings {\n if (!record(value)) return normalizeAppearance();\n const typography = record(value.typography) ? value.typography : {};\n\n return normalizeAppearance({\n theme: oneOf(value.theme, ['light', 'dark', 'system'])\n ? value.theme\n : undefined,\n baseColor: oneOf(value.baseColor, ['neutral', 'slate', 'warm'])\n ? value.baseColor\n : undefined,\n accentColor: color(value.accentColor) ? value.accentColor : undefined,\n darkAccentColor: color(value.darkAccentColor)\n ? value.darkAccentColor\n : undefined,\n chartColors:\n Array.isArray(value.chartColors) &&\n value.chartColors.length >= 1 &&\n value.chartColors.length <= 8 &&\n value.chartColors.every(color)\n ? value.chartColors\n : undefined,\n density: oneOf(value.density, ['compact', 'comfortable', 'spacious'])\n ? value.density\n : undefined,\n cornerRadius: oneOf(value.cornerRadius, [\n 'none',\n 'small',\n 'medium',\n 'large',\n ])\n ? value.cornerRadius\n : undefined,\n elevation: oneOf(value.elevation, ['flat', 'subtle', 'raised'])\n ? value.elevation\n : undefined,\n typography: {\n body: font(typography.body) ? typography.body : undefined,\n heading: font(typography.heading) ? typography.heading : undefined,\n },\n });\n}\n\n/** Resolve typed app settings without repeating authoring checks at runtime. */\nexport function normalizeAppearance(\n value: AppearanceOptions = {}\n): AppearanceSettings {\n return {\n theme: value.theme ?? defaults.theme,\n baseColor: value.baseColor ?? defaults.baseColor,\n accentColor: value.accentColor ?? defaults.accentColor,\n density: value.density ?? defaults.density,\n cornerRadius: value.cornerRadius ?? defaults.cornerRadius,\n elevation: value.elevation ?? defaults.elevation,\n darkAccentColor:\n value.darkAccentColor ??\n (value.accentColor === undefined ? defaults.darkAccentColor : undefined),\n chartColors: [...(value.chartColors ?? defaults.chartColors)],\n typography: {\n body: value.typography?.body ?? defaults.typography.body,\n heading: value.typography?.heading ?? defaults.typography.heading,\n },\n };\n}\n\nfunction fontStack(family: string): string {\n return family === 'system'\n ? \"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, ui-sans-serif, sans-serif\"\n : `${JSON.stringify(family)}, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, ui-sans-serif, sans-serif`;\n}\n\n/** Choose a foreground after the browser resolves the configured CSS color. */\nfunction accentForeground(settings: AppearanceSettings, dark: boolean): string {\n const accent =\n getComputedStyle(document.documentElement).getPropertyValue(\n '--atbl-accent'\n ) ||\n (dark\n ? (settings.darkAccentColor ??\n `color-mix(in srgb, ${settings.accentColor} 50%, white)`)\n : settings.accentColor);\n const canvas = document.createElement('canvas');\n canvas.width = canvas.height = 1;\n const context = canvas.getContext('2d')!;\n context.fillStyle = accent;\n context.fillRect(0, 0, 1, 1);\n const channels = [...context.getImageData(0, 0, 1, 1).data]\n .slice(0, 3)\n .map(value => {\n const channel = value / 255;\n return channel <= 0.04045\n ? channel / 12.92\n : ((channel + 0.055) / 1.055) ** 2.4;\n });\n const luminance =\n channels[0]! * 0.2126 + channels[1]! * 0.7152 + channels[2]! * 0.0722;\n return (luminance + 0.05) / 0.05 > 1.05 / (luminance + 0.05)\n ? '#000000'\n : '#ffffff';\n}\n\n/**\n * Select document-owned appearance presets and brand inputs. Public CSS tokens remain\n * overridable in stylesheets. Cleanup restores the previous document appearance.\n */\nexport function applyAppearance(value?: AppearanceOptions): () => void {\n const settings = normalizeAppearance(value);\n const root = document.documentElement;\n const preference = window.matchMedia('(prefers-color-scheme: dark)');\n const attributes: Record<string, string> = {\n 'data-atbl-appearance': '',\n 'data-atbl-base-color': settings.baseColor,\n 'data-atbl-density': settings.density,\n 'data-atbl-radius': settings.cornerRadius,\n 'data-atbl-elevation': settings.elevation,\n 'data-atbl-theme': '',\n };\n const inputs: Record<string, string> = {\n '--atbl-input-font': fontStack(settings.typography.body),\n '--atbl-input-font-heading': fontStack(settings.typography.heading),\n '--atbl-input-accent': settings.accentColor,\n '--atbl-input-dark-accent': settings.darkAccentColor ?? '',\n '--atbl-input-on-accent': '',\n };\n const chartColors = settings.chartColors.length\n ? settings.chartColors\n : defaults.chartColors;\n // Every categorical slot is defined, including after shrinking a custom palette.\n for (let index = 0; index < 8; index++)\n inputs[`--atbl-input-chart-${index + 1}`] =\n chartColors[index % chartColors.length]!;\n\n const previousAttributes = Object.keys(attributes).map(\n name => [name, root.getAttribute(name)] as const\n );\n const previousInputs = Object.keys(inputs).map(\n name =>\n [\n name,\n root.style.getPropertyValue(name),\n root.style.getPropertyPriority(name),\n ] as const\n );\n for (const [name, token] of Object.entries(inputs)) {\n if (token) root.style.setProperty(name, token);\n else root.style.removeProperty(name);\n }\n for (const [name, attribute] of Object.entries(attributes))\n root.setAttribute(name, attribute);\n\n function applyTheme(): void {\n const dark =\n settings.theme === 'dark' ||\n (settings.theme === 'system' && preference.matches);\n root.setAttribute('data-atbl-theme', dark ? 'dark' : 'light');\n root.style.setProperty(\n '--atbl-input-on-accent',\n accentForeground(settings, dark)\n );\n }\n applyTheme();\n if (settings.theme === 'system')\n preference.addEventListener('change', applyTheme);\n\n return () => {\n preference.removeEventListener('change', applyTheme);\n for (const [name, attribute] of previousAttributes) {\n if (attribute === null) root.removeAttribute(name);\n else root.setAttribute(name, attribute);\n }\n for (const [name, token, priority] of previousInputs) {\n if (token) root.style.setProperty(name, token, priority);\n else root.style.removeProperty(name);\n }\n };\n}\n\n/** Persist a viewer theme preference. Applying appearance belongs to the UI owner. */\nexport function createThemeController(\n initialTheme: ThemePreference = 'light'\n): ThemeController {\n const storageKey = 'altertable.data-app.theme';\n let theme = initialTheme;\n try {\n const saved = window.localStorage.getItem(storageKey);\n if (oneOf(saved, ['light', 'dark', 'system'])) theme = saved;\n } catch {\n // Storage can be unavailable in private browsing or embedded contexts.\n }\n const listeners = new Set<() => void>();\n\n return {\n getTheme() {\n return theme;\n },\n setTheme(nextTheme) {\n if (nextTheme === theme) return;\n theme = nextTheme;\n try {\n window.localStorage.setItem(storageKey, theme);\n } catch {\n // The selection still applies to this page.\n }\n listeners.forEach(listener => listener());\n },\n subscribe(listener) {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n };\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "312AAB5702AE519564756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,146 @@
1
+ import {
2
+ DataSourceError2
3
+ } from "./contract-rfbakka4.js";
4
+
5
+ // src/core/operation.ts
6
+ async function executeDataOperation(operation, value, {
7
+ operationName,
8
+ lakehouse,
9
+ signal: requestSignal,
10
+ includeSql,
11
+ requestId
12
+ }) {
13
+ requestSignal.throwIfAborted();
14
+ let input;
15
+ try {
16
+ input = operation.input(value);
17
+ } catch {
18
+ throw new OperationInputError;
19
+ }
20
+ const signal = AbortSignal.any([
21
+ requestSignal,
22
+ AbortSignal.timeout(operation.policy.maxDurationMs)
23
+ ]);
24
+ const execution = createOperationLakehouse(operation, lakehouse, signal, operationName);
25
+ try {
26
+ const output = await runOperation(operation, input, {
27
+ lakehouse: execution.lakehouse,
28
+ signal
29
+ });
30
+ const body = {
31
+ data: operation.output(output),
32
+ requestId,
33
+ queriedAt: new Date().toISOString(),
34
+ queryIds: execution.queryIds,
35
+ ...operation.policy.exposeSql && includeSql ? { queries: execution.queries } : {}
36
+ };
37
+ const serializedBody = JSON.stringify(body);
38
+ const responseBytes = new TextEncoder().encode(serializedBody).byteLength;
39
+ if (responseBytes > (operation.policy.maxResponseBytes ?? 1e6))
40
+ throw new Error("Response exceeds byte limit.");
41
+ return { body, serializedBody };
42
+ } catch (error) {
43
+ if (signal.aborted && !(error instanceof DataSourceError2))
44
+ throw new OperationTimeoutError;
45
+ throw error;
46
+ }
47
+ }
48
+ function createOperationLakehouse(operation, source, signal, operationName) {
49
+ const queryIds = [];
50
+ const queries = [];
51
+ const lakehouse = {
52
+ async queryAll(statement, options) {
53
+ if (!Number.isInteger(options.limit) || options.limit < 1)
54
+ throw new Error("Query needs a positive row limit.");
55
+ if (operation.queryNames && !Object.values(operation.queryNames).includes(options.name ?? ""))
56
+ throw new Error(`Query name is not registered for operation ${operationName}.`);
57
+ const query = {
58
+ name: options.name ?? `Query ${queries.length + 1}`,
59
+ statement
60
+ };
61
+ queries.push(query);
62
+ const limit = Math.min(options.limit, operation.policy.maxQueryRows);
63
+ let result;
64
+ try {
65
+ result = await source.queryAll(statement, {
66
+ limit,
67
+ signal: AbortSignal.any([signal, options.signal])
68
+ });
69
+ } catch (error) {
70
+ if (error instanceof DataSourceError2)
71
+ error.queryName = query.name;
72
+ throw error;
73
+ }
74
+ if (result.rows.length > limit)
75
+ throw new Error("Result exceeds row limit.");
76
+ if (result.queryId) {
77
+ query.queryId = result.queryId;
78
+ queryIds.push(result.queryId);
79
+ }
80
+ return result;
81
+ }
82
+ };
83
+ return { lakehouse, queries, queryIds };
84
+ }
85
+ async function runOperation(operation, input, { lakehouse, signal }) {
86
+ let abort;
87
+ try {
88
+ signal.throwIfAborted();
89
+ const cancelled = new Promise((_, reject) => {
90
+ function rejectAborted() {
91
+ reject(new Error("Request aborted."));
92
+ }
93
+ abort = rejectAborted;
94
+ signal.addEventListener("abort", rejectAborted, { once: true });
95
+ if (signal.aborted)
96
+ rejectAborted();
97
+ });
98
+ return await Promise.race([
99
+ cancelled,
100
+ Promise.resolve().then(() => {
101
+ signal.throwIfAborted();
102
+ return operation.run({ lakehouse, signal }, input);
103
+ })
104
+ ]);
105
+ } finally {
106
+ if (abort)
107
+ signal.removeEventListener("abort", abort);
108
+ }
109
+ }
110
+
111
+ class OperationTimeoutError extends Error {
112
+ }
113
+
114
+ class OperationInputError extends Error {
115
+ }
116
+ var sourceErrorMessages = {
117
+ unauthorized: "This app could not authenticate its data connection. Contact the app owner.",
118
+ forbidden: "This app cannot access the requested data. Contact the app owner.",
119
+ rate_limited: "The lakehouse is busy. Wait a moment and retry.",
120
+ query_rejected: "A lakehouse query was rejected. Check the app's data operation.",
121
+ unavailable: "The lakehouse is unavailable. Check the connection and retry."
122
+ };
123
+ function toDataOperationFailure(error, aborted = false) {
124
+ if (error instanceof OperationInputError)
125
+ return {
126
+ status: 400,
127
+ code: "invalid_input",
128
+ message: "Check this operation's inputs."
129
+ };
130
+ if (error instanceof DataSourceError2)
131
+ return {
132
+ status: error.reason === "rate_limited" ? 429 : 502,
133
+ code: `source_${error.reason}`,
134
+ message: sourceErrorMessages[error.reason]
135
+ };
136
+ const timedOut = aborted || error instanceof OperationTimeoutError;
137
+ return {
138
+ status: timedOut ? 504 : 502,
139
+ code: timedOut ? "timeout" : "query_failed",
140
+ message: timedOut ? "The data request timed out." : "The data request failed."
141
+ };
142
+ }
143
+
144
+ export { executeDataOperation, toDataOperationFailure };
145
+
146
+ //# debugId=066EC66CC0EAE1FD64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/core/operation.ts"],
4
+ "sourcesContent": [
5
+ "import { DataSourceError } from '@/src/core/contract';\nimport type {\n DataOperations,\n DisclosedQuery,\n Lakehouse,\n OperationContext,\n DataQueryBody,\n} from '@/src/core/operation-types';\n\ntype Operation = DataOperations[string];\ntype OperationExecutionOptions = {\n operationName: string;\n lakehouse: Lakehouse;\n signal: AbortSignal;\n /** Include query evidence in the response; this does not keep browser-owned SQL private. */\n includeSql: boolean;\n requestId: string;\n};\n\n/** Shared operation execution. Adapters own authorization, delivery, and public errors. */\nexport async function executeDataOperation(\n operation: Operation,\n value: unknown,\n {\n operationName,\n lakehouse,\n signal: requestSignal,\n includeSql,\n requestId,\n }: OperationExecutionOptions\n) {\n requestSignal.throwIfAborted();\n let input: unknown;\n try {\n input = operation.input(value);\n } catch {\n throw new OperationInputError();\n }\n\n const signal = AbortSignal.any([\n requestSignal,\n AbortSignal.timeout(operation.policy.maxDurationMs),\n ]);\n const execution = createOperationLakehouse(\n operation,\n lakehouse,\n signal,\n operationName\n );\n try {\n const output = await runOperation(operation, input, {\n lakehouse: execution.lakehouse,\n signal,\n });\n const body: DataQueryBody<unknown> = {\n data: operation.output(output),\n requestId,\n queriedAt: new Date().toISOString(),\n queryIds: execution.queryIds,\n ...(operation.policy.exposeSql && includeSql\n ? { queries: execution.queries }\n : {}),\n };\n const serializedBody = JSON.stringify(body);\n const responseBytes = new TextEncoder().encode(serializedBody).byteLength;\n if (responseBytes > (operation.policy.maxResponseBytes ?? 1_000_000))\n throw new Error('Response exceeds byte limit.');\n\n return { body, serializedBody };\n } catch (error) {\n if (signal.aborted && !(error instanceof DataSourceError))\n throw new OperationTimeoutError();\n throw error;\n }\n}\n\n/** Query evidence follows statement invocation order; query IDs retain completion order. */\nfunction createOperationLakehouse(\n operation: Operation,\n source: Lakehouse,\n signal: AbortSignal,\n operationName: string\n) {\n const queryIds: string[] = [];\n const queries: DisclosedQuery[] = [];\n const lakehouse: Lakehouse = {\n async queryAll(statement, options) {\n if (!Number.isInteger(options.limit) || options.limit < 1)\n throw new Error('Query needs a positive row limit.');\n if (\n operation.queryNames &&\n !Object.values(operation.queryNames).includes(options.name ?? '')\n )\n throw new Error(\n `Query name is not registered for operation ${operationName}.`\n );\n const query: DisclosedQuery = {\n name: options.name ?? `Query ${queries.length + 1}`,\n statement,\n };\n queries.push(query);\n const limit = Math.min(options.limit, operation.policy.maxQueryRows);\n let result;\n try {\n result = await source.queryAll(statement, {\n limit,\n signal: AbortSignal.any([signal, options.signal]),\n });\n } catch (error) {\n if (error instanceof DataSourceError) error.queryName = query.name;\n throw error;\n }\n if (result.rows.length > limit)\n throw new Error('Result exceeds row limit.');\n if (result.queryId) {\n query.queryId = result.queryId;\n queryIds.push(result.queryId);\n }\n\n return result;\n },\n };\n\n return { lakehouse, queries, queryIds };\n}\n\n/** Settle cancellation even when operation code or its lakehouse ignores the signal. */\nasync function runOperation(\n operation: Operation,\n input: unknown,\n { lakehouse, signal }: OperationContext\n) {\n let abort: (() => void) | undefined;\n try {\n signal.throwIfAborted();\n const cancelled = new Promise<never>((_, reject) => {\n function rejectAborted() {\n reject(new Error('Request aborted.'));\n }\n abort = rejectAborted;\n signal.addEventListener('abort', rejectAborted, { once: true });\n if (signal.aborted) rejectAborted();\n });\n\n return await Promise.race([\n cancelled,\n Promise.resolve().then(() => {\n signal.throwIfAborted();\n\n return operation.run({ lakehouse, signal }, input as never);\n }),\n ]);\n } finally {\n if (abort) signal.removeEventListener('abort', abort);\n }\n}\n\nclass OperationTimeoutError extends Error {}\n\nclass OperationInputError extends Error {}\n\nconst sourceErrorMessages = {\n unauthorized:\n 'This app could not authenticate its data connection. Contact the app owner.',\n forbidden:\n 'This app cannot access the requested data. Contact the app owner.',\n rate_limited: 'The lakehouse is busy. Wait a moment and retry.',\n query_rejected:\n \"A lakehouse query was rejected. Check the app's data operation.\",\n unavailable: 'The lakehouse is unavailable. Check the connection and retry.',\n};\n\nexport function toDataOperationFailure(error: unknown, aborted = false) {\n if (error instanceof OperationInputError)\n return {\n status: 400,\n code: 'invalid_input',\n message: \"Check this operation's inputs.\",\n };\n if (error instanceof DataSourceError)\n return {\n status: error.reason === 'rate_limited' ? 429 : 502,\n code: `source_${error.reason}`,\n message: sourceErrorMessages[error.reason],\n };\n const timedOut = aborted || error instanceof OperationTimeoutError;\n return {\n status: timedOut ? 504 : 502,\n code: timedOut ? 'timeout' : 'query_failed',\n message: timedOut\n ? 'The data request timed out.'\n : 'The data request failed.',\n };\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "066EC66CC0EAE1FD64756E2164756E21",
9
+ "names": []
10
+ }
@@ -1,6 +1,9 @@
1
1
  import {
2
2
  formatDateRange2
3
3
  } from "./contract-mev09s5v.js";
4
+ import {
5
+ defineMessageRoute2
6
+ } from "./contract-tf8c3qpv.js";
4
7
 
5
8
  // src/core/contract.ts
6
9
  import { parseDate, today } from "@internationalized/date";
@@ -139,6 +142,180 @@ function dimensionPredicate2(column, selection, allowedColumns) {
139
142
  ];
140
143
  return `(${clauses.join(" OR ")})`;
141
144
  }
145
+ // src/core/annotations.ts
146
+ function object(value) {
147
+ if (!value || typeof value !== "object" || Array.isArray(value))
148
+ throw new Error("Invalid annotation.");
149
+ return value;
150
+ }
151
+ function text(value, limit, required = false) {
152
+ if (typeof value !== "string" || value.length > limit || required && !value.trim())
153
+ throw new Error("Invalid annotation text.");
154
+ return value;
155
+ }
156
+ function names(value) {
157
+ if (!Array.isArray(value) || value.length > 20)
158
+ throw new Error("Invalid annotation references.");
159
+ return value.map((name) => text(name, 128, true));
160
+ }
161
+ function number(value, positive = false) {
162
+ if (typeof value !== "number" || !Number.isFinite(value) || Math.abs(value) > 1e6 || positive && value < 0)
163
+ throw new Error("Invalid annotation geometry.");
164
+ return value;
165
+ }
166
+ function parseDataAppAnnotationDraft2(value) {
167
+ const input = object(value);
168
+ const target = object(input.target);
169
+ const context = object(input.context);
170
+ const viewport = object(context.viewport);
171
+ const rect = object(context.rect);
172
+ if (target.kind !== "widget" && target.kind !== "element" && target.kind !== "app")
173
+ throw new Error("Invalid annotation target.");
174
+ let screenshot;
175
+ if (context.screenshot !== undefined) {
176
+ const image = object(context.screenshot);
177
+ const dataUrl = text(image.dataUrl, 350000, true);
178
+ if (image.mimeType !== "image/png" || !/^data:image\/png;base64,iVBORw0KGgo[A-Za-z0-9+/]*={0,2}$/.test(dataUrl))
179
+ throw new Error("Invalid annotation screenshot.");
180
+ const width = number(image.width, true);
181
+ const height = number(image.height, true);
182
+ if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1 || width > 1024 || height > 1024)
183
+ throw new Error("Invalid screenshot dimensions.");
184
+ const encoded = dataUrl.slice("data:image/png;base64,".length);
185
+ if (encoded.length % 4 !== 0 || encoded.length / 4 * 3 - (encoded.endsWith("==") ? 2 : encoded.endsWith("=") ? 1 : 0) > 262144)
186
+ throw new Error("Screenshot is too large.");
187
+ const header = atob(encoded.slice(0, 44));
188
+ if (header.length < 24 || header.slice(12, 16) !== "IHDR")
189
+ throw new Error("Invalid PNG header.");
190
+ const bytes = Uint8Array.from(header, (character) => character.charCodeAt(0));
191
+ const dimensions = new DataView(bytes.buffer);
192
+ if (dimensions.getUint32(16) !== width || dimensions.getUint32(20) !== height)
193
+ throw new Error("Screenshot dimensions do not match the image.");
194
+ screenshot = { mimeType: "image/png", dataUrl, width, height };
195
+ }
196
+ let anchor;
197
+ if (context.anchor !== undefined) {
198
+ const point = object(context.anchor);
199
+ const x = number(point.x, true);
200
+ const y = number(point.y, true);
201
+ if (x > 1 || y > 1)
202
+ throw new Error("Invalid annotation anchor.");
203
+ anchor = { x, y };
204
+ }
205
+ let cursor;
206
+ if (context.cursor !== undefined) {
207
+ const point = object(context.cursor);
208
+ cursor = { x: number(point.x), y: number(point.y) };
209
+ }
210
+ let region;
211
+ if (context.region !== undefined) {
212
+ const area = object(context.region);
213
+ region = {
214
+ x: number(area.x, true),
215
+ y: number(area.y, true),
216
+ width: number(area.width, true),
217
+ height: number(area.height, true)
218
+ };
219
+ if (target.kind !== "app" || region.width <= 0 || region.height <= 0 || region.x + region.width > 1.000001 || region.y + region.height > 1.000001)
220
+ throw new Error("Invalid annotation selection.");
221
+ }
222
+ const draft = {
223
+ id: text(input.id, 128, true),
224
+ target: {
225
+ id: text(target.id, 128, true),
226
+ label: text(target.label, 256, true),
227
+ kind: target.kind,
228
+ text: text(target.text, 1024),
229
+ queryNames: names(target.queryNames),
230
+ glossaryIds: names(target.glossaryIds)
231
+ },
232
+ context: {
233
+ ...anchor ? { anchor } : {},
234
+ ...region ? { region } : {},
235
+ ...cursor ? { cursor } : {},
236
+ ...screenshot ? { screenshot } : {},
237
+ search: text(context.search, 2048),
238
+ hash: text(context.hash, 1024),
239
+ viewport: {
240
+ width: number(viewport.width, true),
241
+ height: number(viewport.height, true)
242
+ },
243
+ rect: {
244
+ x: number(rect.x),
245
+ y: number(rect.y),
246
+ width: number(rect.width, true),
247
+ height: number(rect.height, true)
248
+ },
249
+ ...context.view === undefined ? {} : { view: text(context.view, 128) },
250
+ ...context.displayedInput === undefined ? {} : { displayedInput: context.displayedInput }
251
+ },
252
+ comment: text(input.comment, 2000, true).trim()
253
+ };
254
+ const json = JSON.stringify({
255
+ ...draft,
256
+ context: { ...draft.context, screenshot: undefined }
257
+ });
258
+ if (new TextEncoder().encode(json).byteLength > 12000)
259
+ throw new Error("Annotation is too large.");
260
+ return JSON.parse(JSON.stringify(draft));
261
+ }
262
+ var annotationDraftRoute2 = /* @__PURE__ */ defineMessageRoute2({
263
+ input: parseDataAppAnnotationDraft2,
264
+ output(value) {
265
+ if (value !== null)
266
+ throw new Error("Invalid annotation response.");
267
+ return null;
268
+ }
269
+ });
270
+ var annotationEditorStateRoute2 = /* @__PURE__ */ defineMessageRoute2({
271
+ input(value) {
272
+ const input = object(value);
273
+ if (typeof input.hasUnsavedChanges !== "boolean")
274
+ throw new Error("Invalid annotation editor state.");
275
+ return { hasUnsavedChanges: input.hasUnsavedChanges };
276
+ },
277
+ output(value) {
278
+ if (value !== null)
279
+ throw new Error("Invalid annotation response.");
280
+ return null;
281
+ }
282
+ });
283
+ var annotationUpdateRoute2 = /* @__PURE__ */ defineMessageRoute2({
284
+ input(value) {
285
+ const input = object(value);
286
+ return {
287
+ id: text(input.id, 128, true),
288
+ comment: text(input.comment, 2000, true).trim()
289
+ };
290
+ },
291
+ output(value) {
292
+ if (value !== null)
293
+ throw new Error("Invalid annotation response.");
294
+ return null;
295
+ }
296
+ });
297
+ var annotationModeRoute2 = /* @__PURE__ */ defineMessageRoute2({
298
+ input(value) {
299
+ const input = object(value);
300
+ if (typeof input.active !== "boolean")
301
+ throw new Error("Invalid annotation mode.");
302
+ return { active: input.active };
303
+ },
304
+ output(value) {
305
+ if (value !== null)
306
+ throw new Error("Invalid annotation response.");
307
+ return null;
308
+ }
309
+ });
310
+ function isAnnotationPresentation(value) {
311
+ if (!value || typeof value !== "object")
312
+ return false;
313
+ const input = value;
314
+ return typeof input.enabled === "boolean" && (input.active === undefined || typeof input.active === "boolean") && (input.pinsVisible === undefined || typeof input.pinsVisible === "boolean") && (input.showHint === undefined || typeof input.showHint === "boolean") && (input.readOnly === undefined || typeof input.readOnly === "boolean") && (input.selectedAnnotationId === undefined || typeof input.selectedAnnotationId === "string" && input.selectedAnnotationId.length <= 128) && (input.selectionId === undefined || typeof input.selectionId === "string" && input.selectionId.length <= 128) && (input.selectedTargetId === undefined || typeof input.selectedTargetId === "string" && input.selectedTargetId.length <= 128) && (input.targets === undefined || Array.isArray(input.targets) && input.targets.length <= 50 && input.targets.every((target) => target && typeof target.id === "string" && target.id.length <= 128 && typeof target.targetId === "string" && target.targetId.length <= 128 && Number.isSafeInteger(target.number) && target.number > 0 && (target.anchor === undefined || typeof target.anchor.x === "number" && Number.isFinite(target.anchor.x) && target.anchor.x >= 0 && target.anchor.x <= 1 && typeof target.anchor.y === "number" && Number.isFinite(target.anchor.y) && target.anchor.y >= 0 && target.anchor.y <= 1) && (target.region === undefined || typeof target.region === "object" && target.region !== null && ["x", "y", "width", "height"].every((key) => {
315
+ const value = target.region[key];
316
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
317
+ }) && target.region.width > 0 && target.region.height > 0 && target.region.x + target.region.width <= 1.000001 && target.region.y + target.region.height <= 1.000001) && (target.comment === undefined || typeof target.comment === "string" && target.comment.length <= 2000)));
318
+ }
142
319
 
143
320
  // src/core/contract.ts
144
321
  function parseEmptyInput2(value) {
@@ -323,6 +500,6 @@ function connectionCheck2() {
323
500
  });
324
501
  }
325
502
 
326
- export { invariant, dimensionMemberKey, dimensionFilter2, parseDimensionSelection2, parseFacetOptions2, dimensionPredicate2, parseEmptyInput2, parseTrue2, previousDateRange2, defineDateRangeContract2, parseDateRangeInput2, parseCount2, parseLabel2, rowsAsRecords2, defineQueryNames2, DataSourceError2, defineOperation2, connectionQueryNames2, connectionCheck2 };
503
+ export { invariant, dimensionMemberKey, dimensionFilter2, parseDimensionSelection2, parseFacetOptions2, dimensionPredicate2, parseDataAppAnnotationDraft2, annotationDraftRoute2, annotationEditorStateRoute2, annotationUpdateRoute2, annotationModeRoute2, isAnnotationPresentation, parseEmptyInput2, parseTrue2, previousDateRange2, defineDateRangeContract2, parseDateRangeInput2, parseCount2, parseLabel2, rowsAsRecords2, defineQueryNames2, DataSourceError2, defineOperation2, connectionQueryNames2, connectionCheck2 };
327
504
 
328
- //# debugId=44D567B58A731E4A64756E2164756E21
505
+ //# debugId=876507C08B805AF964756E2164756E21