dataface 0.1.2__py3-none-any.whl

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 (455) hide show
  1. d3_format/__init__.py +14 -0
  2. d3_format/errors.py +19 -0
  3. d3_format/format.py +551 -0
  4. d3_format/spec.py +159 -0
  5. dataface/DATAFACE_SYNTAX.md +1135 -0
  6. dataface/__init__.py +93 -0
  7. dataface/_docs_site.py +20 -0
  8. dataface/_install_hint.py +26 -0
  9. dataface/agent_api/__init__.py +79 -0
  10. dataface/agent_api/_init_templates/__init__.py +0 -0
  11. dataface/agent_api/_init_templates/agents_dft_snippet.md +26 -0
  12. dataface/agent_api/_init_templates/dataface.yml +15 -0
  13. dataface/agent_api/_init_templates/faces-dataface.yml +144 -0
  14. dataface/agent_api/_init_templates/index.md +24 -0
  15. dataface/agent_api/_paths.py +118 -0
  16. dataface/agent_api/_project_agents_md.py +43 -0
  17. dataface/agent_api/_session_store.py +486 -0
  18. dataface/agent_api/_state.py +28 -0
  19. dataface/agent_api/chat.py +221 -0
  20. dataface/agent_api/dashboards.py +257 -0
  21. dataface/agent_api/describe.py +366 -0
  22. dataface/agent_api/describe_query.py +120 -0
  23. dataface/agent_api/docs/__init__.py +25 -0
  24. dataface/agent_api/docs/_loader.py +292 -0
  25. dataface/agent_api/docs/yaml-reference.md +2757 -0
  26. dataface/agent_api/file_refs.py +118 -0
  27. dataface/agent_api/init.py +126 -0
  28. dataface/agent_api/inspect.py +128 -0
  29. dataface/agent_api/mcp_install.py +170 -0
  30. dataface/agent_api/query.py +274 -0
  31. dataface/agent_api/schema.py +658 -0
  32. dataface/agent_api/schema_search.py +284 -0
  33. dataface/agent_api/search.py +270 -0
  34. dataface/agent_api/skill_install.py +141 -0
  35. dataface/agent_api/skill_render.py +90 -0
  36. dataface/agent_api/skills.py +293 -0
  37. dataface/agent_api/surface_aliases.yaml +128 -0
  38. dataface/agent_api/validate.py +175 -0
  39. dataface/agent_api/validate_query.py +84 -0
  40. dataface/ai/__init__.py +39 -0
  41. dataface/ai/agent.py +139 -0
  42. dataface/ai/context.py +45 -0
  43. dataface/ai/events.py +62 -0
  44. dataface/ai/external_mcp.py +610 -0
  45. dataface/ai/generate_sql.py +96 -0
  46. dataface/ai/llm.py +403 -0
  47. dataface/ai/mcp/__init__.py +51 -0
  48. dataface/ai/mcp/server.py +289 -0
  49. dataface/ai/memories.py +85 -0
  50. dataface/ai/prompts.py +177 -0
  51. dataface/ai/schema_context.py +138 -0
  52. dataface/ai/skills/before-after-comparison/SKILL.md +102 -0
  53. dataface/ai/skills/before-after-comparison/examples/before-after-comparison.yml +24 -0
  54. dataface/ai/skills/dashboard-build/SKILL.md +212 -0
  55. dataface/ai/skills/dashboard-build/examples/_smoke.yml +15 -0
  56. dataface/ai/skills/dashboard-design/SKILL.md +182 -0
  57. dataface/ai/skills/dashboard-review/SKILL.md +113 -0
  58. dataface/ai/skills/dashboard-structural-review/SKILL.md +173 -0
  59. dataface/ai/skills/dashboard-visual-review/SKILL.md +139 -0
  60. dataface/ai/skills/dataface-mcp-setup/SKILL.md +177 -0
  61. dataface/ai/skills/dataface-troubleshooting/SKILL.md +225 -0
  62. dataface/ai/skills/drill-down-link/SKILL.md +112 -0
  63. dataface/ai/skills/drill-down-link/examples/drill-down-link.yml +27 -0
  64. dataface/ai/skills/faceted-small-multiples/SKILL.md +116 -0
  65. dataface/ai/skills/faceted-small-multiples/examples/faceted-small-multiples.yml +33 -0
  66. dataface/ai/skills/filter-bar-with-variables/SKILL.md +105 -0
  67. dataface/ai/skills/filter-bar-with-variables/examples/filter-bar-with-variables.yml +49 -0
  68. dataface/ai/skills/kpi-row/SKILL.md +101 -0
  69. dataface/ai/skills/kpi-row/examples/kpi-row.yml +55 -0
  70. dataface/ai/skills/report-design/SKILL.md +184 -0
  71. dataface/ai/skills/single-metric-bignum/SKILL.md +90 -0
  72. dataface/ai/skills/single-metric-bignum/examples/single-metric-bignum.yml +27 -0
  73. dataface/ai/skills/table-heavy-ops-dashboard/SKILL.md +114 -0
  74. dataface/ai/skills/table-heavy-ops-dashboard/examples/table-heavy-ops-dashboard.yml +48 -0
  75. dataface/ai/skills/time-series-trend/SKILL.md +93 -0
  76. dataface/ai/skills/time-series-trend/examples/time-series-trend.yml +26 -0
  77. dataface/ai/skills/top-n-with-detail/SKILL.md +98 -0
  78. dataface/ai/skills/top-n-with-detail/examples/top-n-with-detail.yml +45 -0
  79. dataface/ai/skills/two-by-two-grid-overview/SKILL.md +78 -0
  80. dataface/ai/skills/two-by-two-grid-overview/examples/two-by-two-grid-overview.yml +64 -0
  81. dataface/ai/tool_schemas.py +132 -0
  82. dataface/ai/tools/__init__.py +312 -0
  83. dataface/ai/yaml_utils.py +57 -0
  84. dataface/cli/__init__.py +3 -0
  85. dataface/cli/_console.py +48 -0
  86. dataface/cli/_error_format.py +83 -0
  87. dataface/cli/_extras.py +190 -0
  88. dataface/cli/_json_output.py +8 -0
  89. dataface/cli/_parsing.py +17 -0
  90. dataface/cli/_version_info.py +56 -0
  91. dataface/cli/commands/__init__.py +3 -0
  92. dataface/cli/commands/_agent_input.py +205 -0
  93. dataface/cli/commands/_agent_server.py +115 -0
  94. dataface/cli/commands/chat.py +645 -0
  95. dataface/cli/commands/describe.py +107 -0
  96. dataface/cli/commands/docs.py +131 -0
  97. dataface/cli/commands/extension.py +179 -0
  98. dataface/cli/commands/init.py +240 -0
  99. dataface/cli/commands/inspect.py +94 -0
  100. dataface/cli/commands/mcp_init.py +167 -0
  101. dataface/cli/commands/query.py +386 -0
  102. dataface/cli/commands/render.py +291 -0
  103. dataface/cli/commands/schema.py +411 -0
  104. dataface/cli/commands/search.py +49 -0
  105. dataface/cli/commands/serve.py +114 -0
  106. dataface/cli/commands/skills.py +133 -0
  107. dataface/cli/commands/skills_init.py +161 -0
  108. dataface/cli/commands/validate.py +63 -0
  109. dataface/cli/main.py +1501 -0
  110. dataface/core/__init__.py +75 -0
  111. dataface/core/compile/__init__.py +244 -0
  112. dataface/core/compile/_jinja_helpers.py +78 -0
  113. dataface/core/compile/channel.py +222 -0
  114. dataface/core/compile/chart_focus.py +101 -0
  115. dataface/core/compile/chart_resolved.py +169 -0
  116. dataface/core/compile/chart_type_detection.py +489 -0
  117. dataface/core/compile/chart_update.py +261 -0
  118. dataface/core/compile/colors.py +64 -0
  119. dataface/core/compile/compiler.py +904 -0
  120. dataface/core/compile/config.py +823 -0
  121. dataface/core/compile/custom_chart_types.py +208 -0
  122. dataface/core/compile/data_table_attachment.py +1287 -0
  123. dataface/core/compile/detect.py +110 -0
  124. dataface/core/compile/errors.py +302 -0
  125. dataface/core/compile/filter_injection.py +319 -0
  126. dataface/core/compile/introspection.py +527 -0
  127. dataface/core/compile/jinja.py +511 -0
  128. dataface/core/compile/labels_env.py +52 -0
  129. dataface/core/compile/markdown.py +154 -0
  130. dataface/core/compile/meta.py +388 -0
  131. dataface/core/compile/models/__init__.py +0 -0
  132. dataface/core/compile/models/chart/__init__.py +0 -0
  133. dataface/core/compile/models/chart/authored.py +2137 -0
  134. dataface/core/compile/models/chart/compiled.py +398 -0
  135. dataface/core/compile/models/config.py +347 -0
  136. dataface/core/compile/models/face/__init__.py +0 -0
  137. dataface/core/compile/models/face/authored.py +659 -0
  138. dataface/core/compile/models/face/compiled.py +522 -0
  139. dataface/core/compile/models/factories.py +201 -0
  140. dataface/core/compile/models/markers.py +40 -0
  141. dataface/core/compile/models/palette.py +36 -0
  142. dataface/core/compile/models/primitives.py +415 -0
  143. dataface/core/compile/models/query/__init__.py +0 -0
  144. dataface/core/compile/models/query/authored.py +246 -0
  145. dataface/core/compile/models/query/compiled.py +710 -0
  146. dataface/core/compile/models/refs.py +137 -0
  147. dataface/core/compile/models/source.py +611 -0
  148. dataface/core/compile/models/style/__init__.py +0 -0
  149. dataface/core/compile/models/style/authored.py +481 -0
  150. dataface/core/compile/models/style/compiled.py +3399 -0
  151. dataface/core/compile/models/style/merged.py +1682 -0
  152. dataface/core/compile/models/theme.py +362 -0
  153. dataface/core/compile/models/variable/__init__.py +0 -0
  154. dataface/core/compile/models/variable/authored.py +254 -0
  155. dataface/core/compile/models/vega_lite/__init__.py +0 -0
  156. dataface/core/compile/models/vega_lite/config.py +510 -0
  157. dataface/core/compile/models/vega_lite/contracts.py +171 -0
  158. dataface/core/compile/normalize_charts.py +494 -0
  159. dataface/core/compile/normalize_layout.py +1000 -0
  160. dataface/core/compile/normalize_queries.py +297 -0
  161. dataface/core/compile/normalize_variables.py +489 -0
  162. dataface/core/compile/normalizer.py +543 -0
  163. dataface/core/compile/palette.py +1100 -0
  164. dataface/core/compile/parameterized.py +658 -0
  165. dataface/core/compile/parser.py +228 -0
  166. dataface/core/compile/schema.py +20 -0
  167. dataface/core/compile/schema_renderers/__init__.py +0 -0
  168. dataface/core/compile/schema_renderers/json_schema.py +163 -0
  169. dataface/core/compile/schema_renderers/prompt.py +152 -0
  170. dataface/core/compile/schema_renderers/vscode_schema.py +301 -0
  171. dataface/core/compile/sizing.py +2126 -0
  172. dataface/core/compile/sources.py +518 -0
  173. dataface/core/compile/sql_authoring_lint.py +56 -0
  174. dataface/core/compile/style_cascade.py +471 -0
  175. dataface/core/compile/typography.py +299 -0
  176. dataface/core/compile/validator.py +301 -0
  177. dataface/core/compile/variables.py +53 -0
  178. dataface/core/compile/vega_config.py +98 -0
  179. dataface/core/compile/vega_lite/__init__.py +6 -0
  180. dataface/core/compile/vega_lite/validation.py +95 -0
  181. dataface/core/compile/yaml_error_formatter.py +838 -0
  182. dataface/core/connections.py +38 -0
  183. dataface/core/dashboard.py +358 -0
  184. dataface/core/defaults/default_config.yml +101 -0
  185. dataface/core/defaults/palettes/categorical/category-10-dark.yml +32 -0
  186. dataface/core/defaults/palettes/categorical/category-10-light.yml +43 -0
  187. dataface/core/defaults/palettes/categorical/category-10.yml +31 -0
  188. dataface/core/defaults/palettes/categorical/category-6-tonal-blue.yml +22 -0
  189. dataface/core/defaults/palettes/categorical/category-6-tonal-brown.yml +29 -0
  190. dataface/core/defaults/palettes/categorical/category-6-tonal-green.yml +20 -0
  191. dataface/core/defaults/palettes/categorical/category-6-tonal-orange.yml +21 -0
  192. dataface/core/defaults/palettes/categorical/category-6-tonal-purple.yml +20 -0
  193. dataface/core/defaults/palettes/categorical/editorial-10-dark.yml +32 -0
  194. dataface/core/defaults/palettes/categorical/editorial-10.yml +40 -0
  195. dataface/core/defaults/palettes/categorical/hero-6.yml +17 -0
  196. dataface/core/defaults/palettes/categorical/single-blue.yml +11 -0
  197. dataface/core/defaults/palettes/categorical/tableau.yml +20 -0
  198. dataface/core/defaults/palettes/data/xkcd_colors.json +3803 -0
  199. dataface/core/defaults/palettes/diverging/blue-red.yml +25 -0
  200. dataface/core/defaults/palettes/diverging/coolwarm.yml +24 -0
  201. dataface/core/defaults/palettes/diverging/crimson-green.yml +23 -0
  202. dataface/core/defaults/palettes/diverging/orange-teal.yml +23 -0
  203. dataface/core/defaults/palettes/diverging/sunset.yml +24 -0
  204. dataface/core/defaults/palettes/scaffold/dft-creams.yml +38 -0
  205. dataface/core/defaults/palettes/scaffold/dft-grays.yml +53 -0
  206. dataface/core/defaults/palettes/sequential/amber.yml +22 -0
  207. dataface/core/defaults/palettes/sequential/blue.yml +22 -0
  208. dataface/core/defaults/palettes/sequential/brown.yml +22 -0
  209. dataface/core/defaults/palettes/sequential/gray.yml +22 -0
  210. dataface/core/defaults/palettes/sequential/green.yml +22 -0
  211. dataface/core/defaults/palettes/sequential/purple.yml +22 -0
  212. dataface/core/defaults/palettes/sequential/rust.yml +22 -0
  213. dataface/core/defaults/palettes/sequential/teal.yml +22 -0
  214. dataface/core/defaults/palettes/tone/negative.yml +32 -0
  215. dataface/core/defaults/palettes/tone/positive.yml +22 -0
  216. dataface/core/defaults/palettes/tone/warning.yml +22 -0
  217. dataface/core/defaults/themes/_base.yaml +786 -0
  218. dataface/core/defaults/themes/bi.yaml +16 -0
  219. dataface/core/defaults/themes/carbong100.yaml +41 -0
  220. dataface/core/defaults/themes/cream.yaml +122 -0
  221. dataface/core/defaults/themes/dark.yaml +40 -0
  222. dataface/core/defaults/themes/diagnostics-title-angle-extreme.yaml +9 -0
  223. dataface/core/defaults/themes/diagnostics-title-baseline-extreme.yaml +9 -0
  224. dataface/core/defaults/themes/diagnostics-title-baseline.yaml +24 -0
  225. dataface/core/defaults/themes/diagnostics-title-center.yaml +8 -0
  226. dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +24 -0
  227. dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +25 -0
  228. dataface/core/defaults/themes/diagnostics-title-left.yaml +8 -0
  229. dataface/core/defaults/themes/diagnostics-title-offset-extreme.yaml +9 -0
  230. dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +24 -0
  231. dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +24 -0
  232. dataface/core/defaults/themes/editorial.yaml +147 -0
  233. dataface/core/defaults/themes/light.yaml +30 -0
  234. dataface/core/defaults/themes/looker.yaml +17 -0
  235. dataface/core/defaults/themes/stark.yaml +134 -0
  236. dataface/core/errors/__init__.py +67 -0
  237. dataface/core/errors/codes_compile.py +56 -0
  238. dataface/core/errors/codes_execute.py +177 -0
  239. dataface/core/errors/codes_render.py +106 -0
  240. dataface/core/errors/codes_unknown.py +15 -0
  241. dataface/core/errors/hints.py +74 -0
  242. dataface/core/errors/registry.py +42 -0
  243. dataface/core/errors/structured.py +92 -0
  244. dataface/core/execute/__init__.py +91 -0
  245. dataface/core/execute/adapters/__init__.py +49 -0
  246. dataface/core/execute/adapters/adapter_registry.py +400 -0
  247. dataface/core/execute/adapters/base.py +245 -0
  248. dataface/core/execute/adapters/csv_adapter.py +239 -0
  249. dataface/core/execute/adapters/dbt_adapter.py +283 -0
  250. dataface/core/execute/adapters/dbt_adapter_factory.py +212 -0
  251. dataface/core/execute/adapters/dbt_macro_loader.py +95 -0
  252. dataface/core/execute/adapters/dbt_utils.py +150 -0
  253. dataface/core/execute/adapters/http_adapter.py +224 -0
  254. dataface/core/execute/adapters/metricflow_adapter.py +94 -0
  255. dataface/core/execute/adapters/schema_resolver_adapter.py +144 -0
  256. dataface/core/execute/adapters/sql_adapter.py +710 -0
  257. dataface/core/execute/adapters/values_adapter.py +58 -0
  258. dataface/core/execute/batch.py +744 -0
  259. dataface/core/execute/cache_backend.py +135 -0
  260. dataface/core/execute/cache_keys.py +66 -0
  261. dataface/core/execute/dbt_jinja.py +21 -0
  262. dataface/core/execute/dialects/__init__.py +121 -0
  263. dataface/core/execute/dialects/athena.py +75 -0
  264. dataface/core/execute/dialects/base.py +302 -0
  265. dataface/core/execute/dialects/bigquery.py +38 -0
  266. dataface/core/execute/dialects/databricks.py +68 -0
  267. dataface/core/execute/dialects/duckdb.py +35 -0
  268. dataface/core/execute/dialects/mysql.py +68 -0
  269. dataface/core/execute/dialects/postgres.py +39 -0
  270. dataface/core/execute/dialects/redshift.py +12 -0
  271. dataface/core/execute/dialects/snowflake.py +51 -0
  272. dataface/core/execute/dialects/sqlserver.py +92 -0
  273. dataface/core/execute/duckdb_cache.py +712 -0
  274. dataface/core/execute/duckdb_config.py +26 -0
  275. dataface/core/execute/errors.py +213 -0
  276. dataface/core/execute/executor.py +1249 -0
  277. dataface/core/execute/parallel.py +162 -0
  278. dataface/core/execute/setup_sql.py +58 -0
  279. dataface/core/execute/source_registry.py +72 -0
  280. dataface/core/execute/source_resolver.py +255 -0
  281. dataface/core/execute/sql_guard.py +387 -0
  282. dataface/core/execute/sql_literals.py +199 -0
  283. dataface/core/fonts.py +52 -0
  284. dataface/core/inspect/__init__.py +32 -0
  285. dataface/core/inspect/cache_factory.py +98 -0
  286. dataface/core/inspect/db_types.py +162 -0
  287. dataface/core/inspect/dbt_schema.py +96 -0
  288. dataface/core/inspect/defaults.yml +37 -0
  289. dataface/core/inspect/fanout_risk.py +109 -0
  290. dataface/core/inspect/manifest_utils.py +77 -0
  291. dataface/core/inspect/partials/categorical.yml +40 -0
  292. dataface/core/inspect/partials/date.yml +40 -0
  293. dataface/core/inspect/partials/numeric.yml +55 -0
  294. dataface/core/inspect/partition_types.py +38 -0
  295. dataface/core/inspect/query_validator.py +975 -0
  296. dataface/core/inspect/renderer.py +354 -0
  297. dataface/core/inspect/resolver.py +808 -0
  298. dataface/core/inspect/search.py +461 -0
  299. dataface/core/inspect/sources/__init__.py +32 -0
  300. dataface/core/inspect/sources/dbt.py +738 -0
  301. dataface/core/inspect/sources/duckdb_utils.py +66 -0
  302. dataface/core/inspect/templates/__init__.py +1 -0
  303. dataface/core/inspect/templates/categorical_column.yml +196 -0
  304. dataface/core/inspect/templates/charts.yml +109 -0
  305. dataface/core/inspect/templates/date_column.yml +248 -0
  306. dataface/core/inspect/templates/model.yml +138 -0
  307. dataface/core/inspect/templates/numeric_column.yml +261 -0
  308. dataface/core/inspect/templates/quality.yml +80 -0
  309. dataface/core/inspect/templates/string_column.yml +263 -0
  310. dataface/core/project_roots.py +165 -0
  311. dataface/core/render/__init__.py +87 -0
  312. dataface/core/render/board_links.py +176 -0
  313. dataface/core/render/chart/__init__.py +27 -0
  314. dataface/core/render/chart/arc_attached_table.py +251 -0
  315. dataface/core/render/chart/artifacts.py +16 -0
  316. dataface/core/render/chart/callout.py +225 -0
  317. dataface/core/render/chart/decisions.py +358 -0
  318. dataface/core/render/chart/geo.py +700 -0
  319. dataface/core/render/chart/kpi.py +916 -0
  320. dataface/core/render/chart/labels.py +76 -0
  321. dataface/core/render/chart/pipeline.py +818 -0
  322. dataface/core/render/chart/presentation.py +36 -0
  323. dataface/core/render/chart/profile.py +3438 -0
  324. dataface/core/render/chart/render_single.py +347 -0
  325. dataface/core/render/chart/renderers.py +193 -0
  326. dataface/core/render/chart/rendering.py +565 -0
  327. dataface/core/render/chart/serialization.py +90 -0
  328. dataface/core/render/chart/spark.py +496 -0
  329. dataface/core/render/chart/spark_bar.py +370 -0
  330. dataface/core/render/chart/spec_builders.py +154 -0
  331. dataface/core/render/chart/standard_renderer.py +2645 -0
  332. dataface/core/render/chart/table.py +2957 -0
  333. dataface/core/render/chart/table_support.py +1452 -0
  334. dataface/core/render/chart/tick_values.py +66 -0
  335. dataface/core/render/chart/time_unit_detect.py +809 -0
  336. dataface/core/render/chart/title_overflow.py +157 -0
  337. dataface/core/render/chart/type_inference.py +122 -0
  338. dataface/core/render/chart/validation.py +99 -0
  339. dataface/core/render/chart/vega_lite.py +125 -0
  340. dataface/core/render/chart/vega_lite_types.py +268 -0
  341. dataface/core/render/chart/vl_field_maps.py +346 -0
  342. dataface/core/render/chart_interactivity.py +24 -0
  343. dataface/core/render/control_registry.py +287 -0
  344. dataface/core/render/converters/__init__.py +24 -0
  345. dataface/core/render/converters/chart.py +276 -0
  346. dataface/core/render/converters/html.py +98 -0
  347. dataface/core/render/converters/pdf.py +40 -0
  348. dataface/core/render/converters/png.py +41 -0
  349. dataface/core/render/errors.py +144 -0
  350. dataface/core/render/face_api.py +160 -0
  351. dataface/core/render/faces.py +1194 -0
  352. dataface/core/render/font_measurement.py +48 -0
  353. dataface/core/render/font_support.py +197 -0
  354. dataface/core/render/fonts/DFTSansTabular-Regular.ttf +0 -0
  355. dataface/core/render/fonts/DFTSansTabular-Regular.woff2 +0 -0
  356. dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.ttf +0 -0
  357. dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.ttf +0 -0
  358. dataface/core/render/fonts/InterVariable.ttf +0 -0
  359. dataface/core/render/fonts/InterVariable.woff2 +0 -0
  360. dataface/core/render/fonts/NOTO_COLOR_EMOJI_LICENSE.txt +93 -0
  361. dataface/core/render/fonts/NOTO_EMOJI_LICENSE.txt +93 -0
  362. dataface/core/render/fonts/NotoColorEmoji-Regular.ttf +0 -0
  363. dataface/core/render/fonts/NotoColorEmoji-Regular.woff2 +0 -0
  364. dataface/core/render/fonts/NotoEmoji-Regular.ttf +0 -0
  365. dataface/core/render/fonts/NotoEmoji-Regular.woff2 +0 -0
  366. dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -0
  367. dataface/core/render/fonts/SOURCE_SERIF_4_LICENSE.txt +98 -0
  368. dataface/core/render/fonts/SourceCodePro-Regular.ttf +0 -0
  369. dataface/core/render/fonts/SourceSerif4-Regular.ttf +0 -0
  370. dataface/core/render/fonts/_emoji_font_face.css +43 -0
  371. dataface/core/render/fonts/source-serif-4-variable-latin.woff2 +0 -0
  372. dataface/core/render/format_utils.py +329 -0
  373. dataface/core/render/geo_defaults.yml +28 -0
  374. dataface/core/render/json_format.py +146 -0
  375. dataface/core/render/layout_sizing.py +865 -0
  376. dataface/core/render/layouts.py +541 -0
  377. dataface/core/render/markdown_defaults.yml +16 -0
  378. dataface/core/render/missing_vars_prompt.py +79 -0
  379. dataface/core/render/placeholder.py +389 -0
  380. dataface/core/render/render_result.py +14 -0
  381. dataface/core/render/renderer.py +467 -0
  382. dataface/core/render/script_embedding.py +16 -0
  383. dataface/core/render/svg_utils.py +212 -0
  384. dataface/core/render/template_loader.py +69 -0
  385. dataface/core/render/templates/controls/_styles.css +606 -0
  386. dataface/core/render/templates/controls/checkbox.html +16 -0
  387. dataface/core/render/templates/controls/date.html +16 -0
  388. dataface/core/render/templates/controls/number.html +19 -0
  389. dataface/core/render/templates/controls/readonly.html +9 -0
  390. dataface/core/render/templates/controls/select.html +21 -0
  391. dataface/core/render/templates/controls/slider.html +22 -0
  392. dataface/core/render/templates/controls/text.html +16 -0
  393. dataface/core/render/templates/scripts/chart_interactivity.js +191 -0
  394. dataface/core/render/templates/scripts/variables.js +976 -0
  395. dataface/core/render/templates/svg/grid_pattern.svg +3 -0
  396. dataface/core/render/templates/svg/styles.css +51 -0
  397. dataface/core/render/terminal.py +311 -0
  398. dataface/core/render/terminal_charts.py +563 -0
  399. dataface/core/render/terminal_defaults.yml +2 -0
  400. dataface/core/render/terminal_layouts.py +299 -0
  401. dataface/core/render/terminal_text.py +31 -0
  402. dataface/core/render/text/__init__.py +1 -0
  403. dataface/core/render/text/case.py +113 -0
  404. dataface/core/render/text_format.py +129 -0
  405. dataface/core/render/utils.py +106 -0
  406. dataface/core/render/variable_controls.py +946 -0
  407. dataface/core/render/variable_input_refinement.py +140 -0
  408. dataface/core/render/warnings/__init__.py +15 -0
  409. dataface/core/render/warnings/bar_color_1_to_1_with_x.py +80 -0
  410. dataface/core/render/warnings/base.py +44 -0
  411. dataface/core/render/warnings/fanout_risk.py +15 -0
  412. dataface/core/render/warnings/from_query_diagnostic.py +56 -0
  413. dataface/core/render/warnings/missing_join_predicate.py +13 -0
  414. dataface/core/render/warnings/query_parse_error.py +14 -0
  415. dataface/core/render/warnings/query_returned_zero_rows.py +42 -0
  416. dataface/core/render/warnings/reaggregation.py +14 -0
  417. dataface/core/render/warnings/registry.py +45 -0
  418. dataface/core/render/warnings/suppression.py +46 -0
  419. dataface/core/render/warnings/temporal_single_point.py +63 -0
  420. dataface/core/render/warnings/unreferenced_chart.py +15 -0
  421. dataface/core/render/warnings/y_encoding_mostly_null.py +76 -0
  422. dataface/core/render/yaml_format.py +167 -0
  423. dataface/core/resolve_face.py +195 -0
  424. dataface/core/schema/__init__.py +0 -0
  425. dataface/core/schema/guidance.py +151 -0
  426. dataface/core/scoped_paths.py +59 -0
  427. dataface/core/serve/__init__.py +14 -0
  428. dataface/core/serve/bootstrap.py +39 -0
  429. dataface/core/serve/embedded.py +57 -0
  430. dataface/core/serve/port.py +129 -0
  431. dataface/core/serve/server.py +938 -0
  432. dataface/core/serve/templates/__init__.py +0 -0
  433. dataface/core/serve/templates/directory.yml +6 -0
  434. dataface/core/serve/templates/error.html.j2 +217 -0
  435. dataface/core/utils.py +121 -0
  436. dataface/core/validate.py +64 -0
  437. dataface/integrations/__init__.py +0 -0
  438. dataface/integrations/highlighting.py +351 -0
  439. dataface/integrations/markdown.py +537 -0
  440. dataface/py.typed +0 -0
  441. dataface-0.1.2.dist-info/METADATA +375 -0
  442. dataface-0.1.2.dist-info/RECORD +455 -0
  443. dataface-0.1.2.dist-info/WHEEL +4 -0
  444. dataface-0.1.2.dist-info/entry_points.txt +2 -0
  445. dataface-0.1.2.dist-info/licenses/LICENSE +202 -0
  446. mdsvg/__init__.py +168 -0
  447. mdsvg/fonts.py +656 -0
  448. mdsvg/images.py +299 -0
  449. mdsvg/parser.py +629 -0
  450. mdsvg/playground.py +284 -0
  451. mdsvg/py.typed +2 -0
  452. mdsvg/renderer.py +1623 -0
  453. mdsvg/style.py +355 -0
  454. mdsvg/types.py +200 -0
  455. mdsvg/utils.py +86 -0
