@flanksource/clicky-ui
Version:
Flanksource Clicky UI — React component library built on shadcn/ui with light/dark and density theming.
251 lines (250 loc) • 8.51 kB
JavaScript
import { useState, useEffect } from "react";
import { isPositionalParam } from "./types.js";
function titleCase(value) {
return value.split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
}
function defaultValueForParameter(param, method) {
var _a;
if (method.toUpperCase() === "GET" && param.in === "query" && !param.required) {
return "";
}
const fallback = param.in === "path" ? "" : void 0;
const value = ((_a = param.schema) == null ? void 0 : _a.default) ?? fallback;
if (typeof value === "boolean") {
return value ? "true" : "false";
}
if (Array.isArray(value) || value != null && typeof value === "object") {
return "";
}
if (value == null) {
return "";
}
return String(value);
}
function buildInitialParameterValues(parameters, method, lockedValues = {}, initialValues = {}) {
const values = Object.fromEntries(
parameters.map((param) => [param.name, defaultValueForParameter(param, method)])
);
return { ...values, ...initialValues, ...lockedValues };
}
function pruneParameterValues(values) {
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== ""));
}
function packParameterValues(values, parameters) {
const positionalNames = new Set(
parameters.filter((param) => param.in !== "path" && isPositionalParam(param)).map((p) => p.name)
);
const params = {};
const args = [];
for (const [key, value] of Object.entries(values)) {
if (!value) continue;
if (positionalNames.has(key)) {
args.push(value);
} else {
params[key] = value;
}
}
if (args.length > 0) {
params.args = args.join(",");
}
return params;
}
function parametersToFormConfig(parameters, values, setValues, options = {}) {
var _a, _b;
const emitFilters = [];
const lookupFilters = ((_a = options.lookup) == null ? void 0 : _a.filters) ?? {};
const includeLocations = new Set(options.includeLocations ?? ["path", "query", "header"]);
const lockedValues = options.lockedValues ?? {};
const hideLocked = options.hideLocked ?? false;
const rangeStart = parameters.find(
(param) => {
var _a2;
return includeLocations.has(param.in) && param.in === "query" && ((_a2 = lookupFilters[param.name]) == null ? void 0 : _a2.type) === "from";
}
);
const rangeEnd = parameters.find(
(param) => {
var _a2;
return includeLocations.has(param.in) && param.in === "query" && ((_a2 = lookupFilters[param.name]) == null ? void 0 : _a2.type) === "to";
}
);
const hasTimeRange = rangeStart != null && rangeEnd != null;
for (const param of parameters) {
if (!includeLocations.has(param.in)) continue;
if (hasTimeRange && (param.name === (rangeStart == null ? void 0 : rangeStart.name) || param.name === (rangeEnd == null ? void 0 : rangeEnd.name))) {
continue;
}
const disabled = Object.prototype.hasOwnProperty.call(lockedValues, param.name);
if (disabled && hideLocked) continue;
const value = disabled ? lockedValues[param.name] ?? "" : values[param.name] ?? "";
const label = ((_b = lookupFilters[param.name]) == null ? void 0 : _b.label) ?? titleCase(param.name);
const onChange = (next) => {
if (disabled) return;
const stringValue = typeof next === "boolean" ? next ? "true" : "false" : next;
setValues((current) => ({ ...current, [param.name]: stringValue }));
};
const schema = param.schema;
const lookupFilter = lookupFilters[param.name];
if ((lookupFilter == null ? void 0 : lookupFilter.type) === "multi-filter" && param.in === "query") {
emitFilters.push({
key: param.name,
kind: "multi",
label,
value: parseMultiFilterValue(value),
disabled,
options: lookupOptionsToFieldOptions(lookupFilter),
onChange: (next) => setValues((current) => ({
...current,
[param.name]: serializeMultiFilterValue(next)
}))
});
continue;
}
if (schema == null ? void 0 : schema.enum) {
emitFilters.push({
key: param.name,
kind: "enum",
label,
value,
disabled,
options: schema.enum.map((item) => ({
value: String(item),
label: String(item)
})),
onChange: (next) => onChange(next)
});
continue;
}
if ((lookupFilter == null ? void 0 : lookupFilter.type) === "bool" || (schema == null ? void 0 : schema.type) === "boolean") {
emitFilters.push({
key: param.name,
kind: "boolean",
label,
value: value === "true",
disabled,
onChange: (next) => onChange(next)
});
continue;
}
if (lookupFilter != null && param.in === "query") {
if (lookupFilter.multi) {
emitFilters.push({
key: param.name,
kind: "lookup-multi",
label,
value: splitCommaValues(value),
disabled,
options: lookupOptionsToFieldOptions(lookupFilter),
placeholder: param.description ?? "value-1, value-2",
onChange: (next) => setValues((current) => ({ ...current, [param.name]: next.join(",") }))
});
continue;
}
emitFilters.push({
key: param.name,
kind: "lookup",
label,
value,
disabled,
options: lookupOptionsToFieldOptions(lookupFilter),
placeholder: param.description ?? label,
inputType: lookupFilter.type === "number" ? "number" : lookupFilter.type === "date" ? "date" : "text",
onChange: (next) => onChange(next)
});
continue;
}
emitFilters.push({
key: param.name,
kind: "text",
label,
value,
disabled,
placeholder: param.description ?? label,
onChange: (next) => onChange(next)
});
}
const config = { filters: emitFilters };
if (hasTimeRange && rangeStart != null && rangeEnd != null) {
config.timeRange = {
from: values[rangeStart.name] ?? "",
to: values[rangeEnd.name] ?? "",
onApply: (from, to) => setValues((current) => ({
...current,
[rangeStart.name]: from,
[rangeEnd.name]: to
})),
...rangeStart.description ? { fromPlaceholder: rangeStart.description } : {},
...rangeEnd.description ? { toPlaceholder: rangeEnd.description } : {}
};
}
return config;
}
function useDebouncedRecord(value, delayMs) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timeoutId = window.setTimeout(() => setDebounced(value), delayMs);
return () => window.clearTimeout(timeoutId);
}, [delayMs, value]);
return debounced;
}
function parseMultiFilterValue(value) {
const parsed = {};
for (const item of splitCommaValues(value)) {
if (item.startsWith("!") && item.length > 1) {
parsed[item.slice(1)] = "exclude";
} else {
parsed[item] = "include";
}
}
return parsed;
}
function serializeMultiFilterValue(value) {
return Object.entries(value).flatMap(([key, mode]) => {
if (mode === "include") return [key];
if (mode === "exclude") return [`!${key}`];
return [];
}).join(",");
}
function splitCommaValues(value) {
return value.split(",").map((item) => item.trim()).filter(Boolean);
}
function lookupOptionsToFieldOptions(filter) {
const merged = /* @__PURE__ */ new Map();
for (const [value, node] of Object.entries(filter.options ?? {})) {
merged.set(value, {
label: clickyNodeToPlainText(node) || value,
title: clickyNodeToPlainText(node) || value
});
}
for (const [value, node] of Object.entries(filter.selected ?? {})) {
if (!merged.has(value)) {
merged.set(value, {
label: clickyNodeToPlainText(node) || value,
title: clickyNodeToPlainText(node) || value
});
}
}
return Array.from(merged.entries()).map(([value, meta]) => ({
value,
label: meta.label ?? value,
title: meta.title ?? value
}));
}
function clickyNodeToPlainText(node) {
if (node == null) return "";
if (node.plain) return node.plain;
if (node.text) return node.text;
return (node.children ?? []).map((child) => clickyNodeToPlainText(child)).join("");
}
export {
buildInitialParameterValues,
defaultValueForParameter,
packParameterValues,
parametersToFormConfig,
parseMultiFilterValue,
pruneParameterValues,
serializeMultiFilterValue,
titleCase,
useDebouncedRecord
};
//# sourceMappingURL=formMetadata.js.map