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.
- d3_format/__init__.py +14 -0
- d3_format/errors.py +19 -0
- d3_format/format.py +551 -0
- d3_format/spec.py +159 -0
- dataface/DATAFACE_SYNTAX.md +1135 -0
- dataface/__init__.py +93 -0
- dataface/_docs_site.py +20 -0
- dataface/_install_hint.py +26 -0
- dataface/agent_api/__init__.py +79 -0
- dataface/agent_api/_init_templates/__init__.py +0 -0
- dataface/agent_api/_init_templates/agents_dft_snippet.md +26 -0
- dataface/agent_api/_init_templates/dataface.yml +15 -0
- dataface/agent_api/_init_templates/faces-dataface.yml +144 -0
- dataface/agent_api/_init_templates/index.md +24 -0
- dataface/agent_api/_paths.py +118 -0
- dataface/agent_api/_project_agents_md.py +43 -0
- dataface/agent_api/_session_store.py +486 -0
- dataface/agent_api/_state.py +28 -0
- dataface/agent_api/chat.py +221 -0
- dataface/agent_api/dashboards.py +257 -0
- dataface/agent_api/describe.py +366 -0
- dataface/agent_api/describe_query.py +120 -0
- dataface/agent_api/docs/__init__.py +25 -0
- dataface/agent_api/docs/_loader.py +292 -0
- dataface/agent_api/docs/yaml-reference.md +2757 -0
- dataface/agent_api/file_refs.py +118 -0
- dataface/agent_api/init.py +126 -0
- dataface/agent_api/inspect.py +128 -0
- dataface/agent_api/mcp_install.py +170 -0
- dataface/agent_api/query.py +274 -0
- dataface/agent_api/schema.py +658 -0
- dataface/agent_api/schema_search.py +284 -0
- dataface/agent_api/search.py +270 -0
- dataface/agent_api/skill_install.py +141 -0
- dataface/agent_api/skill_render.py +90 -0
- dataface/agent_api/skills.py +293 -0
- dataface/agent_api/surface_aliases.yaml +128 -0
- dataface/agent_api/validate.py +175 -0
- dataface/agent_api/validate_query.py +84 -0
- dataface/ai/__init__.py +39 -0
- dataface/ai/agent.py +139 -0
- dataface/ai/context.py +45 -0
- dataface/ai/events.py +62 -0
- dataface/ai/external_mcp.py +610 -0
- dataface/ai/generate_sql.py +96 -0
- dataface/ai/llm.py +403 -0
- dataface/ai/mcp/__init__.py +51 -0
- dataface/ai/mcp/server.py +289 -0
- dataface/ai/memories.py +85 -0
- dataface/ai/prompts.py +177 -0
- dataface/ai/schema_context.py +138 -0
- dataface/ai/skills/before-after-comparison/SKILL.md +102 -0
- dataface/ai/skills/before-after-comparison/examples/before-after-comparison.yml +24 -0
- dataface/ai/skills/dashboard-build/SKILL.md +212 -0
- dataface/ai/skills/dashboard-build/examples/_smoke.yml +15 -0
- dataface/ai/skills/dashboard-design/SKILL.md +182 -0
- dataface/ai/skills/dashboard-review/SKILL.md +113 -0
- dataface/ai/skills/dashboard-structural-review/SKILL.md +173 -0
- dataface/ai/skills/dashboard-visual-review/SKILL.md +139 -0
- dataface/ai/skills/dataface-mcp-setup/SKILL.md +177 -0
- dataface/ai/skills/dataface-troubleshooting/SKILL.md +225 -0
- dataface/ai/skills/drill-down-link/SKILL.md +112 -0
- dataface/ai/skills/drill-down-link/examples/drill-down-link.yml +27 -0
- dataface/ai/skills/faceted-small-multiples/SKILL.md +116 -0
- dataface/ai/skills/faceted-small-multiples/examples/faceted-small-multiples.yml +33 -0
- dataface/ai/skills/filter-bar-with-variables/SKILL.md +105 -0
- dataface/ai/skills/filter-bar-with-variables/examples/filter-bar-with-variables.yml +49 -0
- dataface/ai/skills/kpi-row/SKILL.md +101 -0
- dataface/ai/skills/kpi-row/examples/kpi-row.yml +55 -0
- dataface/ai/skills/report-design/SKILL.md +184 -0
- dataface/ai/skills/single-metric-bignum/SKILL.md +90 -0
- dataface/ai/skills/single-metric-bignum/examples/single-metric-bignum.yml +27 -0
- dataface/ai/skills/table-heavy-ops-dashboard/SKILL.md +114 -0
- dataface/ai/skills/table-heavy-ops-dashboard/examples/table-heavy-ops-dashboard.yml +48 -0
- dataface/ai/skills/time-series-trend/SKILL.md +93 -0
- dataface/ai/skills/time-series-trend/examples/time-series-trend.yml +26 -0
- dataface/ai/skills/top-n-with-detail/SKILL.md +98 -0
- dataface/ai/skills/top-n-with-detail/examples/top-n-with-detail.yml +45 -0
- dataface/ai/skills/two-by-two-grid-overview/SKILL.md +78 -0
- dataface/ai/skills/two-by-two-grid-overview/examples/two-by-two-grid-overview.yml +64 -0
- dataface/ai/tool_schemas.py +132 -0
- dataface/ai/tools/__init__.py +312 -0
- dataface/ai/yaml_utils.py +57 -0
- dataface/cli/__init__.py +3 -0
- dataface/cli/_console.py +48 -0
- dataface/cli/_error_format.py +83 -0
- dataface/cli/_extras.py +190 -0
- dataface/cli/_json_output.py +8 -0
- dataface/cli/_parsing.py +17 -0
- dataface/cli/_version_info.py +56 -0
- dataface/cli/commands/__init__.py +3 -0
- dataface/cli/commands/_agent_input.py +205 -0
- dataface/cli/commands/_agent_server.py +115 -0
- dataface/cli/commands/chat.py +645 -0
- dataface/cli/commands/describe.py +107 -0
- dataface/cli/commands/docs.py +131 -0
- dataface/cli/commands/extension.py +179 -0
- dataface/cli/commands/init.py +240 -0
- dataface/cli/commands/inspect.py +94 -0
- dataface/cli/commands/mcp_init.py +167 -0
- dataface/cli/commands/query.py +386 -0
- dataface/cli/commands/render.py +291 -0
- dataface/cli/commands/schema.py +411 -0
- dataface/cli/commands/search.py +49 -0
- dataface/cli/commands/serve.py +114 -0
- dataface/cli/commands/skills.py +133 -0
- dataface/cli/commands/skills_init.py +161 -0
- dataface/cli/commands/validate.py +63 -0
- dataface/cli/main.py +1501 -0
- dataface/core/__init__.py +75 -0
- dataface/core/compile/__init__.py +244 -0
- dataface/core/compile/_jinja_helpers.py +78 -0
- dataface/core/compile/channel.py +222 -0
- dataface/core/compile/chart_focus.py +101 -0
- dataface/core/compile/chart_resolved.py +169 -0
- dataface/core/compile/chart_type_detection.py +489 -0
- dataface/core/compile/chart_update.py +261 -0
- dataface/core/compile/colors.py +64 -0
- dataface/core/compile/compiler.py +904 -0
- dataface/core/compile/config.py +823 -0
- dataface/core/compile/custom_chart_types.py +208 -0
- dataface/core/compile/data_table_attachment.py +1287 -0
- dataface/core/compile/detect.py +110 -0
- dataface/core/compile/errors.py +302 -0
- dataface/core/compile/filter_injection.py +319 -0
- dataface/core/compile/introspection.py +527 -0
- dataface/core/compile/jinja.py +511 -0
- dataface/core/compile/labels_env.py +52 -0
- dataface/core/compile/markdown.py +154 -0
- dataface/core/compile/meta.py +388 -0
- dataface/core/compile/models/__init__.py +0 -0
- dataface/core/compile/models/chart/__init__.py +0 -0
- dataface/core/compile/models/chart/authored.py +2137 -0
- dataface/core/compile/models/chart/compiled.py +398 -0
- dataface/core/compile/models/config.py +347 -0
- dataface/core/compile/models/face/__init__.py +0 -0
- dataface/core/compile/models/face/authored.py +659 -0
- dataface/core/compile/models/face/compiled.py +522 -0
- dataface/core/compile/models/factories.py +201 -0
- dataface/core/compile/models/markers.py +40 -0
- dataface/core/compile/models/palette.py +36 -0
- dataface/core/compile/models/primitives.py +415 -0
- dataface/core/compile/models/query/__init__.py +0 -0
- dataface/core/compile/models/query/authored.py +246 -0
- dataface/core/compile/models/query/compiled.py +710 -0
- dataface/core/compile/models/refs.py +137 -0
- dataface/core/compile/models/source.py +611 -0
- dataface/core/compile/models/style/__init__.py +0 -0
- dataface/core/compile/models/style/authored.py +481 -0
- dataface/core/compile/models/style/compiled.py +3399 -0
- dataface/core/compile/models/style/merged.py +1682 -0
- dataface/core/compile/models/theme.py +362 -0
- dataface/core/compile/models/variable/__init__.py +0 -0
- dataface/core/compile/models/variable/authored.py +254 -0
- dataface/core/compile/models/vega_lite/__init__.py +0 -0
- dataface/core/compile/models/vega_lite/config.py +510 -0
- dataface/core/compile/models/vega_lite/contracts.py +171 -0
- dataface/core/compile/normalize_charts.py +494 -0
- dataface/core/compile/normalize_layout.py +1000 -0
- dataface/core/compile/normalize_queries.py +297 -0
- dataface/core/compile/normalize_variables.py +489 -0
- dataface/core/compile/normalizer.py +543 -0
- dataface/core/compile/palette.py +1100 -0
- dataface/core/compile/parameterized.py +658 -0
- dataface/core/compile/parser.py +228 -0
- dataface/core/compile/schema.py +20 -0
- dataface/core/compile/schema_renderers/__init__.py +0 -0
- dataface/core/compile/schema_renderers/json_schema.py +163 -0
- dataface/core/compile/schema_renderers/prompt.py +152 -0
- dataface/core/compile/schema_renderers/vscode_schema.py +301 -0
- dataface/core/compile/sizing.py +2126 -0
- dataface/core/compile/sources.py +518 -0
- dataface/core/compile/sql_authoring_lint.py +56 -0
- dataface/core/compile/style_cascade.py +471 -0
- dataface/core/compile/typography.py +299 -0
- dataface/core/compile/validator.py +301 -0
- dataface/core/compile/variables.py +53 -0
- dataface/core/compile/vega_config.py +98 -0
- dataface/core/compile/vega_lite/__init__.py +6 -0
- dataface/core/compile/vega_lite/validation.py +95 -0
- dataface/core/compile/yaml_error_formatter.py +838 -0
- dataface/core/connections.py +38 -0
- dataface/core/dashboard.py +358 -0
- dataface/core/defaults/default_config.yml +101 -0
- dataface/core/defaults/palettes/categorical/category-10-dark.yml +32 -0
- dataface/core/defaults/palettes/categorical/category-10-light.yml +43 -0
- dataface/core/defaults/palettes/categorical/category-10.yml +31 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-blue.yml +22 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-brown.yml +29 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-green.yml +20 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-orange.yml +21 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-purple.yml +20 -0
- dataface/core/defaults/palettes/categorical/editorial-10-dark.yml +32 -0
- dataface/core/defaults/palettes/categorical/editorial-10.yml +40 -0
- dataface/core/defaults/palettes/categorical/hero-6.yml +17 -0
- dataface/core/defaults/palettes/categorical/single-blue.yml +11 -0
- dataface/core/defaults/palettes/categorical/tableau.yml +20 -0
- dataface/core/defaults/palettes/data/xkcd_colors.json +3803 -0
- dataface/core/defaults/palettes/diverging/blue-red.yml +25 -0
- dataface/core/defaults/palettes/diverging/coolwarm.yml +24 -0
- dataface/core/defaults/palettes/diverging/crimson-green.yml +23 -0
- dataface/core/defaults/palettes/diverging/orange-teal.yml +23 -0
- dataface/core/defaults/palettes/diverging/sunset.yml +24 -0
- dataface/core/defaults/palettes/scaffold/dft-creams.yml +38 -0
- dataface/core/defaults/palettes/scaffold/dft-grays.yml +53 -0
- dataface/core/defaults/palettes/sequential/amber.yml +22 -0
- dataface/core/defaults/palettes/sequential/blue.yml +22 -0
- dataface/core/defaults/palettes/sequential/brown.yml +22 -0
- dataface/core/defaults/palettes/sequential/gray.yml +22 -0
- dataface/core/defaults/palettes/sequential/green.yml +22 -0
- dataface/core/defaults/palettes/sequential/purple.yml +22 -0
- dataface/core/defaults/palettes/sequential/rust.yml +22 -0
- dataface/core/defaults/palettes/sequential/teal.yml +22 -0
- dataface/core/defaults/palettes/tone/negative.yml +32 -0
- dataface/core/defaults/palettes/tone/positive.yml +22 -0
- dataface/core/defaults/palettes/tone/warning.yml +22 -0
- dataface/core/defaults/themes/_base.yaml +786 -0
- dataface/core/defaults/themes/bi.yaml +16 -0
- dataface/core/defaults/themes/carbong100.yaml +41 -0
- dataface/core/defaults/themes/cream.yaml +122 -0
- dataface/core/defaults/themes/dark.yaml +40 -0
- dataface/core/defaults/themes/diagnostics-title-angle-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-baseline-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-baseline.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-center.yaml +8 -0
- dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +25 -0
- dataface/core/defaults/themes/diagnostics-title-left.yaml +8 -0
- dataface/core/defaults/themes/diagnostics-title-offset-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +24 -0
- dataface/core/defaults/themes/editorial.yaml +147 -0
- dataface/core/defaults/themes/light.yaml +30 -0
- dataface/core/defaults/themes/looker.yaml +17 -0
- dataface/core/defaults/themes/stark.yaml +134 -0
- dataface/core/errors/__init__.py +67 -0
- dataface/core/errors/codes_compile.py +56 -0
- dataface/core/errors/codes_execute.py +177 -0
- dataface/core/errors/codes_render.py +106 -0
- dataface/core/errors/codes_unknown.py +15 -0
- dataface/core/errors/hints.py +74 -0
- dataface/core/errors/registry.py +42 -0
- dataface/core/errors/structured.py +92 -0
- dataface/core/execute/__init__.py +91 -0
- dataface/core/execute/adapters/__init__.py +49 -0
- dataface/core/execute/adapters/adapter_registry.py +400 -0
- dataface/core/execute/adapters/base.py +245 -0
- dataface/core/execute/adapters/csv_adapter.py +239 -0
- dataface/core/execute/adapters/dbt_adapter.py +283 -0
- dataface/core/execute/adapters/dbt_adapter_factory.py +212 -0
- dataface/core/execute/adapters/dbt_macro_loader.py +95 -0
- dataface/core/execute/adapters/dbt_utils.py +150 -0
- dataface/core/execute/adapters/http_adapter.py +224 -0
- dataface/core/execute/adapters/metricflow_adapter.py +94 -0
- dataface/core/execute/adapters/schema_resolver_adapter.py +144 -0
- dataface/core/execute/adapters/sql_adapter.py +710 -0
- dataface/core/execute/adapters/values_adapter.py +58 -0
- dataface/core/execute/batch.py +744 -0
- dataface/core/execute/cache_backend.py +135 -0
- dataface/core/execute/cache_keys.py +66 -0
- dataface/core/execute/dbt_jinja.py +21 -0
- dataface/core/execute/dialects/__init__.py +121 -0
- dataface/core/execute/dialects/athena.py +75 -0
- dataface/core/execute/dialects/base.py +302 -0
- dataface/core/execute/dialects/bigquery.py +38 -0
- dataface/core/execute/dialects/databricks.py +68 -0
- dataface/core/execute/dialects/duckdb.py +35 -0
- dataface/core/execute/dialects/mysql.py +68 -0
- dataface/core/execute/dialects/postgres.py +39 -0
- dataface/core/execute/dialects/redshift.py +12 -0
- dataface/core/execute/dialects/snowflake.py +51 -0
- dataface/core/execute/dialects/sqlserver.py +92 -0
- dataface/core/execute/duckdb_cache.py +712 -0
- dataface/core/execute/duckdb_config.py +26 -0
- dataface/core/execute/errors.py +213 -0
- dataface/core/execute/executor.py +1249 -0
- dataface/core/execute/parallel.py +162 -0
- dataface/core/execute/setup_sql.py +58 -0
- dataface/core/execute/source_registry.py +72 -0
- dataface/core/execute/source_resolver.py +255 -0
- dataface/core/execute/sql_guard.py +387 -0
- dataface/core/execute/sql_literals.py +199 -0
- dataface/core/fonts.py +52 -0
- dataface/core/inspect/__init__.py +32 -0
- dataface/core/inspect/cache_factory.py +98 -0
- dataface/core/inspect/db_types.py +162 -0
- dataface/core/inspect/dbt_schema.py +96 -0
- dataface/core/inspect/defaults.yml +37 -0
- dataface/core/inspect/fanout_risk.py +109 -0
- dataface/core/inspect/manifest_utils.py +77 -0
- dataface/core/inspect/partials/categorical.yml +40 -0
- dataface/core/inspect/partials/date.yml +40 -0
- dataface/core/inspect/partials/numeric.yml +55 -0
- dataface/core/inspect/partition_types.py +38 -0
- dataface/core/inspect/query_validator.py +975 -0
- dataface/core/inspect/renderer.py +354 -0
- dataface/core/inspect/resolver.py +808 -0
- dataface/core/inspect/search.py +461 -0
- dataface/core/inspect/sources/__init__.py +32 -0
- dataface/core/inspect/sources/dbt.py +738 -0
- dataface/core/inspect/sources/duckdb_utils.py +66 -0
- dataface/core/inspect/templates/__init__.py +1 -0
- dataface/core/inspect/templates/categorical_column.yml +196 -0
- dataface/core/inspect/templates/charts.yml +109 -0
- dataface/core/inspect/templates/date_column.yml +248 -0
- dataface/core/inspect/templates/model.yml +138 -0
- dataface/core/inspect/templates/numeric_column.yml +261 -0
- dataface/core/inspect/templates/quality.yml +80 -0
- dataface/core/inspect/templates/string_column.yml +263 -0
- dataface/core/project_roots.py +165 -0
- dataface/core/render/__init__.py +87 -0
- dataface/core/render/board_links.py +176 -0
- dataface/core/render/chart/__init__.py +27 -0
- dataface/core/render/chart/arc_attached_table.py +251 -0
- dataface/core/render/chart/artifacts.py +16 -0
- dataface/core/render/chart/callout.py +225 -0
- dataface/core/render/chart/decisions.py +358 -0
- dataface/core/render/chart/geo.py +700 -0
- dataface/core/render/chart/kpi.py +916 -0
- dataface/core/render/chart/labels.py +76 -0
- dataface/core/render/chart/pipeline.py +818 -0
- dataface/core/render/chart/presentation.py +36 -0
- dataface/core/render/chart/profile.py +3438 -0
- dataface/core/render/chart/render_single.py +347 -0
- dataface/core/render/chart/renderers.py +193 -0
- dataface/core/render/chart/rendering.py +565 -0
- dataface/core/render/chart/serialization.py +90 -0
- dataface/core/render/chart/spark.py +496 -0
- dataface/core/render/chart/spark_bar.py +370 -0
- dataface/core/render/chart/spec_builders.py +154 -0
- dataface/core/render/chart/standard_renderer.py +2645 -0
- dataface/core/render/chart/table.py +2957 -0
- dataface/core/render/chart/table_support.py +1452 -0
- dataface/core/render/chart/tick_values.py +66 -0
- dataface/core/render/chart/time_unit_detect.py +809 -0
- dataface/core/render/chart/title_overflow.py +157 -0
- dataface/core/render/chart/type_inference.py +122 -0
- dataface/core/render/chart/validation.py +99 -0
- dataface/core/render/chart/vega_lite.py +125 -0
- dataface/core/render/chart/vega_lite_types.py +268 -0
- dataface/core/render/chart/vl_field_maps.py +346 -0
- dataface/core/render/chart_interactivity.py +24 -0
- dataface/core/render/control_registry.py +287 -0
- dataface/core/render/converters/__init__.py +24 -0
- dataface/core/render/converters/chart.py +276 -0
- dataface/core/render/converters/html.py +98 -0
- dataface/core/render/converters/pdf.py +40 -0
- dataface/core/render/converters/png.py +41 -0
- dataface/core/render/errors.py +144 -0
- dataface/core/render/face_api.py +160 -0
- dataface/core/render/faces.py +1194 -0
- dataface/core/render/font_measurement.py +48 -0
- dataface/core/render/font_support.py +197 -0
- dataface/core/render/fonts/DFTSansTabular-Regular.ttf +0 -0
- dataface/core/render/fonts/DFTSansTabular-Regular.woff2 +0 -0
- dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.ttf +0 -0
- dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.ttf +0 -0
- dataface/core/render/fonts/InterVariable.ttf +0 -0
- dataface/core/render/fonts/InterVariable.woff2 +0 -0
- dataface/core/render/fonts/NOTO_COLOR_EMOJI_LICENSE.txt +93 -0
- dataface/core/render/fonts/NOTO_EMOJI_LICENSE.txt +93 -0
- dataface/core/render/fonts/NotoColorEmoji-Regular.ttf +0 -0
- dataface/core/render/fonts/NotoColorEmoji-Regular.woff2 +0 -0
- dataface/core/render/fonts/NotoEmoji-Regular.ttf +0 -0
- dataface/core/render/fonts/NotoEmoji-Regular.woff2 +0 -0
- dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -0
- dataface/core/render/fonts/SOURCE_SERIF_4_LICENSE.txt +98 -0
- dataface/core/render/fonts/SourceCodePro-Regular.ttf +0 -0
- dataface/core/render/fonts/SourceSerif4-Regular.ttf +0 -0
- dataface/core/render/fonts/_emoji_font_face.css +43 -0
- dataface/core/render/fonts/source-serif-4-variable-latin.woff2 +0 -0
- dataface/core/render/format_utils.py +329 -0
- dataface/core/render/geo_defaults.yml +28 -0
- dataface/core/render/json_format.py +146 -0
- dataface/core/render/layout_sizing.py +865 -0
- dataface/core/render/layouts.py +541 -0
- dataface/core/render/markdown_defaults.yml +16 -0
- dataface/core/render/missing_vars_prompt.py +79 -0
- dataface/core/render/placeholder.py +389 -0
- dataface/core/render/render_result.py +14 -0
- dataface/core/render/renderer.py +467 -0
- dataface/core/render/script_embedding.py +16 -0
- dataface/core/render/svg_utils.py +212 -0
- dataface/core/render/template_loader.py +69 -0
- dataface/core/render/templates/controls/_styles.css +606 -0
- dataface/core/render/templates/controls/checkbox.html +16 -0
- dataface/core/render/templates/controls/date.html +16 -0
- dataface/core/render/templates/controls/number.html +19 -0
- dataface/core/render/templates/controls/readonly.html +9 -0
- dataface/core/render/templates/controls/select.html +21 -0
- dataface/core/render/templates/controls/slider.html +22 -0
- dataface/core/render/templates/controls/text.html +16 -0
- dataface/core/render/templates/scripts/chart_interactivity.js +191 -0
- dataface/core/render/templates/scripts/variables.js +976 -0
- dataface/core/render/templates/svg/grid_pattern.svg +3 -0
- dataface/core/render/templates/svg/styles.css +51 -0
- dataface/core/render/terminal.py +311 -0
- dataface/core/render/terminal_charts.py +563 -0
- dataface/core/render/terminal_defaults.yml +2 -0
- dataface/core/render/terminal_layouts.py +299 -0
- dataface/core/render/terminal_text.py +31 -0
- dataface/core/render/text/__init__.py +1 -0
- dataface/core/render/text/case.py +113 -0
- dataface/core/render/text_format.py +129 -0
- dataface/core/render/utils.py +106 -0
- dataface/core/render/variable_controls.py +946 -0
- dataface/core/render/variable_input_refinement.py +140 -0
- dataface/core/render/warnings/__init__.py +15 -0
- dataface/core/render/warnings/bar_color_1_to_1_with_x.py +80 -0
- dataface/core/render/warnings/base.py +44 -0
- dataface/core/render/warnings/fanout_risk.py +15 -0
- dataface/core/render/warnings/from_query_diagnostic.py +56 -0
- dataface/core/render/warnings/missing_join_predicate.py +13 -0
- dataface/core/render/warnings/query_parse_error.py +14 -0
- dataface/core/render/warnings/query_returned_zero_rows.py +42 -0
- dataface/core/render/warnings/reaggregation.py +14 -0
- dataface/core/render/warnings/registry.py +45 -0
- dataface/core/render/warnings/suppression.py +46 -0
- dataface/core/render/warnings/temporal_single_point.py +63 -0
- dataface/core/render/warnings/unreferenced_chart.py +15 -0
- dataface/core/render/warnings/y_encoding_mostly_null.py +76 -0
- dataface/core/render/yaml_format.py +167 -0
- dataface/core/resolve_face.py +195 -0
- dataface/core/schema/__init__.py +0 -0
- dataface/core/schema/guidance.py +151 -0
- dataface/core/scoped_paths.py +59 -0
- dataface/core/serve/__init__.py +14 -0
- dataface/core/serve/bootstrap.py +39 -0
- dataface/core/serve/embedded.py +57 -0
- dataface/core/serve/port.py +129 -0
- dataface/core/serve/server.py +938 -0
- dataface/core/serve/templates/__init__.py +0 -0
- dataface/core/serve/templates/directory.yml +6 -0
- dataface/core/serve/templates/error.html.j2 +217 -0
- dataface/core/utils.py +121 -0
- dataface/core/validate.py +64 -0
- dataface/integrations/__init__.py +0 -0
- dataface/integrations/highlighting.py +351 -0
- dataface/integrations/markdown.py +537 -0
- dataface/py.typed +0 -0
- dataface-0.1.2.dist-info/METADATA +375 -0
- dataface-0.1.2.dist-info/RECORD +455 -0
- dataface-0.1.2.dist-info/WHEEL +4 -0
- dataface-0.1.2.dist-info/entry_points.txt +2 -0
- dataface-0.1.2.dist-info/licenses/LICENSE +202 -0
- mdsvg/__init__.py +168 -0
- mdsvg/fonts.py +656 -0
- mdsvg/images.py +299 -0
- mdsvg/parser.py +629 -0
- mdsvg/playground.py +284 -0
- mdsvg/py.typed +2 -0
- mdsvg/renderer.py +1623 -0
- mdsvg/style.py +355 -0
- mdsvg/types.py +200 -0
- 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>
|