@@ -0,0 +1,69 @@
1
+ """Template loader for SVG/HTML templates.
2
+
3
+ Stage: RENDER
4
+ Purpose: Load and render Jinja2 templates for SVG and HTML rendering.
5
+
6
+ This module provides a single function for loading templates from the
7
+ render/templates directory and rendering them with Jinja2.
8
+
9
+ Note: The global _jinja_env uses lazy initialization without locking.
10
+ This is acceptable for single-threaded use. In multi-threaded contexts
11
+ (e.g., Django), the worst case is redundant Environment creation.
12
+ """
13
+
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ from jinja2 import (
18
+ ChoiceLoader,
19
+ DictLoader,
20
+ Environment,
21
+ FileSystemLoader,
22
+ select_autoescape,
23
+ )
24
+
25
+ # Lazy-initialized Jinja environment (not thread-safe, but acceptable)
26
+ _jinja_env: Environment | None = None
27
+
28
+
29
+ def _get_jinja_env() -> Environment:
30
+ """Get or create Jinja environment for templates."""
31
+ global _jinja_env
32
+
33
+ if _jinja_env is None:
34
+ template_dir = Path(__file__).parent / "templates"
35
+ fonts_dir = Path(__file__).parent / "fonts"
36
+ # Load only the emoji partial from fonts/ via DictLoader — avoids exposing
37
+ # binaries, license files, and README as includeable Jinja templates.
38
+ emoji_partial = (fonts_dir / "_emoji_font_face.css").read_text()
39
+ _jinja_env = Environment(
40
+ loader=ChoiceLoader(
41
+ [
42
+ FileSystemLoader(str(template_dir)),
43
+ DictLoader({"_emoji_font_face.css": emoji_partial}),
44
+ ]
45
+ ),
46
+ autoescape=select_autoescape(["html", "xml", "svg"]),
47
+ )
48
+
49
+ return _jinja_env
50
+
51
+
52
+ def render_template(template_path: str, **context: Any) -> str:
53
+ """Render a template with the given context.
54
+
55
+ This is the ONLY function needed for template loading. Use it for:
56
+ - Templates with variables: render_template("svg/grid_pattern.svg", **ctx)
57
+ - Static templates: render_template("svg/grid_pattern.svg")
58
+
59
+ Args:
60
+ template_path: Path to template relative to templates/ directory
61
+ (e.g., "svg/grid_pattern.svg")
62
+ **context: Template context variables (optional)
63
+
64
+ Returns:
65
+ Rendered template string
66
+ """
67
+ env = _get_jinja_env()
68
+ template = env.get_template(template_path)
69
+ return template.render(**context)
@@ -0,0 +1,606 @@
1
+ /* ==========================================================================
2
+ Variable Controls - CSS Styles
3
+
4
+ These styles use CSS custom properties (variables) set on the .dft-variables
5
+ container based on the current theme. This enables:
6
+ - Centralized theming (colors set once, inherited everywhere)
7
+ - Hover/focus states (impossible with inline styles)
8
+ - Easier maintenance (styles in one place)
9
+
10
+ CSS Custom Properties (set by renderer based on theme):
11
+ --dft-font-family: Container font family
12
+ --dft-label-color: Label text color
13
+ --dft-label-weight: Label font weight
14
+ --dft-label-size: Label font size in px
15
+ --dft-text-color: Input text color
16
+ --dft-value-color: Value text color (slider, readonly)
17
+ --dft-value-family: Value font family
18
+ --dft-value-size: Value font size in px
19
+ --dft-value-weight: Value font weight
20
+ --dft-value-numeric-variant: Font variant numeric (tabular-nums/normal)
21
+ --dft-placeholder-color: Placeholder/hint text color (unselected variable inputs)
22
+ Note: font-style, text-decoration, and case are cascaded through MergedFontStyle
23
+ but are not consumed by these controls — the control renderer emits only the
24
+ properties listed above (family, size, weight, color). Adding style/decoration/case
25
+ here would require extend the CSS custom-property emit path for control elements.
26
+ --dft-input-bg: Input background color
27
+ --dft-input-border: Input border color
28
+ --dft-accent-color: Accent/focus color
29
+ --dft-muted-color: Muted/secondary text color
30
+ --dft-focus-ring: Focus ring color (CSS color-mix, requires Color Level 5)
31
+ ========================================================================== */
32
+
33
+ /* --------------------------------------------------------------------------
34
+ Variables Container
35
+ -------------------------------------------------------------------------- */
36
+
37
+ .dft-variables {
38
+ display: flex;
39
+ flex-wrap: wrap;
40
+ gap: 15px;
41
+ align-items: center;
42
+ background: #f8f9fa;
43
+ padding: 10px 15px;
44
+ border-radius: 6px;
45
+ font-family: var(--dft-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
46
+ box-sizing: border-box;
47
+ height: 100%;
48
+ /* Allow absolutely-positioned popovers (daterange chip) to overflow the
49
+ SVG clipping rect when this container is embedded inside an SVG element. */
50
+ overflow: visible;
51
+ }
52
+
53
+ /* --------------------------------------------------------------------------
54
+ Individual Control Wrapper
55
+ -------------------------------------------------------------------------- */
56
+
57
+ .dft-variable-control {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 8px;
61
+ }
62
+
63
+ /* --------------------------------------------------------------------------
64
+ Labels
65
+ -------------------------------------------------------------------------- */
66
+
67
+ .dft-variable-label {
68
+ font-weight: var(--dft-label-weight, 500);
69
+ color: var(--dft-label-color, #495057);
70
+ font-size: var(--dft-label-size, inherit);
71
+ white-space: nowrap;
72
+ user-select: none;
73
+ }
74
+
75
+ /* --------------------------------------------------------------------------
76
+ Base Input Styles (applies to all input types)
77
+ -------------------------------------------------------------------------- */
78
+
79
+ .dft-variable-input {
80
+ padding: 4px 8px;
81
+ border: 1px solid var(--dft-input-border, #ced4da);
82
+ border-radius: 4px;
83
+ background: var(--dft-input-bg, #ffffff);
84
+ color: var(--dft-text-color, #333333);
85
+ font-size: inherit;
86
+ font-family: inherit;
87
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
88
+ }
89
+
90
+ .dft-variable-input:hover {
91
+ border-color: var(--dft-accent-color, #667eea);
92
+ }
93
+
94
+ .dft-variable-input:focus {
95
+ outline: none;
96
+ border-color: var(--dft-accent-color, #667eea);
97
+ box-shadow: 0 0 0 3px var(--dft-focus-ring, rgba(102, 126, 234, 0.2));
98
+ }
99
+
100
+ .dft-variable-input:disabled {
101
+ opacity: 0.6;
102
+ cursor: not-allowed;
103
+ }
104
+
105
+ /* Placeholder / unselected state — server-rendered data-placeholder="true" tags
106
+ any input whose value is empty/null. Reads lighter than the selected-value
107
+ text so the strip is scannable at a glance. */
108
+ .dft-variable-input[data-placeholder="true"] {
109
+ color: var(--dft-placeholder-color, var(--dft-text-color));
110
+ }
111
+
112
+ /* --------------------------------------------------------------------------
113
+ Select / Dropdown
114
+ -------------------------------------------------------------------------- */
115
+
116
+ select.dft-variable-input {
117
+ cursor: pointer;
118
+ min-width: 120px;
119
+ padding-right: 24px; /* Space for dropdown arrow */
120
+ appearance: none;
121
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M2 4l4 4 4-4'/%3E%3C/svg%3E");
122
+ background-repeat: no-repeat;
123
+ background-position: right 8px center;
124
+ }
125
+
126
+ /* --------------------------------------------------------------------------
127
+ Checkbox
128
+ -------------------------------------------------------------------------- */
129
+
130
+ .dft-variable-checkbox {
131
+ cursor: pointer;
132
+ }
133
+
134
+ .dft-variable-checkbox input[type="checkbox"] {
135
+ width: 16px;
136
+ height: 16px;
137
+ cursor: pointer;
138
+ accent-color: var(--dft-accent-color, #667eea);
139
+ margin: 0;
140
+ }
141
+
142
+ .dft-variable-checkbox .dft-variable-label {
143
+ cursor: pointer;
144
+ }
145
+
146
+ /* --------------------------------------------------------------------------
147
+ Slider / Range
148
+ -------------------------------------------------------------------------- */
149
+
150
+ .dft-variable-slider {
151
+ gap: 8px;
152
+ }
153
+
154
+ .dft-variable-slider input[type="range"] {
155
+ width: 120px;
156
+ cursor: pointer;
157
+ accent-color: var(--dft-accent-color, #667eea);
158
+ height: 4px;
159
+ border: none;
160
+ background: transparent;
161
+ }
162
+
163
+ .dft-variable-slider-value {
164
+ min-width: 30px;
165
+ color: var(--dft-value-color, #333333);
166
+ font-family: var(--dft-value-family, inherit);
167
+ font-size: var(--dft-value-size, inherit);
168
+ font-weight: var(--dft-value-weight, inherit);
169
+ font-variant-numeric: var(--dft-value-numeric-variant, tabular-nums);
170
+ text-align: right;
171
+ }
172
+
173
+ /* --------------------------------------------------------------------------
174
+ Text Input
175
+ -------------------------------------------------------------------------- */
176
+
177
+ input[type="text"].dft-variable-input {
178
+ width: 150px;
179
+ }
180
+
181
+ /* --------------------------------------------------------------------------
182
+ Number Input
183
+ -------------------------------------------------------------------------- */
184
+
185
+ input[type="number"].dft-variable-input {
186
+ width: 80px;
187
+ font-variant-numeric: var(--dft-value-numeric-variant, tabular-nums);
188
+ }
189
+
190
+ /* Remove spinner buttons for cleaner look */
191
+ input[type="number"].dft-variable-input::-webkit-inner-spin-button,
192
+ input[type="number"].dft-variable-input::-webkit-outer-spin-button {
193
+ -webkit-appearance: none;
194
+ margin: 0;
195
+ }
196
+
197
+ input[type="number"].dft-variable-input {
198
+ -moz-appearance: textfield;
199
+ }
200
+
201
+ /* --------------------------------------------------------------------------
202
+ Date Input
203
+ -------------------------------------------------------------------------- */
204
+
205
+ input[type="date"].dft-variable-input {
206
+ cursor: pointer;
207
+ }
208
+
209
+ /* --------------------------------------------------------------------------
210
+ Date Range — chip trigger + popover
211
+ -------------------------------------------------------------------------- */
212
+
213
+ /* Derived accent values are inlined at each consumer (range tint, focus ring,
214
+ primary-hover) rather than defined as :root-level custom properties.
215
+ Why: when a custom property's value contains a var() reference, the inner
216
+ var() resolves at the DEFINING element, not the consumer. --dft-accent-color
217
+ is set on .dft-variables (inline by the renderer) and snapshotted onto the
218
+ reparented popover — not on :root. So a :root-level
219
+ `--dft-accent-hover: color-mix(in srgb, var(--dft-accent-color, #667eea) 85%, black)`
220
+ computes once at :root where --dft-accent-color is unset, freezing the
221
+ fallback indigo into the value the popover then inherits. Inlining the
222
+ color-mix at the rule that uses it forces the substitution to happen at the
223
+ consumer, where --dft-accent-color carries the theme's accent. */
224
+
225
+ /* Positioning anchor for the popover. */
226
+ .dft-chip-host {
227
+ position: relative;
228
+ display: inline-flex;
229
+ align-items: center;
230
+ gap: 4px;
231
+ }
232
+
233
+ /* Chip trigger button — transparent background blends with the canvas;
234
+ the border carries the "this is a control" signal. */
235
+ .dft-chip {
236
+ display: inline-flex;
237
+ align-items: center;
238
+ gap: 8px;
239
+ /* Padding matches the variables-bar select controls (theme.variables.input.padding)
240
+ so the chip's height aligns with Plan/Country selects sitting next to it.
241
+ The prototype used 6/10, but the chip there stood alone — in DFT the chip
242
+ shares a strip with `select.dft-variable-input` (4/8 padding), and the
243
+ tallness mismatch reads as inconsistency. */
244
+ padding: 4px 8px;
245
+ border: 1px solid var(--dft-input-border, #ced4da);
246
+ border-radius: 4px;
247
+ background: transparent;
248
+ cursor: pointer;
249
+ font-family: inherit;
250
+ /* Inherit from the .dft-variables flex container (11px in theme YAML) so
251
+ the chip text baseline matches the adjacent select.dft-variable-input
252
+ controls. A hardcoded 13px here lifts the chip ~1.8px above its
253
+ neighbours and breaks the title-inline header alignment.
254
+ NOTE: never write a literal angle-bracket tag like the s-e-l-e-c-t one
255
+ inside a comment in this file. The CSS gets injected verbatim into the
256
+ rendered SVG inside a style element, and the SVG XML parser tokenises
257
+ such occurrences as tag openers, breaking PNG conversion. */
258
+ font-size: inherit;
259
+ font-variant-numeric: tabular-nums;
260
+ color: var(--dft-text-color, #333333);
261
+ /* Fade border-color and focus ring together to avoid asymmetric snap. */
262
+ transition: border-color 80ms, box-shadow 80ms;
263
+ }
264
+
265
+ /* Border-only hover — no background tint (quieter across the variable bar). */
266
+ .dft-chip:hover {
267
+ border-color: var(--dft-muted-color, #6c757d);
268
+ }
269
+
270
+ .dft-chip:focus {
271
+ outline: none;
272
+ border-color: var(--dft-accent-color, #667eea);
273
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--dft-accent-color, #667eea) 18%, transparent);
274
+ }
275
+
276
+ /* Chip icon — always full ink; placeholder dims text only, not the icon. */
277
+ .dft-chip-icon {
278
+ display: inline-flex;
279
+ align-items: center;
280
+ flex-shrink: 0;
281
+ width: 16px;
282
+ height: 16px;
283
+ color: var(--dft-text-color, #333333);
284
+ }
285
+
286
+ .dft-chip-icon svg {
287
+ width: 100%;
288
+ height: 100%;
289
+ }
290
+
291
+ .dft-chip-label {
292
+ font-weight: 500;
293
+ white-space: nowrap;
294
+ }
295
+
296
+ /* Placeholder state — lighter text, normal weight. */
297
+ .dft-chip[data-placeholder="true"] .dft-chip-label {
298
+ color: var(--dft-placeholder-color, var(--dft-muted-color, #6c757d));
299
+ font-weight: 400;
300
+ }
301
+
302
+ /* Clear button — sibling of .dft-chip inside .dft-chip-host.
303
+ Hidden until the chip carries data-active; shown via adjacent-sibling selector. */
304
+ .dft-chip-clear {
305
+ display: none;
306
+ width: 16px;
307
+ height: 16px;
308
+ line-height: 14px;
309
+ text-align: center;
310
+ border-radius: 50%;
311
+ color: var(--dft-muted-color, #6c757d);
312
+ font-size: 14px;
313
+ cursor: pointer;
314
+ background: none;
315
+ border: none;
316
+ padding: 0;
317
+ }
318
+
319
+ .dft-chip-clear:hover {
320
+ background: var(--dft-input-bg, #ffffff);
321
+ color: var(--dft-text-color, #333333);
322
+ }
323
+
324
+ /* Show clear button when the sibling chip carries data-active. */
325
+ .dft-chip[data-active] + .dft-chip-clear {
326
+ display: inline-block;
327
+ }
328
+
329
+ /* Popover — hidden by default; .dft-popover-open shows it.
330
+ The calendar is chrome (same category as a native select dropdown), not
331
+ editorial content — hardcode Inter/sans so it stays consistent across themes
332
+ regardless of the page's heading typeface, and survives being reparented to
333
+ document.body for SVG-stacking escape (where it'd otherwise inherit the
334
+ body's serif).
335
+
336
+ Background uses a dedicated --dft-popover-bg token rather than --dft-input-bg:
337
+ the variables-bar input background is intentionally `transparent` in the
338
+ `stark` cascade root so it inherits the page canvas. A popover needs an
339
+ OPAQUE card surface — falling back to #ffffff matches the prototype's --card.
340
+
341
+ NOTE: avoid angle brackets in any comment inside this stylesheet. resvg's
342
+ CSS parser used by the PNG and PDF converters mis-reads them as SVG tag
343
+ opens (since this whole CSS is embedded in an SVG style element), then
344
+ errors at the closing style tag — silently breaking every dashboard
345
+ download. The first version of this comment shipped with "select" between
346
+ angle brackets and broke the test_dashboard_download_supported_format
347
+ tests. */
348
+ .dft-popover {
349
+ display: none;
350
+ position: absolute;
351
+ top: calc(100% + 6px);
352
+ left: 0;
353
+ background: var(--dft-popover-bg, #ffffff);
354
+ border: 1px solid var(--dft-input-border, #ced4da);
355
+ border-radius: 8px;
356
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.10);
357
+ overflow: hidden;
358
+ z-index: 100;
359
+ font-family: 'Inter Variable', Inter, system-ui, -apple-system, sans-serif;
360
+ }
361
+
362
+ .dft-popover.dft-popover-open {
363
+ display: flex;
364
+ }
365
+
366
+ /* Preset rail — left column of the popover. Background tracks the theme's
367
+ subtle-surface step (--dft-popover-rail-bg, emitted by the renderer per
368
+ theme): gray-50 on white-canvas themes, cream-toned on warm-canvas themes.
369
+ Distinct from the popover card itself, which is always white-equivalent. */
370
+ .dft-preset-rail {
371
+ width: 164px;
372
+ border-right: 1px solid var(--dft-input-border, #ced4da);
373
+ padding: 10px 0;
374
+ flex-shrink: 0;
375
+ background: var(--dft-popover-rail-bg, #fafafa);
376
+ }
377
+
378
+ .dft-preset-rail button {
379
+ display: block;
380
+ width: 100%;
381
+ padding: 7px 16px;
382
+ text-align: left;
383
+ background: none;
384
+ border: none;
385
+ cursor: pointer;
386
+ font-family: inherit;
387
+ font-size: 13px;
388
+ color: var(--dft-text-color, #333333);
389
+ }
390
+
391
+ .dft-preset-rail button:hover {
392
+ background: var(--dft-input-border, #ced4da);
393
+ }
394
+
395
+ .dft-preset-rail button.dft-preset-active {
396
+ background: var(--dft-accent-color, #667eea);
397
+ color: white;
398
+ }
399
+
400
+ .dft-preset-divider {
401
+ height: 1px;
402
+ background: var(--dft-input-border, #ced4da);
403
+ margin: 6px 12px;
404
+ }
405
+
406
+ /* Calendar area — right column, contains header, grid, footer.
407
+ Explicit width (not flex: 1) so the popover sizes correctly when reparented
408
+ to document.body for SVG-stacking escape — `position: fixed` parents have
409
+ no width constraint, and `flex: 1` would stretch to viewport width. */
410
+ .dft-calendar-area {
411
+ padding: 14px;
412
+ flex-shrink: 0;
413
+ width: 272px;
414
+ }
415
+
416
+ /* All-sans calendar header (the calendar is UI chrome, not editorial content). */
417
+ .dft-cal-header {
418
+ display: flex;
419
+ align-items: center;
420
+ justify-content: space-between;
421
+ margin-bottom: 10px;
422
+ padding: 0 4px;
423
+ }
424
+
425
+ .dft-cal-nav {
426
+ background: none;
427
+ border: none;
428
+ cursor: pointer;
429
+ font-size: 18px;
430
+ color: var(--dft-text-color, #333333);
431
+ padding: 2px 8px;
432
+ border-radius: 3px;
433
+ }
434
+
435
+ .dft-cal-nav:hover {
436
+ background: var(--dft-input-border, #ced4da);
437
+ }
438
+
439
+ /* min-width keeps nav arrows stable as month name length varies
440
+ ("May 2026" → "September 2026"). */
441
+ .dft-cal-month {
442
+ font-weight: 500;
443
+ font-size: 14px;
444
+ min-width: 130px;
445
+ text-align: center;
446
+ color: var(--dft-text-color, #333333);
447
+ }
448
+
449
+ /* Fixed-width 7-column grid — 34px per cell. Fixed height eliminates
450
+ height jumps as the user pages between months (fixedHeight = 42 cells). */
451
+ .dft-cal-grid {
452
+ display: grid;
453
+ grid-template-columns: repeat(7, 34px);
454
+ gap: 0;
455
+ }
456
+
457
+ .dft-cal-dow {
458
+ font-size: 11px;
459
+ color: var(--dft-muted-color, #6c757d);
460
+ text-align: center;
461
+ padding: 6px 0 4px;
462
+ font-weight: 500;
463
+ }
464
+
465
+ /* Cells — proportional figures (grid is fixed-width, no jitter to prevent).
466
+ Two visual tiers: in-month (full ink) and out-of-month (muted). */
467
+ .dft-cal-cell {
468
+ text-align: center;
469
+ padding: 7px 0;
470
+ font-family: inherit;
471
+ font-size: 13px;
472
+ cursor: pointer;
473
+ border-radius: 3px;
474
+ background: none;
475
+ border: none;
476
+ color: var(--dft-text-color, #333333);
477
+ min-height: 18px;
478
+ }
479
+
480
+ .dft-cal-cell.dft-other-month {
481
+ color: var(--dft-placeholder-color, var(--dft-muted-color, #6c757d));
482
+ }
483
+
484
+ .dft-cal-cell:hover {
485
+ background: var(--dft-input-border, #ced4da);
486
+ }
487
+
488
+ /* Range tint — accent at 28% over the popover card. */
489
+ .dft-cal-cell.dft-in-range,
490
+ .dft-cal-cell.dft-preview-in-range {
491
+ background: color-mix(in srgb, var(--dft-accent-color, #667eea) 28%, transparent);
492
+ color: var(--dft-text-color, #333333);
493
+ }
494
+
495
+ /* Selected endpoints — solid accent fill. */
496
+ .dft-cal-cell.dft-selected {
497
+ background: var(--dft-accent-color, #667eea);
498
+ color: white;
499
+ font-weight: 500;
500
+ }
501
+
502
+ /* Hover preview of the second endpoint — outlined, not filled. */
503
+ .dft-cal-cell.dft-preview-end {
504
+ background: transparent;
505
+ color: var(--dft-accent-color, #667eea);
506
+ font-weight: 500;
507
+ box-shadow: inset 0 0 0 1px var(--dft-accent-color, #667eea);
508
+ }
509
+
510
+ /* Radius shaping for range endpoints. */
511
+ .dft-cal-cell.dft-selected.dft-range-start { border-radius: 3px 0 0 3px; }
512
+ .dft-cal-cell.dft-selected.dft-range-end { border-radius: 0 3px 3px 0; }
513
+ .dft-cal-cell.dft-selected.dft-range-start.dft-range-end { border-radius: 3px; }
514
+
515
+ /* Today marker: weight-only (same ink, heavier weight).
516
+ Color or ring conflicted with selection visuals. Bold weight is the
517
+ cleanest "this is today" signal — same hue, different emphasis. */
518
+ .dft-cal-cell.dft-today:not(.dft-selected):not(.dft-preview-end) {
519
+ font-weight: 700;
520
+ }
521
+
522
+ /* Footer: only shows when a selection exists; contains the Clear action. */
523
+ .dft-cal-footer {
524
+ display: flex;
525
+ gap: 8px;
526
+ margin-top: 14px;
527
+ padding-top: 12px;
528
+ border-top: 1px solid var(--dft-input-border, #ced4da);
529
+ align-items: center;
530
+ justify-content: flex-end;
531
+ }
532
+
533
+ .dft-cal-action {
534
+ padding: 6px 12px;
535
+ background: none;
536
+ border: 1px solid var(--dft-input-border, #ced4da);
537
+ border-radius: 4px;
538
+ font-family: inherit;
539
+ font-size: 12px;
540
+ cursor: pointer;
541
+ color: var(--dft-text-color, #333333);
542
+ }
543
+
544
+ .dft-cal-action:hover {
545
+ border-color: var(--dft-muted-color, #6c757d);
546
+ }
547
+
548
+ /* Apply — the explicit-close primary action (lingering popover pattern:
549
+ commit happens automatically on the second pick, popover stays open
550
+ until Apply, Apply closes without mutating state). Accent fill, white
551
+ ink. Hover darkens the accent by mixing 15% black. */
552
+ .dft-cal-action-primary {
553
+ background: var(--dft-accent-color, #667eea);
554
+ color: white;
555
+ border-color: var(--dft-accent-color, #667eea);
556
+ }
557
+
558
+ .dft-cal-action-primary:hover {
559
+ background: color-mix(in srgb, var(--dft-accent-color, #667eea) 85%, black);
560
+ border-color: color-mix(in srgb, var(--dft-accent-color, #667eea) 85%, black);
561
+ }
562
+
563
+ /* (Clear-pushed-left positioning is set inline in variables.js — see the
564
+ comment in rebuildCalendar's footer block. Attribute selectors and the
565
+ :not() pseudo both trip resvg's CSS parser used in the PNG/PDF converter,
566
+ so this stylesheet sticks to selectors resvg handles cleanly.) */
567
+
568
+ /* --------------------------------------------------------------------------
569
+ Readonly / Fallback
570
+ -------------------------------------------------------------------------- */
571
+
572
+ .dft-variable-readonly {
573
+ display: flex;
574
+ align-items: center;
575
+ gap: 8px;
576
+ }
577
+
578
+ .dft-variable-readonly-value {
579
+ color: var(--dft-value-color, #333333);
580
+ font-family: var(--dft-value-family, inherit);
581
+ font-size: var(--dft-value-size, inherit);
582
+ font-weight: var(--dft-value-weight, inherit);
583
+ }
584
+
585
+ /* --------------------------------------------------------------------------
586
+ Loading State
587
+ -------------------------------------------------------------------------- */
588
+
589
+ .dft-variable-control.loading {
590
+ opacity: 0.6;
591
+ pointer-events: none;
592
+ }
593
+
594
+ .dft-variable-control.loading::after {
595
+ content: "";
596
+ width: 14px;
597
+ height: 14px;
598
+ border: 2px solid var(--dft-input-border, #ced4da);
599
+ border-top-color: var(--dft-accent-color, #667eea);
600
+ border-radius: 50%;
601
+ animation: dft-control-spin 0.6s linear infinite;
602
+ }
603
+
604
+ @keyframes dft-control-spin {
605
+ to { transform: rotate(360deg); }
606
+ }
@@ -0,0 +1,16 @@
1
+ {# Checkbox Control #}
2
+ <div class="dft-variable-control dft-variable dft-variable-checkbox"
3
+ data-variable-id="{{ name }}"
4
+ data-variable-label="{{ label }}"
5
+ {% if depends_on %}data-depends-on="{{ depends_on | tojson }}"{% endif %}>
6
+
7
+ <input type="checkbox"
8
+ id="var-{{ name }}"
9
+ name="{{ name }}"
10
+ class="dft-variable-input"
11
+ data-variable="{{ name }}"
12
+ onchange="updateVariable('{{ name | js_escape }}', this.checked)"
13
+ {% if value %}checked{% endif %}>
14
+
15
+ <label class="dft-variable-label" for="var-{{ name }}">{{ label }}</label>
16
+ </div>
@@ -0,0 +1,16 @@
1
+ {# Date Input Control #}
2
+ <div class="dft-variable-control dft-variable"
3
+ data-variable-id="{{ name }}"
4
+ data-variable-label="{{ label }}"
5
+ {% if depends_on %}data-depends-on="{{ depends_on | tojson }}"{% endif %}>
6
+
7
+ <label class="dft-variable-label" for="var-{{ name }}">{{ label }}:</label>
8
+
9
+ <input type="date"
10
+ id="var-{{ name }}"
11
+ name="{{ name }}"
12
+ class="dft-variable-input"
13
+ data-variable="{{ name }}"
14
+ value="{{ value or '' }}"
15
+ onchange="updateVariable('{{ name | js_escape }}', this.value)">
16
+ </div>