/* Copyright 2026 Marimo. All rights reserved. */ /* oxlint-disable typescript/no-base-to-string */ import { ExpandedType } from "compassql/build/src/query/expandedtype"; import { PrimitiveType, type Schema } from "compassql/build/src/schema"; import { SHORT_WILDCARD } from "compassql/build/src/wildcard"; import { useAtomValue } from "jotai"; import { FunctionSquareIcon } from "lucide-react"; import React from "react"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Strings } from "@/utils/strings"; import type { EncodingChannel, FieldDefinition } from "../encoding"; import { MULTI_TEMPORAL_FUNCTIONS, QUANTITATIVE_FUNCTIONS, SINGLE_TEMPORAL_FUNCTIONS, } from "../functions/function"; import type { FieldFunction } from "../functions/types"; import { MARKS, type SpecMark } from "../marks"; import { chartSpecAtom, useChartSpecActions } from "../state/reducer"; import { PRIMITIVE_TYPE_ICON } from "./icons"; interface Props { schema: Schema; mark: SpecMark; } const ENCODINGS: EncodingChannel[] = ["x", "y", "row", "column"]; const MARK_ENCODINGS: EncodingChannel[] = ["color", "size", "shape"]; /** * Query form component that allows users to select encodings * for the chart spec. */ export const QueryForm: React.FC = ({ schema, mark }) => { const value = useAtomValue(chartSpecAtom); const actions = useChartSpecActions(); const canConfigureRowOrColumn = value.encoding.x && value.encoding.y; const renderChannel = (channel: EncodingChannel) => { const isRowOrColumn = channel === "row" || channel === "column"; const disabled = isRowOrColumn && !canConfigureRowOrColumn; return ( actions.setEncoding({ [channel]: value })} /> ); }; const markSelect = ( ); return (
Encodings
{ENCODINGS.map(renderChannel)}
Mark
{markSelect}
{MARK_ENCODINGS.map(renderChannel)}
); }; /** * Select dropdown to choose a field */ const FieldSelect = ({ label, schema, fieldDefinition, disabled, onChange, }: { label: string; schema: Schema; disabled: boolean; fieldDefinition: FieldDefinition | undefined; onChange: (def: FieldDefinition | undefined) => void; }) => { const renderValue = () => { if (!fieldDefinition) { return "--"; } if (fieldDefinition.field === "*") { return ( {PRIMITIVE_TYPE_ICON[PrimitiveType.NUMBER]} Count ); } const field = fieldDefinition.field.toString(); const renderLabel = () => { if (fieldDefinition.fn) { return `${fieldDefinition.fn}(${fieldDefinition.field})`; } return field; }; return ( {PRIMITIVE_TYPE_ICON[schema.primitiveType(field)]} {renderLabel()} ); }; const clear = () => { onChange(undefined); }; const field = fieldDefinition?.field.toString() ?? ""; return ( <>
{fieldDefinition && ( )}
); }; const NONE_FN = "__"; /** * Field options. Currently only changes the fields aggregate/time functions. */ const FieldOptions = ({ field, onChange, }: { field: FieldDefinition; onChange: (def: FieldDefinition | undefined) => void; }) => { if (field.field === "*") { return null; } let options: [string, FieldFunction[]][] = []; if (field.type === ExpandedType.QUANTITATIVE) { options = [["", QUANTITATIVE_FUNCTIONS]]; } if (field.type === ExpandedType.TEMPORAL) { options = [ ["Single", SINGLE_TEMPORAL_FUNCTIONS], ["Multi", MULTI_TEMPORAL_FUNCTIONS], ]; } if (options.length > 0) { return ( ); } return null; };