@lattice-php/lattice 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/components/chart.js +49 -2
- package/dist/core/components/chart.js.map +1 -1
- package/dist/core/components/control.d.ts +1 -1
- package/dist/form/components/fields/date-picker-control.js +11 -13
- package/dist/form/components/fields/date-picker-control.js.map +1 -1
- package/dist/form/components/fields/time-input.js +54 -15
- package/dist/form/components/fields/time-input.js.map +1 -1
- package/dist/form/components/fields/time-picker-columns.d.ts +23 -0
- package/dist/form/components/fields/time-picker-columns.js +76 -0
- package/dist/form/components/fields/time-picker-columns.js.map +1 -0
- package/dist/form/components/fields/time-picker.d.ts +17 -0
- package/dist/form/components/fields/time-picker.js +132 -0
- package/dist/form/components/fields/time-picker.js.map +1 -0
- package/dist/format/number.d.ts +2 -0
- package/dist/format/number.js +23 -0
- package/dist/format/number.js.map +1 -0
- package/dist/{table/components/cells → format}/numeric.js +1 -1
- package/dist/format/numeric.js.map +1 -0
- package/dist/format/value.d.ts +6 -0
- package/dist/format/value.js +17 -0
- package/dist/format/value.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/table/components/cells/money-cell.js +11 -12
- package/dist/table/components/cells/money-cell.js.map +1 -1
- package/dist/table/components/cells/number-cell.js +11 -11
- package/dist/table/components/cells/number-cell.js.map +1 -1
- package/dist/table/format.d.ts +2 -0
- package/dist/table/format.js +2 -0
- package/dist/table/format.js.map +1 -1
- package/dist/types/generated.d.ts +18 -0
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/resources/icons/clock.svg +16 -0
- package/dist/table/components/cells/numeric.js.map +0 -1
- /package/dist/{table/components/cells → format}/numeric.d.ts +0 -0
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { useLocale } from "../../i18n/locale.js";
|
|
2
|
+
import { useTimezone } from "../../i18n/timezone.js";
|
|
1
3
|
import { nodeIdentity } from "../test-id.js";
|
|
4
|
+
import { formatValue } from "../../format/value.js";
|
|
2
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
6
|
import { Area, AreaChart, Bar, BarChart, CartesianGrid, Cell, ComposedChart, Legend, Line, LineChart, Pie, PieChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
|
4
7
|
//#region resources/js/core/components/chart.tsx
|
|
@@ -20,6 +23,18 @@ var compactLegendProps = {
|
|
|
20
23
|
}
|
|
21
24
|
};
|
|
22
25
|
var axisTick = { fontSize: 10 };
|
|
26
|
+
var tooltipProps = {
|
|
27
|
+
contentStyle: {
|
|
28
|
+
background: "var(--lt-surface)",
|
|
29
|
+
border: "1px solid var(--lt-border)",
|
|
30
|
+
borderRadius: "var(--lt-radius-sm)",
|
|
31
|
+
boxShadow: "var(--lt-shadow-sm)",
|
|
32
|
+
color: "var(--lt-surface-fg)",
|
|
33
|
+
fontSize: 12
|
|
34
|
+
},
|
|
35
|
+
itemStyle: { color: "var(--lt-surface-fg)" },
|
|
36
|
+
labelStyle: { color: "var(--lt-muted-fg)" }
|
|
37
|
+
};
|
|
23
38
|
var palette = [
|
|
24
39
|
"var(--lt-primary)",
|
|
25
40
|
"var(--lt-success)",
|
|
@@ -70,6 +85,21 @@ function ChartFrame({ children, description, id, title }) {
|
|
|
70
85
|
function CartesianChart({ props }) {
|
|
71
86
|
const series = props.series.filter(isCartesianSeries);
|
|
72
87
|
const RechartsChart = cartesianChartFor(series);
|
|
88
|
+
const { locale } = useLocale();
|
|
89
|
+
const { timezone } = useTimezone();
|
|
90
|
+
const ctx = {
|
|
91
|
+
locale,
|
|
92
|
+
timezone
|
|
93
|
+
};
|
|
94
|
+
const formatCategory = (value) => formatValue(value, props.categoryFormat, ctx);
|
|
95
|
+
const formatValueTick = (value) => formatValue(value, props.valueFormat, ctx);
|
|
96
|
+
const tooltipCursor = series.some((item) => item.type === "bar") ? {
|
|
97
|
+
fill: "var(--lt-muted-fg)",
|
|
98
|
+
fillOpacity: .15
|
|
99
|
+
} : {
|
|
100
|
+
stroke: "var(--lt-muted-fg)",
|
|
101
|
+
strokeOpacity: .4
|
|
102
|
+
};
|
|
73
103
|
return /* @__PURE__ */ jsx(ResponsiveContainer, {
|
|
74
104
|
width: "100%",
|
|
75
105
|
height: props.height,
|
|
@@ -86,15 +116,22 @@ function CartesianChart({ props }) {
|
|
|
86
116
|
dataKey: props.categoryKey,
|
|
87
117
|
stroke: "var(--lt-muted-fg)",
|
|
88
118
|
tick: axisTick,
|
|
119
|
+
tickFormatter: formatCategory,
|
|
89
120
|
tickLine: false
|
|
90
121
|
}),
|
|
91
122
|
props.yAxis && /* @__PURE__ */ jsx(YAxis, {
|
|
92
123
|
stroke: "var(--lt-muted-fg)",
|
|
93
124
|
tick: axisTick,
|
|
125
|
+
tickFormatter: formatValueTick,
|
|
94
126
|
tickLine: false,
|
|
95
127
|
width: 42
|
|
96
128
|
}),
|
|
97
|
-
props.tooltip && /* @__PURE__ */ jsx(Tooltip, {
|
|
129
|
+
props.tooltip && /* @__PURE__ */ jsx(Tooltip, {
|
|
130
|
+
...tooltipProps,
|
|
131
|
+
cursor: tooltipCursor,
|
|
132
|
+
formatter: formatValueTick,
|
|
133
|
+
labelFormatter: formatCategory
|
|
134
|
+
}),
|
|
98
135
|
props.legend && /* @__PURE__ */ jsx(Legend, { ...compactLegendProps }),
|
|
99
136
|
series.map((item, index) => {
|
|
100
137
|
const color = item.color ?? colorAt(index);
|
|
@@ -134,6 +171,12 @@ function CartesianChart({ props }) {
|
|
|
134
171
|
});
|
|
135
172
|
}
|
|
136
173
|
function PieChartView({ props, series }) {
|
|
174
|
+
const { locale } = useLocale();
|
|
175
|
+
const { timezone } = useTimezone();
|
|
176
|
+
const ctx = {
|
|
177
|
+
locale,
|
|
178
|
+
timezone
|
|
179
|
+
};
|
|
137
180
|
return /* @__PURE__ */ jsx(ResponsiveContainer, {
|
|
138
181
|
width: "100%",
|
|
139
182
|
height: props.height,
|
|
@@ -141,7 +184,11 @@ function PieChartView({ props, series }) {
|
|
|
141
184
|
children: /* @__PURE__ */ jsxs(PieChart, {
|
|
142
185
|
margin: chartMargin,
|
|
143
186
|
children: [
|
|
144
|
-
props.tooltip && /* @__PURE__ */ jsx(Tooltip, {
|
|
187
|
+
props.tooltip && /* @__PURE__ */ jsx(Tooltip, {
|
|
188
|
+
...tooltipProps,
|
|
189
|
+
formatter: (value) => formatValue(value, props.valueFormat, ctx),
|
|
190
|
+
labelFormatter: (value) => formatValue(value, props.categoryFormat, ctx)
|
|
191
|
+
}),
|
|
145
192
|
props.legend && /* @__PURE__ */ jsx(Legend, { ...compactLegendProps }),
|
|
146
193
|
/* @__PURE__ */ jsx(Pie, {
|
|
147
194
|
data: props.data,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart.js","names":[],"sources":["../../../resources/js/core/components/chart.tsx"],"sourcesContent":["import {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Cell,\n ComposedChart,\n Legend,\n Line,\n LineChart,\n Pie,\n PieChart,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from \"recharts\";\nimport type { ComponentType, ReactNode } from \"react\";\nimport { nodeIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { PropsOf, RendererComponent } from \"@lattice-php/lattice/core/types\";\n\ntype ChartProps = PropsOf<\"chart\">;\ntype ChartSeries = ChartProps[\"series\"][number];\ntype ChartDatum = ChartProps[\"data\"][number];\ntype CartesianSeries = ChartSeries & { type: \"area\" | \"bar\" | \"line\" };\ntype ChartMargin = { bottom: number; left: number; right: number; top: number };\ntype CartesianChartComponent = ComponentType<{\n children: ReactNode;\n data: ChartProps[\"data\"];\n margin?: ChartMargin;\n}>;\n\nconst chartMargin: ChartMargin = { bottom: 0, left: 0, right: 16, top: 8 };\nconst compactLegendProps = {\n align: \"center\" as const,\n height: 24,\n iconSize: 7,\n verticalAlign: \"top\" as const,\n wrapperStyle: { fontSize: 11, lineHeight: \"14px\", paddingBottom: 6 },\n};\nconst axisTick = { fontSize: 10 };\n\nconst palette = [\n \"var(--lt-primary)\",\n \"var(--lt-success)\",\n \"var(--lt-info)\",\n \"var(--lt-warning)\",\n \"var(--lt-danger)\",\n \"var(--lt-muted-fg)\",\n];\n\nfunction colorAt(index: number): string {\n return palette[index % palette.length] ?? \"var(--lt-primary)\";\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction datumColor(datum: ChartDatum, series: ChartSeries, index: number): string {\n if (isRecord(datum) && typeof datum.color === \"string\") {\n return datum.color;\n }\n\n return series.color ?? colorAt(index);\n}\n\nfunction isCartesianSeries(series: ChartSeries): series is CartesianSeries {\n return series.type === \"area\" || series.type === \"bar\" || series.type === \"line\";\n}\n\nfunction cartesianChartFor(series: CartesianSeries[]): CartesianChartComponent {\n const types = new Set(series.map((item) => item.type));\n const firstSeries = series[0];\n\n if (firstSeries === undefined || types.size !== 1) {\n return ComposedChart;\n }\n\n switch (firstSeries.type) {\n case \"area\":\n return AreaChart;\n case \"bar\":\n return BarChart;\n case \"line\":\n return LineChart;\n }\n}\n\nfunction ChartFrame({\n children,\n description,\n id,\n title,\n}: {\n children: ReactNode;\n description: string | null;\n id?: string;\n title: string | null;\n}) {\n const hasHeader = title !== null || description !== null;\n\n return (\n <div\n className=\"flex flex-col gap-3 rounded-lt border border-lt-border bg-lt-surface p-4 text-lt-surface-fg shadow-lt-sm\"\n data-lattice-component={id}\n >\n {hasHeader && (\n <div className=\"flex min-w-0 flex-col gap-1.5\">\n {title !== null && <div className=\"text-sm font-semibold leading-tight\">{title}</div>}\n {description !== null && (\n <div className=\"text-xs leading-5 text-lt-muted-fg\">{description}</div>\n )}\n </div>\n )}\n {children}\n </div>\n );\n}\n\nfunction CartesianChart({ props }: { props: ChartProps }) {\n const series = props.series.filter(isCartesianSeries);\n const RechartsChart = cartesianChartFor(series);\n\n return (\n <ResponsiveContainer width=\"100%\" height={props.height} debounce={100}>\n <RechartsChart data={props.data} margin={chartMargin}>\n {props.grid && <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--lt-border)\" />}\n {props.xAxis && props.categoryKey !== null && (\n <XAxis\n dataKey={props.categoryKey}\n stroke=\"var(--lt-muted-fg)\"\n tick={axisTick}\n tickLine={false}\n />\n )}\n {props.yAxis && (\n <YAxis stroke=\"var(--lt-muted-fg)\" tick={axisTick} tickLine={false} width={42} />\n )}\n {props.tooltip && <Tooltip />}\n {props.legend && <Legend {...compactLegendProps} />}\n {series.map((item, index) => {\n const color = item.color ?? colorAt(index);\n const key = `${item.type}:${item.dataKey}`;\n\n if (item.type === \"area\") {\n return (\n <Area\n key={key}\n dataKey={item.dataKey}\n fill={color}\n fillOpacity={0.16}\n name={item.name ?? undefined}\n stackId={item.stackId ?? undefined}\n stroke={color}\n type=\"monotone\"\n />\n );\n }\n\n if (item.type === \"bar\") {\n return (\n <Bar\n key={key}\n dataKey={item.dataKey}\n fill={color}\n name={item.name ?? undefined}\n radius={[4, 4, 0, 0]}\n stackId={item.stackId ?? undefined}\n />\n );\n }\n\n return (\n <Line\n key={key}\n dataKey={item.dataKey}\n dot={false}\n name={item.name ?? undefined}\n stroke={color}\n strokeWidth={2}\n type=\"monotone\"\n />\n );\n })}\n </RechartsChart>\n </ResponsiveContainer>\n );\n}\n\nfunction PieChartView({ props, series }: { props: ChartProps; series: ChartSeries }) {\n return (\n <ResponsiveContainer width=\"100%\" height={props.height} debounce={100}>\n <PieChart margin={chartMargin}>\n {props.tooltip && <Tooltip />}\n {props.legend && <Legend {...compactLegendProps} />}\n <Pie\n data={props.data}\n dataKey={series.dataKey}\n name={series.name ?? undefined}\n nameKey={series.nameKey ?? undefined}\n outerRadius=\"68%\"\n >\n {props.data.map((datum, index) => (\n <Cell key={index} fill={datumColor(datum, series, index)} />\n ))}\n </Pie>\n </PieChart>\n </ResponsiveContainer>\n );\n}\n\nconst ChartComponent: RendererComponent<\"chart\"> = ({ node }) => {\n const props = node.props;\n const pieSeries = props.series.find((series) => series.type === \"pie\");\n const hasCartesianSeries = props.series.some(isCartesianSeries);\n\n return (\n <ChartFrame description={props.description} id={nodeIdentity(node)} title={props.title}>\n <div className=\"min-h-0 w-full\">\n {pieSeries && !hasCartesianSeries ? (\n <PieChartView props={props} series={pieSeries} />\n ) : (\n <CartesianChart props={props} />\n )}\n </div>\n </ChartFrame>\n );\n};\n\nexport default ChartComponent;\n"],"mappings":";;;;AAiCA,IAAM,cAA2B;CAAE,QAAQ;CAAG,MAAM;CAAG,OAAO;CAAI,KAAK;AAAE;AACzE,IAAM,qBAAqB;CACzB,OAAO;CACP,QAAQ;CACR,UAAU;CACV,eAAe;CACf,cAAc;EAAE,UAAU;EAAI,YAAY;EAAQ,eAAe;CAAE;AACrE;AACA,IAAM,WAAW,EAAE,UAAU,GAAG;AAEhC,IAAM,UAAU;CACd;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAS,QAAQ,OAAuB;CACtC,OAAO,QAAQ,QAAQ,QAAQ,WAAW;AAC5C;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,WAAW,OAAmB,QAAqB,OAAuB;CACjF,IAAI,SAAS,KAAK,KAAK,OAAO,MAAM,UAAU,UAC5C,OAAO,MAAM;CAGf,OAAO,OAAO,SAAS,QAAQ,KAAK;AACtC;AAEA,SAAS,kBAAkB,QAAgD;CACzE,OAAO,OAAO,SAAS,UAAU,OAAO,SAAS,SAAS,OAAO,SAAS;AAC5E;AAEA,SAAS,kBAAkB,QAAoD;CAC7E,MAAM,QAAQ,IAAI,IAAI,OAAO,KAAK,SAAS,KAAK,IAAI,CAAC;CACrD,MAAM,cAAc,OAAO;CAE3B,IAAI,gBAAgB,KAAA,KAAa,MAAM,SAAS,GAC9C,OAAO;CAGT,QAAQ,YAAY,MAApB;EACE,KAAK,QACH,OAAO;EACT,KAAK,OACH,OAAO;EACT,KAAK,QACH,OAAO;CACX;AACF;AAEA,SAAS,WAAW,EAClB,UACA,aACA,IACA,SAMC;CAGD,OACE,qBAAC,OAAD;EACE,WAAU;EACV,0BAAwB;YAF1B,EAHgB,UAAU,QAAQ,gBAAgB,SAQ9C,qBAAC,OAAD;GAAK,WAAU;aAAf,CACG,UAAU,QAAQ,oBAAC,OAAD;IAAK,WAAU;cAAuC;GAAW,CAAA,GACnF,gBAAgB,QACf,oBAAC,OAAD;IAAK,WAAU;cAAsC;GAAiB,CAAA,CAErE;MAEN,QACE;;AAET;AAEA,SAAS,eAAe,EAAE,SAAgC;CACxD,MAAM,SAAS,MAAM,OAAO,OAAO,iBAAiB;CACpD,MAAM,gBAAgB,kBAAkB,MAAM;CAE9C,OACE,oBAAC,qBAAD;EAAqB,OAAM;EAAO,QAAQ,MAAM;EAAQ,UAAU;YAChE,qBAAC,eAAD;GAAe,MAAM,MAAM;GAAM,QAAQ;aAAzC;IACG,MAAM,QAAQ,oBAAC,eAAD;KAAe,iBAAgB;KAAM,QAAO;IAAoB,CAAA;IAC9E,MAAM,SAAS,MAAM,gBAAgB,QACpC,oBAAC,OAAD;KACE,SAAS,MAAM;KACf,QAAO;KACP,MAAM;KACN,UAAU;IACX,CAAA;IAEF,MAAM,SACL,oBAAC,OAAD;KAAO,QAAO;KAAqB,MAAM;KAAU,UAAU;KAAO,OAAO;IAAK,CAAA;IAEjF,MAAM,WAAW,oBAAC,SAAD,CAAU,CAAA;IAC3B,MAAM,UAAU,oBAAC,QAAD,EAAQ,GAAI,mBAAqB,CAAA;IACjD,OAAO,KAAK,MAAM,UAAU;KAC3B,MAAM,QAAQ,KAAK,SAAS,QAAQ,KAAK;KACzC,MAAM,MAAM,GAAG,KAAK,KAAK,GAAG,KAAK;KAEjC,IAAI,KAAK,SAAS,QAChB,OACE,oBAAC,MAAD;MAEE,SAAS,KAAK;MACd,MAAM;MACN,aAAa;MACb,MAAM,KAAK,QAAQ,KAAA;MACnB,SAAS,KAAK,WAAW,KAAA;MACzB,QAAQ;MACR,MAAK;KACN,GARM,GAQN;KAIL,IAAI,KAAK,SAAS,OAChB,OACE,oBAAC,KAAD;MAEE,SAAS,KAAK;MACd,MAAM;MACN,MAAM,KAAK,QAAQ,KAAA;MACnB,QAAQ;OAAC;OAAG;OAAG;OAAG;MAAC;MACnB,SAAS,KAAK,WAAW,KAAA;KAC1B,GANM,GAMN;KAIL,OACE,oBAAC,MAAD;MAEE,SAAS,KAAK;MACd,KAAK;MACL,MAAM,KAAK,QAAQ,KAAA;MACnB,QAAQ;MACR,aAAa;MACb,MAAK;KACN,GAPM,GAON;IAEL,CAAC;GACY;;CACI,CAAA;AAEzB;AAEA,SAAS,aAAa,EAAE,OAAO,UAAsD;CACnF,OACE,oBAAC,qBAAD;EAAqB,OAAM;EAAO,QAAQ,MAAM;EAAQ,UAAU;YAChE,qBAAC,UAAD;GAAU,QAAQ;aAAlB;IACG,MAAM,WAAW,oBAAC,SAAD,CAAU,CAAA;IAC3B,MAAM,UAAU,oBAAC,QAAD,EAAQ,GAAI,mBAAqB,CAAA;IAClD,oBAAC,KAAD;KACE,MAAM,MAAM;KACZ,SAAS,OAAO;KAChB,MAAM,OAAO,QAAQ,KAAA;KACrB,SAAS,OAAO,WAAW,KAAA;KAC3B,aAAY;eAEX,MAAM,KAAK,KAAK,OAAO,UACtB,oBAAC,MAAD,EAAkB,MAAM,WAAW,OAAO,QAAQ,KAAK,EAAI,GAAhD,KAAgD,CAC5D;IACE,CAAA;GACG;;CACS,CAAA;AAEzB;AAEA,IAAM,kBAA8C,EAAE,WAAW;CAC/D,MAAM,QAAQ,KAAK;CACnB,MAAM,YAAY,MAAM,OAAO,MAAM,WAAW,OAAO,SAAS,KAAK;CACrE,MAAM,qBAAqB,MAAM,OAAO,KAAK,iBAAiB;CAE9D,OACE,oBAAC,YAAD;EAAY,aAAa,MAAM;EAAa,IAAI,aAAa,IAAI;EAAG,OAAO,MAAM;YAC/E,oBAAC,OAAD;GAAK,WAAU;aACZ,aAAa,CAAC,qBACb,oBAAC,cAAD;IAAqB;IAAO,QAAQ;GAAY,CAAA,IAEhD,oBAAC,gBAAD,EAAuB,MAAQ,CAAA;EAE9B,CAAA;CACK,CAAA;AAEhB"}
|
|
1
|
+
{"version":3,"file":"chart.js","names":[],"sources":["../../../resources/js/core/components/chart.tsx"],"sourcesContent":["import {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Cell,\n ComposedChart,\n Legend,\n Line,\n LineChart,\n Pie,\n PieChart,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from \"recharts\";\nimport type { ComponentType, ReactNode } from \"react\";\nimport { nodeIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { PropsOf, RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { useLocale, useTimezone } from \"@lattice-php/lattice/i18n\";\nimport { formatValue } from \"../../format/value\";\n\ntype ChartProps = PropsOf<\"chart\">;\ntype ChartSeries = ChartProps[\"series\"][number];\ntype ChartDatum = ChartProps[\"data\"][number];\ntype CartesianSeries = ChartSeries & { type: \"area\" | \"bar\" | \"line\" };\ntype ChartMargin = { bottom: number; left: number; right: number; top: number };\ntype CartesianChartComponent = ComponentType<{\n children: ReactNode;\n data: ChartProps[\"data\"];\n margin?: ChartMargin;\n}>;\n\nconst chartMargin: ChartMargin = { bottom: 0, left: 0, right: 16, top: 8 };\nconst compactLegendProps = {\n align: \"center\" as const,\n height: 24,\n iconSize: 7,\n verticalAlign: \"top\" as const,\n wrapperStyle: { fontSize: 11, lineHeight: \"14px\", paddingBottom: 6 },\n};\nconst axisTick = { fontSize: 10 };\nconst tooltipProps = {\n contentStyle: {\n background: \"var(--lt-surface)\",\n border: \"1px solid var(--lt-border)\",\n borderRadius: \"var(--lt-radius-sm)\",\n boxShadow: \"var(--lt-shadow-sm)\",\n color: \"var(--lt-surface-fg)\",\n fontSize: 12,\n },\n itemStyle: { color: \"var(--lt-surface-fg)\" },\n labelStyle: { color: \"var(--lt-muted-fg)\" },\n} as const;\n\nconst palette = [\n \"var(--lt-primary)\",\n \"var(--lt-success)\",\n \"var(--lt-info)\",\n \"var(--lt-warning)\",\n \"var(--lt-danger)\",\n \"var(--lt-muted-fg)\",\n];\n\nfunction colorAt(index: number): string {\n return palette[index % palette.length] ?? \"var(--lt-primary)\";\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction datumColor(datum: ChartDatum, series: ChartSeries, index: number): string {\n if (isRecord(datum) && typeof datum.color === \"string\") {\n return datum.color;\n }\n\n return series.color ?? colorAt(index);\n}\n\nfunction isCartesianSeries(series: ChartSeries): series is CartesianSeries {\n return series.type === \"area\" || series.type === \"bar\" || series.type === \"line\";\n}\n\nfunction cartesianChartFor(series: CartesianSeries[]): CartesianChartComponent {\n const types = new Set(series.map((item) => item.type));\n const firstSeries = series[0];\n\n if (firstSeries === undefined || types.size !== 1) {\n return ComposedChart;\n }\n\n switch (firstSeries.type) {\n case \"area\":\n return AreaChart;\n case \"bar\":\n return BarChart;\n case \"line\":\n return LineChart;\n }\n}\n\nfunction ChartFrame({\n children,\n description,\n id,\n title,\n}: {\n children: ReactNode;\n description: string | null;\n id?: string;\n title: string | null;\n}) {\n const hasHeader = title !== null || description !== null;\n\n return (\n <div\n className=\"flex flex-col gap-3 rounded-lt border border-lt-border bg-lt-surface p-4 text-lt-surface-fg shadow-lt-sm\"\n data-lattice-component={id}\n >\n {hasHeader && (\n <div className=\"flex min-w-0 flex-col gap-1.5\">\n {title !== null && <div className=\"text-sm font-semibold leading-tight\">{title}</div>}\n {description !== null && (\n <div className=\"text-xs leading-5 text-lt-muted-fg\">{description}</div>\n )}\n </div>\n )}\n {children}\n </div>\n );\n}\n\nfunction CartesianChart({ props }: { props: ChartProps }) {\n const series = props.series.filter(isCartesianSeries);\n const RechartsChart = cartesianChartFor(series);\n const { locale } = useLocale();\n const { timezone } = useTimezone();\n const ctx = { locale, timezone };\n const formatCategory = (value: unknown) => formatValue(value, props.categoryFormat, ctx);\n const formatValueTick = (value: unknown) => formatValue(value, props.valueFormat, ctx);\n const hasBarSeries = series.some((item) => item.type === \"bar\");\n const tooltipCursor = hasBarSeries\n ? { fill: \"var(--lt-muted-fg)\", fillOpacity: 0.15 }\n : { stroke: \"var(--lt-muted-fg)\", strokeOpacity: 0.4 };\n\n return (\n <ResponsiveContainer width=\"100%\" height={props.height} debounce={100}>\n <RechartsChart data={props.data} margin={chartMargin}>\n {props.grid && <CartesianGrid strokeDasharray=\"3 3\" stroke=\"var(--lt-border)\" />}\n {props.xAxis && props.categoryKey !== null && (\n <XAxis\n dataKey={props.categoryKey}\n stroke=\"var(--lt-muted-fg)\"\n tick={axisTick}\n tickFormatter={formatCategory}\n tickLine={false}\n />\n )}\n {props.yAxis && (\n <YAxis\n stroke=\"var(--lt-muted-fg)\"\n tick={axisTick}\n tickFormatter={formatValueTick}\n tickLine={false}\n width={42}\n />\n )}\n {props.tooltip && (\n <Tooltip\n {...tooltipProps}\n cursor={tooltipCursor}\n formatter={formatValueTick}\n labelFormatter={formatCategory}\n />\n )}\n {props.legend && <Legend {...compactLegendProps} />}\n {series.map((item, index) => {\n const color = item.color ?? colorAt(index);\n const key = `${item.type}:${item.dataKey}`;\n\n if (item.type === \"area\") {\n return (\n <Area\n key={key}\n dataKey={item.dataKey}\n fill={color}\n fillOpacity={0.16}\n name={item.name ?? undefined}\n stackId={item.stackId ?? undefined}\n stroke={color}\n type=\"monotone\"\n />\n );\n }\n\n if (item.type === \"bar\") {\n return (\n <Bar\n key={key}\n dataKey={item.dataKey}\n fill={color}\n name={item.name ?? undefined}\n radius={[4, 4, 0, 0]}\n stackId={item.stackId ?? undefined}\n />\n );\n }\n\n return (\n <Line\n key={key}\n dataKey={item.dataKey}\n dot={false}\n name={item.name ?? undefined}\n stroke={color}\n strokeWidth={2}\n type=\"monotone\"\n />\n );\n })}\n </RechartsChart>\n </ResponsiveContainer>\n );\n}\n\nfunction PieChartView({ props, series }: { props: ChartProps; series: ChartSeries }) {\n const { locale } = useLocale();\n const { timezone } = useTimezone();\n const ctx = { locale, timezone };\n\n return (\n <ResponsiveContainer width=\"100%\" height={props.height} debounce={100}>\n <PieChart margin={chartMargin}>\n {props.tooltip && (\n <Tooltip\n {...tooltipProps}\n formatter={(value) => formatValue(value, props.valueFormat, ctx)}\n labelFormatter={(value) => formatValue(value, props.categoryFormat, ctx)}\n />\n )}\n {props.legend && <Legend {...compactLegendProps} />}\n <Pie\n data={props.data}\n dataKey={series.dataKey}\n name={series.name ?? undefined}\n nameKey={series.nameKey ?? undefined}\n outerRadius=\"68%\"\n >\n {props.data.map((datum, index) => (\n <Cell key={index} fill={datumColor(datum, series, index)} />\n ))}\n </Pie>\n </PieChart>\n </ResponsiveContainer>\n );\n}\n\nconst ChartComponent: RendererComponent<\"chart\"> = ({ node }) => {\n const props = node.props;\n const pieSeries = props.series.find((series) => series.type === \"pie\");\n const hasCartesianSeries = props.series.some(isCartesianSeries);\n\n return (\n <ChartFrame description={props.description} id={nodeIdentity(node)} title={props.title}>\n <div className=\"min-h-0 w-full\">\n {pieSeries && !hasCartesianSeries ? (\n <PieChartView props={props} series={pieSeries} />\n ) : (\n <CartesianChart props={props} />\n )}\n </div>\n </ChartFrame>\n );\n};\n\nexport default ChartComponent;\n"],"mappings":"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"}
|
|
@@ -7,6 +7,6 @@ export declare const FOCUS_RING = "focus-visible:border-lt-ring focus-visible:ri
|
|
|
7
7
|
* border, focus ring, invalid, and disabled treatment are unified.
|
|
8
8
|
*/
|
|
9
9
|
export declare const controlSurface: (props?: ({
|
|
10
|
-
density?: "
|
|
10
|
+
density?: "compact" | "comfortable" | null | undefined;
|
|
11
11
|
} & import('class-variance-authority/types').ClassProp) | undefined) => string;
|
|
12
12
|
export type ControlSurfaceVariants = VariantProps<typeof controlSurface>;
|
|
@@ -3,6 +3,8 @@ import { cn } from "../../../lib/utils.js";
|
|
|
3
3
|
import { Icon } from "../../../icons/sprite.js";
|
|
4
4
|
import { Button } from "../../../core/components/button.js";
|
|
5
5
|
import { Input } from "../base/input.js";
|
|
6
|
+
import { parseTimeString } from "./time-picker-columns.js";
|
|
7
|
+
import { TimePicker } from "./time-picker.js";
|
|
6
8
|
import { formatDateDisplayValue, formatDateTimeDisplayValue, formatDateTimeValue, formatDateValue, formatTimeInputValue, parseDateDisplayValue, parseDateTimeDisplayValue, parseDateTimeValue, parseDateValue } from "./date-picker-value.js";
|
|
7
9
|
import { createElement, useId, useMemo } from "react";
|
|
8
10
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -166,21 +168,17 @@ function DatePickerControl({ mode, label, name, testId, value, min, max, step, d
|
|
|
166
168
|
})))
|
|
167
169
|
})]
|
|
168
170
|
}),
|
|
169
|
-
mode === "date-time" ? /* @__PURE__ */ jsx(
|
|
170
|
-
|
|
171
|
+
mode === "date-time" ? /* @__PURE__ */ jsx(TimePicker, {
|
|
172
|
+
value: parseTimeString(formatTimeInputValue(selected[0], timezone)),
|
|
173
|
+
onChange: (next) => api.setTime({
|
|
174
|
+
hour: next.hour,
|
|
175
|
+
minute: next.minute,
|
|
176
|
+
second: next.second
|
|
177
|
+
}),
|
|
178
|
+
step,
|
|
171
179
|
disabled,
|
|
172
|
-
onChange: (event) => {
|
|
173
|
-
const [hour = "0", minute = "0", second = "0"] = event.target.value.split(":");
|
|
174
|
-
api.setTime({
|
|
175
|
-
hour: Number(hour),
|
|
176
|
-
minute: Number(minute),
|
|
177
|
-
second: Number(second)
|
|
178
|
-
});
|
|
179
|
-
},
|
|
180
180
|
readOnly,
|
|
181
|
-
|
|
182
|
-
type: "time",
|
|
183
|
-
value: formatTimeInputValue(selected[0], timezone)
|
|
181
|
+
testId: `${testId}-time`
|
|
184
182
|
}) : null
|
|
185
183
|
]
|
|
186
184
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"date-picker-control.js","names":[],"sources":["../../../../resources/js/form/components/fields/date-picker-control.tsx"],"sourcesContent":["import type { DateValue } from \"@internationalized/date\";\nimport * as datePicker from \"@zag-js/date-picker\";\nimport { normalizeProps, useMachine } from \"@zag-js/react\";\nimport { useId, useMemo } from \"react\";\nimport { Button } from \"@lattice-php/lattice/core/components/button\";\nimport { Icon } from \"@lattice-php/lattice/icons\";\nimport { useLocale } from \"@lattice-php/lattice/i18n\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { Input } from \"../base/input\";\nimport {\n formatDateDisplayValue,\n formatDateTimeDisplayValue,\n formatDateTimeValue,\n formatDateValue,\n formatTimeInputValue,\n parseDateDisplayValue,\n parseDateTimeDisplayValue,\n parseDateTimeValue,\n parseDateValue,\n} from \"./date-picker-value\";\n\nexport type DatePickerControlProps = {\n mode: \"date\" | \"date-time\";\n label: string;\n name: string;\n testId: string;\n value: unknown;\n min?: string | null;\n max?: string | null;\n step?: number | null;\n disabled: boolean;\n readOnly: boolean;\n autoFocus?: boolean;\n tabIndex?: number | null;\n timezone?: string;\n onChange: (value: string) => void;\n onBlur?: () => void;\n};\n\nexport function DatePickerControl({\n mode,\n label,\n name,\n testId,\n value,\n min,\n max,\n step,\n disabled,\n readOnly,\n autoFocus = false,\n tabIndex,\n timezone = \"UTC\",\n onChange,\n onBlur,\n}: DatePickerControlProps) {\n const id = useId();\n const { locale } = useLocale();\n const selected = useMemo(\n () =>\n [mode === \"date\" ? parseDateValue(value) : parseDateTimeValue(value, timezone)].filter(\n Boolean,\n ) as DateValue[],\n [mode, timezone, value],\n );\n const service = useMachine(datePicker.machine, {\n id,\n name,\n value: selected.length > 0 ? selected : undefined,\n min: min ? parseDateValue(min) : undefined,\n max: max ? parseDateValue(max) : undefined,\n disabled,\n readOnly,\n locale,\n selectionMode: \"single\",\n timeZone: timezone,\n closeOnSelect: mode === \"date\",\n format(date) {\n return mode === \"date\"\n ? formatDateDisplayValue(date, locale)\n : formatDateTimeDisplayValue(date, locale, timezone);\n },\n parse(text) {\n return mode === \"date\"\n ? parseDateDisplayValue(text, locale)\n : parseDateTimeDisplayValue(text, locale, timezone);\n },\n onValueChange(details) {\n const next = details.value[0];\n\n onChange(mode === \"date\" ? formatDateValue(next) : formatDateTimeValue(next, timezone));\n },\n onOpenChange(details) {\n if (!details.open) {\n onBlur?.();\n }\n },\n });\n const api = datePicker.connect(service, normalizeProps);\n const { name: _inputName, onInput, ...inputProps } = api.getInputProps();\n const submittedValue =\n mode === \"date\" ? formatDateValue(selected[0]) : formatDateTimeValue(selected[0], timezone);\n\n return (\n <div {...api.getRootProps()} className={cn(\"relative\", api.open && \"z-lt-popover\")}>\n <input type=\"hidden\" name={name} value={submittedValue} data-test={`${testId}-value`} />\n <div {...api.getControlProps()} className=\"flex gap-2\">\n <Input\n {...inputProps}\n aria-label={label}\n autoFocus={autoFocus}\n data-test={testId}\n disabled={disabled}\n id={name}\n onInput={(event) => {\n onInput?.(event);\n\n if (mode !== \"date\") {\n return;\n }\n\n const normalized = normalizeDateInputValue(event.currentTarget.value);\n\n if (!normalized) {\n return;\n }\n\n const next = parseDateValue(normalized);\n\n if (!next) {\n return;\n }\n\n event.currentTarget.value = normalized;\n api.setValue([next]);\n event.currentTarget.value = formatDateDisplayValue(next, locale);\n onChange(formatDateValue(next));\n }}\n readOnly={readOnly}\n tabIndex={tabIndex ?? undefined}\n />\n <Button\n {...api.getTriggerProps()}\n aria-label={`Open ${label || name} calendar`}\n disabled={disabled || readOnly}\n size=\"icon\"\n type=\"button\"\n variant=\"secondary\"\n >\n <Icon name=\"calendar\" className=\"size-lt-icon-md\" aria-hidden=\"true\" />\n </Button>\n </div>\n {api.open ? (\n <div\n {...api.getPositionerProps()}\n className=\"absolute z-lt-popover mt-2 rounded-lt-sm border border-lt-border bg-lt-popover p-3 text-lt-popover-fg shadow-lt-md\"\n >\n <div {...api.getContentProps()} className=\"grid gap-3\">\n <div className=\"flex items-center justify-between gap-2\">\n <Button {...api.getPrevTriggerProps()} size=\"icon\" type=\"button\" variant=\"ghost\">\n <Icon name=\"chevron-left\" className=\"size-lt-icon-md\" aria-hidden=\"true\" />\n </Button>\n <div {...api.getRangeTextProps()} className=\"text-sm font-medium text-lt-fg\" />\n <Button {...api.getNextTriggerProps()} size=\"icon\" type=\"button\" variant=\"ghost\">\n <Icon name=\"chevron-right\" className=\"size-lt-icon-md\" aria-hidden=\"true\" />\n </Button>\n </div>\n <table {...api.getTableProps()} className=\"w-full border-collapse text-sm\">\n <thead {...api.getTableHeadProps()}>\n <tr {...api.getTableRowProps()}>\n {api.weekDays.map((day) => (\n <th\n {...api.getTableHeaderProps()}\n aria-label={day.long}\n key={day.value.toString()}\n className=\"size-8 text-center text-xs font-medium text-lt-muted-fg\"\n >\n {day.narrow}\n </th>\n ))}\n </tr>\n </thead>\n <tbody {...api.getTableBodyProps()}>\n {api.weeks.map((week, weekIndex) => (\n <tr {...api.getTableRowProps()} key={weekIndex}>\n {week.map((day) => {\n const state = api.getDayTableCellState({ value: day });\n\n return (\n <td\n {...api.getDayTableCellProps({ value: day })}\n key={day.toString()}\n className=\"p-0 text-center\"\n >\n <button\n {...api.getDayTableCellTriggerProps({ value: day })}\n type=\"button\"\n className={cn(\n \"size-8 rounded-lt-sm text-sm text-lt-fg hover:bg-lt-muted\",\n state.selected && \"bg-lt-primary text-lt-primary-fg\",\n state.outsideRange && \"text-lt-muted-fg\",\n state.disabled && \"cursor-not-allowed opacity-40\",\n )}\n >\n {day.day}\n </button>\n </td>\n );\n })}\n </tr>\n ))}\n </tbody>\n </table>\n {mode === \"date-time\" ? (\n <Input\n aria-label={`${label || name} time`}\n disabled={disabled}\n onChange={(event) => {\n const [hour = \"0\", minute = \"0\", second = \"0\"] = event.target.value.split(\":\");\n\n api.setTime({\n hour: Number(hour),\n minute: Number(minute),\n second: Number(second),\n });\n }}\n readOnly={readOnly}\n step={step ?? undefined}\n type=\"time\"\n value={formatTimeInputValue(selected[0], timezone)}\n />\n ) : null}\n </div>\n </div>\n ) : null}\n </div>\n );\n}\n\nfunction normalizeDateInputValue(value: string): string | undefined {\n const compact = value.replace(/\\D/g, \"\");\n\n if (compact.length !== 8) {\n return undefined;\n }\n\n return `${compact.slice(0, 4)}-${compact.slice(4, 6)}-${compact.slice(6, 8)}`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"date-picker-control.js","names":[],"sources":["../../../../resources/js/form/components/fields/date-picker-control.tsx"],"sourcesContent":["import type { DateValue } from \"@internationalized/date\";\nimport * as datePicker from \"@zag-js/date-picker\";\nimport { normalizeProps, useMachine } from \"@zag-js/react\";\nimport { useId, useMemo } from \"react\";\nimport { Button } from \"@lattice-php/lattice/core/components/button\";\nimport { Icon } from \"@lattice-php/lattice/icons\";\nimport { useLocale } from \"@lattice-php/lattice/i18n\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { Input } from \"../base/input\";\nimport {\n formatDateDisplayValue,\n formatDateTimeDisplayValue,\n formatDateTimeValue,\n formatDateValue,\n formatTimeInputValue,\n parseDateDisplayValue,\n parseDateTimeDisplayValue,\n parseDateTimeValue,\n parseDateValue,\n} from \"./date-picker-value\";\nimport { TimePicker } from \"./time-picker\";\nimport { parseTimeString } from \"./time-picker-columns\";\n\nexport type DatePickerControlProps = {\n mode: \"date\" | \"date-time\";\n label: string;\n name: string;\n testId: string;\n value: unknown;\n min?: string | null;\n max?: string | null;\n step?: number | null;\n disabled: boolean;\n readOnly: boolean;\n autoFocus?: boolean;\n tabIndex?: number | null;\n timezone?: string;\n onChange: (value: string) => void;\n onBlur?: () => void;\n};\n\nexport function DatePickerControl({\n mode,\n label,\n name,\n testId,\n value,\n min,\n max,\n step,\n disabled,\n readOnly,\n autoFocus = false,\n tabIndex,\n timezone = \"UTC\",\n onChange,\n onBlur,\n}: DatePickerControlProps) {\n const id = useId();\n const { locale } = useLocale();\n const selected = useMemo(\n () =>\n [mode === \"date\" ? parseDateValue(value) : parseDateTimeValue(value, timezone)].filter(\n Boolean,\n ) as DateValue[],\n [mode, timezone, value],\n );\n const service = useMachine(datePicker.machine, {\n id,\n name,\n value: selected.length > 0 ? selected : undefined,\n min: min ? parseDateValue(min) : undefined,\n max: max ? parseDateValue(max) : undefined,\n disabled,\n readOnly,\n locale,\n selectionMode: \"single\",\n timeZone: timezone,\n closeOnSelect: mode === \"date\",\n format(date) {\n return mode === \"date\"\n ? formatDateDisplayValue(date, locale)\n : formatDateTimeDisplayValue(date, locale, timezone);\n },\n parse(text) {\n return mode === \"date\"\n ? parseDateDisplayValue(text, locale)\n : parseDateTimeDisplayValue(text, locale, timezone);\n },\n onValueChange(details) {\n const next = details.value[0];\n\n onChange(mode === \"date\" ? formatDateValue(next) : formatDateTimeValue(next, timezone));\n },\n onOpenChange(details) {\n if (!details.open) {\n onBlur?.();\n }\n },\n });\n const api = datePicker.connect(service, normalizeProps);\n const { name: _inputName, onInput, ...inputProps } = api.getInputProps();\n const submittedValue =\n mode === \"date\" ? formatDateValue(selected[0]) : formatDateTimeValue(selected[0], timezone);\n\n return (\n <div {...api.getRootProps()} className={cn(\"relative\", api.open && \"z-lt-popover\")}>\n <input type=\"hidden\" name={name} value={submittedValue} data-test={`${testId}-value`} />\n <div {...api.getControlProps()} className=\"flex gap-2\">\n <Input\n {...inputProps}\n aria-label={label}\n autoFocus={autoFocus}\n data-test={testId}\n disabled={disabled}\n id={name}\n onInput={(event) => {\n onInput?.(event);\n\n if (mode !== \"date\") {\n return;\n }\n\n const normalized = normalizeDateInputValue(event.currentTarget.value);\n\n if (!normalized) {\n return;\n }\n\n const next = parseDateValue(normalized);\n\n if (!next) {\n return;\n }\n\n event.currentTarget.value = normalized;\n api.setValue([next]);\n event.currentTarget.value = formatDateDisplayValue(next, locale);\n onChange(formatDateValue(next));\n }}\n readOnly={readOnly}\n tabIndex={tabIndex ?? undefined}\n />\n <Button\n {...api.getTriggerProps()}\n aria-label={`Open ${label || name} calendar`}\n disabled={disabled || readOnly}\n size=\"icon\"\n type=\"button\"\n variant=\"secondary\"\n >\n <Icon name=\"calendar\" className=\"size-lt-icon-md\" aria-hidden=\"true\" />\n </Button>\n </div>\n {api.open ? (\n <div\n {...api.getPositionerProps()}\n className=\"absolute z-lt-popover mt-2 rounded-lt-sm border border-lt-border bg-lt-popover p-3 text-lt-popover-fg shadow-lt-md\"\n >\n <div {...api.getContentProps()} className=\"grid gap-3\">\n <div className=\"flex items-center justify-between gap-2\">\n <Button {...api.getPrevTriggerProps()} size=\"icon\" type=\"button\" variant=\"ghost\">\n <Icon name=\"chevron-left\" className=\"size-lt-icon-md\" aria-hidden=\"true\" />\n </Button>\n <div {...api.getRangeTextProps()} className=\"text-sm font-medium text-lt-fg\" />\n <Button {...api.getNextTriggerProps()} size=\"icon\" type=\"button\" variant=\"ghost\">\n <Icon name=\"chevron-right\" className=\"size-lt-icon-md\" aria-hidden=\"true\" />\n </Button>\n </div>\n <table {...api.getTableProps()} className=\"w-full border-collapse text-sm\">\n <thead {...api.getTableHeadProps()}>\n <tr {...api.getTableRowProps()}>\n {api.weekDays.map((day) => (\n <th\n {...api.getTableHeaderProps()}\n aria-label={day.long}\n key={day.value.toString()}\n className=\"size-8 text-center text-xs font-medium text-lt-muted-fg\"\n >\n {day.narrow}\n </th>\n ))}\n </tr>\n </thead>\n <tbody {...api.getTableBodyProps()}>\n {api.weeks.map((week, weekIndex) => (\n <tr {...api.getTableRowProps()} key={weekIndex}>\n {week.map((day) => {\n const state = api.getDayTableCellState({ value: day });\n\n return (\n <td\n {...api.getDayTableCellProps({ value: day })}\n key={day.toString()}\n className=\"p-0 text-center\"\n >\n <button\n {...api.getDayTableCellTriggerProps({ value: day })}\n type=\"button\"\n className={cn(\n \"size-8 rounded-lt-sm text-sm text-lt-fg hover:bg-lt-muted\",\n state.selected && \"bg-lt-primary text-lt-primary-fg\",\n state.outsideRange && \"text-lt-muted-fg\",\n state.disabled && \"cursor-not-allowed opacity-40\",\n )}\n >\n {day.day}\n </button>\n </td>\n );\n })}\n </tr>\n ))}\n </tbody>\n </table>\n {mode === \"date-time\" ? (\n <TimePicker\n value={parseTimeString(formatTimeInputValue(selected[0], timezone))}\n onChange={(next) =>\n api.setTime({ hour: next.hour, minute: next.minute, second: next.second })\n }\n step={step}\n disabled={disabled}\n readOnly={readOnly}\n testId={`${testId}-time`}\n />\n ) : null}\n </div>\n </div>\n ) : null}\n </div>\n );\n}\n\nfunction normalizeDateInputValue(value: string): string | undefined {\n const compact = value.replace(/\\D/g, \"\");\n\n if (compact.length !== 8) {\n return undefined;\n }\n\n return `${compact.slice(0, 4)}-${compact.slice(4, 6)}-${compact.slice(6, 8)}`;\n}\n"],"mappings":"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"}
|
|
@@ -1,26 +1,65 @@
|
|
|
1
|
+
import { Icon } from "../../../icons/sprite.js";
|
|
2
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../../../core/components/popover.js";
|
|
3
|
+
import { Button } from "../../../core/components/button.js";
|
|
1
4
|
import { SimpleField } from "./simple-field.js";
|
|
2
5
|
import { Input } from "../base/input.js";
|
|
3
|
-
import {
|
|
6
|
+
import { formatTimeValue, parseTimeString, secondsEnabled } from "./time-picker-columns.js";
|
|
7
|
+
import { TimePicker } from "./time-picker.js";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
9
|
//#region resources/js/form/components/fields/time-input.tsx
|
|
5
10
|
var TimeInputComponent = ({ node }) => {
|
|
6
11
|
const props = node.props;
|
|
12
|
+
const withSeconds = secondsEnabled(props.step);
|
|
13
|
+
const triggerLabel = props.label ?? props.name;
|
|
7
14
|
return /* @__PURE__ */ jsx(SimpleField, {
|
|
8
15
|
node,
|
|
9
16
|
label: props.label ?? "",
|
|
10
|
-
children: ({ name, testId, value, readOnly, disabled, commit }) => /* @__PURE__ */
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
children: ({ name, testId, value, readOnly, disabled, commit, blur }) => /* @__PURE__ */ jsxs("div", {
|
|
18
|
+
className: "flex gap-2",
|
|
19
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
20
|
+
"aria-label": triggerLabel,
|
|
21
|
+
autoFocus: props.autoFocus ?? false,
|
|
22
|
+
"data-test": testId,
|
|
23
|
+
disabled,
|
|
24
|
+
id: name,
|
|
25
|
+
name,
|
|
26
|
+
onBlur: () => {
|
|
27
|
+
const parsed = parseTimeString(value);
|
|
28
|
+
if (parsed) commit(formatTimeValue(parsed, withSeconds));
|
|
29
|
+
blur();
|
|
30
|
+
},
|
|
31
|
+
onChange: (event) => commit(event.target.value),
|
|
32
|
+
readOnly,
|
|
33
|
+
tabIndex: props.tabIndex ?? void 0,
|
|
34
|
+
type: "text",
|
|
35
|
+
value
|
|
36
|
+
}), /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
37
|
+
asChild: true,
|
|
38
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
39
|
+
"aria-label": `Open ${triggerLabel} time picker`,
|
|
40
|
+
disabled: disabled || readOnly,
|
|
41
|
+
size: "icon",
|
|
42
|
+
type: "button",
|
|
43
|
+
variant: "secondary",
|
|
44
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
45
|
+
name: "clock",
|
|
46
|
+
className: "size-lt-icon-md",
|
|
47
|
+
"aria-hidden": "true"
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
51
|
+
className: "p-2",
|
|
52
|
+
children: /* @__PURE__ */ jsx(TimePicker, {
|
|
53
|
+
value: parseTimeString(value),
|
|
54
|
+
onChange: (next) => commit(formatTimeValue(next, withSeconds)),
|
|
55
|
+
step: props.step,
|
|
56
|
+
min: props.min,
|
|
57
|
+
max: props.max,
|
|
58
|
+
disabled,
|
|
59
|
+
readOnly,
|
|
60
|
+
testId: `${testId}-picker`
|
|
61
|
+
})
|
|
62
|
+
})] })]
|
|
24
63
|
})
|
|
25
64
|
});
|
|
26
65
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"time-input.js","names":[],"sources":["../../../../resources/js/form/components/fields/time-input.tsx"],"sourcesContent":["import type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { Input } from \"../base/input\";\nimport { SimpleField } from \"./simple-field\";\n\nexport const TimeInputComponent: RendererComponent<\"field.time-input\"> = ({ node }) => {\n const props = node.props;\n\n return (\n <SimpleField node={node} label={props.label ?? \"\"}>\n {({ name, testId, value, readOnly, disabled, commit }) => (\n <Input\n
|
|
1
|
+
{"version":3,"file":"time-input.js","names":[],"sources":["../../../../resources/js/form/components/fields/time-input.tsx"],"sourcesContent":["import type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { Button } from \"@lattice-php/lattice/core/components/button\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@lattice-php/lattice/core/components/popover\";\nimport { Icon } from \"@lattice-php/lattice/icons\";\nimport { Input } from \"../base/input\";\nimport { SimpleField } from \"./simple-field\";\nimport { TimePicker } from \"./time-picker\";\nimport { formatTimeValue, parseTimeString, secondsEnabled } from \"./time-picker-columns\";\n\nexport const TimeInputComponent: RendererComponent<\"field.time-input\"> = ({ node }) => {\n const props = node.props;\n const withSeconds = secondsEnabled(props.step);\n const triggerLabel = props.label ?? props.name;\n\n return (\n <SimpleField node={node} label={props.label ?? \"\"}>\n {({ name, testId, value, readOnly, disabled, commit, blur }) => (\n <div className=\"flex gap-2\">\n <Input\n aria-label={triggerLabel}\n autoFocus={props.autoFocus ?? false}\n data-test={testId}\n disabled={disabled}\n id={name}\n name={name}\n onBlur={() => {\n const parsed = parseTimeString(value);\n\n if (parsed) {\n commit(formatTimeValue(parsed, withSeconds));\n }\n\n blur();\n }}\n onChange={(event) => commit(event.target.value)}\n readOnly={readOnly}\n tabIndex={props.tabIndex ?? undefined}\n type=\"text\"\n value={value}\n />\n <Popover>\n <PopoverTrigger asChild>\n <Button\n aria-label={`Open ${triggerLabel} time picker`}\n disabled={disabled || readOnly}\n size=\"icon\"\n type=\"button\"\n variant=\"secondary\"\n >\n <Icon name=\"clock\" className=\"size-lt-icon-md\" aria-hidden=\"true\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent className=\"p-2\">\n <TimePicker\n value={parseTimeString(value)}\n onChange={(next) => commit(formatTimeValue(next, withSeconds))}\n step={props.step}\n min={props.min}\n max={props.max}\n disabled={disabled}\n readOnly={readOnly}\n testId={`${testId}-picker`}\n />\n </PopoverContent>\n </Popover>\n </div>\n )}\n </SimpleField>\n );\n};\n"],"mappings":";;;;;;;;;AAaA,IAAa,sBAA6D,EAAE,WAAW;CACrF,MAAM,QAAQ,KAAK;CACnB,MAAM,cAAc,eAAe,MAAM,IAAI;CAC7C,MAAM,eAAe,MAAM,SAAS,MAAM;CAE1C,OACE,oBAAC,aAAD;EAAmB;EAAM,OAAO,MAAM,SAAS;aAC3C,EAAE,MAAM,QAAQ,OAAO,UAAU,UAAU,QAAQ,WACnD,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,OAAD;IACE,cAAY;IACZ,WAAW,MAAM,aAAa;IAC9B,aAAW;IACD;IACV,IAAI;IACE;IACN,cAAc;KACZ,MAAM,SAAS,gBAAgB,KAAK;KAEpC,IAAI,QACF,OAAO,gBAAgB,QAAQ,WAAW,CAAC;KAG7C,KAAK;IACP;IACA,WAAW,UAAU,OAAO,MAAM,OAAO,KAAK;IACpC;IACV,UAAU,MAAM,YAAY,KAAA;IAC5B,MAAK;IACE;GACR,CAAA,GACD,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,gBAAD;IAAgB,SAAA;cACd,oBAAC,QAAD;KACE,cAAY,QAAQ,aAAa;KACjC,UAAU,YAAY;KACtB,MAAK;KACL,MAAK;KACL,SAAQ;eAER,oBAAC,MAAD;MAAM,MAAK;MAAQ,WAAU;MAAkB,eAAY;KAAQ,CAAA;IAC7D,CAAA;GACM,CAAA,GAChB,oBAAC,gBAAD;IAAgB,WAAU;cACxB,oBAAC,YAAD;KACE,OAAO,gBAAgB,KAAK;KAC5B,WAAW,SAAS,OAAO,gBAAgB,MAAM,WAAW,CAAC;KAC7D,MAAM,MAAM;KACZ,KAAK,MAAM;KACX,KAAK,MAAM;KACD;KACA;KACV,QAAQ,GAAG,OAAO;IACnB,CAAA;GACa,CAAA,CACT,EAAA,CAAA,CACN;;CAEI,CAAA;AAEjB"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type TimeValue = {
|
|
2
|
+
hour: number;
|
|
3
|
+
minute: number;
|
|
4
|
+
second: number;
|
|
5
|
+
};
|
|
6
|
+
export type TimeColumnOption = {
|
|
7
|
+
value: number;
|
|
8
|
+
label: string;
|
|
9
|
+
disabled: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type TimeColumns = {
|
|
12
|
+
hours: TimeColumnOption[];
|
|
13
|
+
minutes: TimeColumnOption[];
|
|
14
|
+
seconds: TimeColumnOption[] | null;
|
|
15
|
+
};
|
|
16
|
+
export declare function parseTimeString(value: string | null | undefined): TimeValue | null;
|
|
17
|
+
export declare function formatTimeValue(value: TimeValue, withSeconds: boolean): string;
|
|
18
|
+
export declare function secondsEnabled(step: number | null | undefined): boolean;
|
|
19
|
+
export declare function buildTimeColumns(step: number | null | undefined, options?: {
|
|
20
|
+
min?: string | null;
|
|
21
|
+
max?: string | null;
|
|
22
|
+
current?: TimeValue | null;
|
|
23
|
+
}): TimeColumns;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//#region resources/js/form/components/fields/time-picker-columns.ts
|
|
2
|
+
var timePattern = /^(\d{1,2}):(\d{2})(?::(\d{2}))?$/;
|
|
3
|
+
function parseTimeString(value) {
|
|
4
|
+
if (typeof value !== "string") return null;
|
|
5
|
+
const match = timePattern.exec(value.trim());
|
|
6
|
+
if (!match) return null;
|
|
7
|
+
const hour = Number(match[1]);
|
|
8
|
+
const minute = Number(match[2]);
|
|
9
|
+
const second = match[3] ? Number(match[3]) : 0;
|
|
10
|
+
if (hour > 23 || minute > 59 || second > 59) return null;
|
|
11
|
+
return {
|
|
12
|
+
hour,
|
|
13
|
+
minute,
|
|
14
|
+
second
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function formatTimeValue(value, withSeconds) {
|
|
18
|
+
const parts = [pad(value.hour), pad(value.minute)];
|
|
19
|
+
if (withSeconds) parts.push(pad(value.second));
|
|
20
|
+
return parts.join(":");
|
|
21
|
+
}
|
|
22
|
+
function secondsEnabled(step) {
|
|
23
|
+
return step != null && step < 60;
|
|
24
|
+
}
|
|
25
|
+
function buildTimeColumns(step, options = {}) {
|
|
26
|
+
const { min, max, current } = options;
|
|
27
|
+
const minBound = toBound(min);
|
|
28
|
+
const maxBound = toBound(max);
|
|
29
|
+
const minuteStep = step == null || step < 60 ? 1 : step % 60 === 0 ? step / 60 : 1;
|
|
30
|
+
return {
|
|
31
|
+
hours: range(0, 23, 1).map((value) => ({
|
|
32
|
+
value,
|
|
33
|
+
label: pad(value),
|
|
34
|
+
disabled: minBound != null && value < minBound.hour || maxBound != null && value > maxBound.hour
|
|
35
|
+
})),
|
|
36
|
+
minutes: withValue(range(0, 59, minuteStep), current?.minute).map((value) => ({
|
|
37
|
+
value,
|
|
38
|
+
label: pad(value),
|
|
39
|
+
disabled: minuteDisabled(value, current, minBound, maxBound)
|
|
40
|
+
})),
|
|
41
|
+
seconds: secondsEnabled(step) ? range(0, 59, 1).map((value) => ({
|
|
42
|
+
value,
|
|
43
|
+
label: pad(value),
|
|
44
|
+
disabled: false
|
|
45
|
+
})) : null
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function minuteDisabled(minute, current, minBound, maxBound) {
|
|
49
|
+
if (!current) return false;
|
|
50
|
+
if (minBound != null && current.hour === minBound.hour && minute < minBound.minute) return true;
|
|
51
|
+
if (maxBound != null && current.hour === maxBound.hour && minute > maxBound.minute) return true;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
function toBound(value) {
|
|
55
|
+
const parsed = parseTimeString(value);
|
|
56
|
+
return parsed ? {
|
|
57
|
+
hour: parsed.hour,
|
|
58
|
+
minute: parsed.minute
|
|
59
|
+
} : null;
|
|
60
|
+
}
|
|
61
|
+
function withValue(values, extra) {
|
|
62
|
+
if (extra == null || values.includes(extra)) return values;
|
|
63
|
+
return [...values, extra].sort((left, right) => left - right);
|
|
64
|
+
}
|
|
65
|
+
function range(start, end, step) {
|
|
66
|
+
const values = [];
|
|
67
|
+
for (let value = start; value <= end; value += step) values.push(value);
|
|
68
|
+
return values;
|
|
69
|
+
}
|
|
70
|
+
function pad(value) {
|
|
71
|
+
return String(value).padStart(2, "0");
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
export { buildTimeColumns, formatTimeValue, parseTimeString, secondsEnabled };
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=time-picker-columns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-picker-columns.js","names":[],"sources":["../../../../resources/js/form/components/fields/time-picker-columns.ts"],"sourcesContent":["export type TimeValue = { hour: number; minute: number; second: number };\n\nexport type TimeColumnOption = { value: number; label: string; disabled: boolean };\n\nexport type TimeColumns = {\n hours: TimeColumnOption[];\n minutes: TimeColumnOption[];\n seconds: TimeColumnOption[] | null;\n};\n\ntype Bound = { hour: number; minute: number };\n\nconst timePattern = /^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?$/;\n\nexport function parseTimeString(value: string | null | undefined): TimeValue | null {\n if (typeof value !== \"string\") {\n return null;\n }\n\n const match = timePattern.exec(value.trim());\n\n if (!match) {\n return null;\n }\n\n const hour = Number(match[1]);\n const minute = Number(match[2]);\n const second = match[3] ? Number(match[3]) : 0;\n\n if (hour > 23 || minute > 59 || second > 59) {\n return null;\n }\n\n return { hour, minute, second };\n}\n\nexport function formatTimeValue(value: TimeValue, withSeconds: boolean): string {\n const parts = [pad(value.hour), pad(value.minute)];\n\n if (withSeconds) {\n parts.push(pad(value.second));\n }\n\n return parts.join(\":\");\n}\n\nexport function secondsEnabled(step: number | null | undefined): boolean {\n return step != null && step < 60;\n}\n\nexport function buildTimeColumns(\n step: number | null | undefined,\n options: { min?: string | null; max?: string | null; current?: TimeValue | null } = {},\n): TimeColumns {\n const { min, max, current } = options;\n const minBound = toBound(min);\n const maxBound = toBound(max);\n const minuteStep = step == null || step < 60 ? 1 : step % 60 === 0 ? step / 60 : 1;\n\n const hours = range(0, 23, 1).map((value) => ({\n value,\n label: pad(value),\n disabled:\n (minBound != null && value < minBound.hour) || (maxBound != null && value > maxBound.hour),\n }));\n\n const minuteValues = withValue(range(0, 59, minuteStep), current?.minute);\n const minutes = minuteValues.map((value) => ({\n value,\n label: pad(value),\n disabled: minuteDisabled(value, current, minBound, maxBound),\n }));\n\n const seconds = secondsEnabled(step)\n ? range(0, 59, 1).map((value) => ({ value, label: pad(value), disabled: false }))\n : null;\n\n return { hours, minutes, seconds };\n}\n\nfunction minuteDisabled(\n minute: number,\n current: TimeValue | null | undefined,\n minBound: Bound | null,\n maxBound: Bound | null,\n): boolean {\n if (!current) {\n return false;\n }\n\n if (minBound != null && current.hour === minBound.hour && minute < minBound.minute) {\n return true;\n }\n\n if (maxBound != null && current.hour === maxBound.hour && minute > maxBound.minute) {\n return true;\n }\n\n return false;\n}\n\nfunction toBound(value: string | null | undefined): Bound | null {\n const parsed = parseTimeString(value);\n\n return parsed ? { hour: parsed.hour, minute: parsed.minute } : null;\n}\n\nfunction withValue(values: number[], extra: number | undefined): number[] {\n if (extra == null || values.includes(extra)) {\n return values;\n }\n\n return [...values, extra].sort((left, right) => left - right);\n}\n\nfunction range(start: number, end: number, step: number): number[] {\n const values: number[] = [];\n\n for (let value = start; value <= end; value += step) {\n values.push(value);\n }\n\n return values;\n}\n\nfunction pad(value: number): string {\n return String(value).padStart(2, \"0\");\n}\n"],"mappings":";AAYA,IAAM,cAAc;AAEpB,SAAgB,gBAAgB,OAAoD;CAClF,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,MAAM,QAAQ,YAAY,KAAK,MAAM,KAAK,CAAC;CAE3C,IAAI,CAAC,OACH,OAAO;CAGT,MAAM,OAAO,OAAO,MAAM,EAAE;CAC5B,MAAM,SAAS,OAAO,MAAM,EAAE;CAC9B,MAAM,SAAS,MAAM,KAAK,OAAO,MAAM,EAAE,IAAI;CAE7C,IAAI,OAAO,MAAM,SAAS,MAAM,SAAS,IACvC,OAAO;CAGT,OAAO;EAAE;EAAM;EAAQ;CAAO;AAChC;AAEA,SAAgB,gBAAgB,OAAkB,aAA8B;CAC9E,MAAM,QAAQ,CAAC,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,MAAM,CAAC;CAEjD,IAAI,aACF,MAAM,KAAK,IAAI,MAAM,MAAM,CAAC;CAG9B,OAAO,MAAM,KAAK,GAAG;AACvB;AAEA,SAAgB,eAAe,MAA0C;CACvE,OAAO,QAAQ,QAAQ,OAAO;AAChC;AAEA,SAAgB,iBACd,MACA,UAAoF,CAAC,GACxE;CACb,MAAM,EAAE,KAAK,KAAK,YAAY;CAC9B,MAAM,WAAW,QAAQ,GAAG;CAC5B,MAAM,WAAW,QAAQ,GAAG;CAC5B,MAAM,aAAa,QAAQ,QAAQ,OAAO,KAAK,IAAI,OAAO,OAAO,IAAI,OAAO,KAAK;CAoBjF,OAAO;EAAE,OAlBK,MAAM,GAAG,IAAI,CAAC,EAAE,KAAK,WAAW;GAC5C;GACA,OAAO,IAAI,KAAK;GAChB,UACG,YAAY,QAAQ,QAAQ,SAAS,QAAU,YAAY,QAAQ,QAAQ,SAAS;EACzF,EAaS;EAAO,SAXK,UAAU,MAAM,GAAG,IAAI,UAAU,GAAG,SAAS,MAClD,EAAa,KAAK,WAAW;GAC3C;GACA,OAAO,IAAI,KAAK;GAChB,UAAU,eAAe,OAAO,SAAS,UAAU,QAAQ;EAC7D,EAMgB;EAAS,SAJT,eAAe,IAAI,IAC/B,MAAM,GAAG,IAAI,CAAC,EAAE,KAAK,WAAW;GAAE;GAAO,OAAO,IAAI,KAAK;GAAG,UAAU;EAAM,EAAE,IAC9E;CAE6B;AACnC;AAEA,SAAS,eACP,QACA,SACA,UACA,UACS;CACT,IAAI,CAAC,SACH,OAAO;CAGT,IAAI,YAAY,QAAQ,QAAQ,SAAS,SAAS,QAAQ,SAAS,SAAS,QAC1E,OAAO;CAGT,IAAI,YAAY,QAAQ,QAAQ,SAAS,SAAS,QAAQ,SAAS,SAAS,QAC1E,OAAO;CAGT,OAAO;AACT;AAEA,SAAS,QAAQ,OAAgD;CAC/D,MAAM,SAAS,gBAAgB,KAAK;CAEpC,OAAO,SAAS;EAAE,MAAM,OAAO;EAAM,QAAQ,OAAO;CAAO,IAAI;AACjE;AAEA,SAAS,UAAU,QAAkB,OAAqC;CACxE,IAAI,SAAS,QAAQ,OAAO,SAAS,KAAK,GACxC,OAAO;CAGT,OAAO,CAAC,GAAG,QAAQ,KAAK,EAAE,MAAM,MAAM,UAAU,OAAO,KAAK;AAC9D;AAEA,SAAS,MAAM,OAAe,KAAa,MAAwB;CACjE,MAAM,SAAmB,CAAC;CAE1B,KAAK,IAAI,QAAQ,OAAO,SAAS,KAAK,SAAS,MAC7C,OAAO,KAAK,KAAK;CAGnB,OAAO;AACT;AAEA,SAAS,IAAI,OAAuB;CAClC,OAAO,OAAO,KAAK,EAAE,SAAS,GAAG,GAAG;AACtC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { TimeValue } from './time-picker-columns';
|
|
2
|
+
export type TimePickerProps = {
|
|
3
|
+
value: TimeValue | null;
|
|
4
|
+
onChange: (next: TimeValue) => void;
|
|
5
|
+
step?: number | null;
|
|
6
|
+
min?: string | null;
|
|
7
|
+
max?: string | null;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
readOnly?: boolean;
|
|
10
|
+
labels?: {
|
|
11
|
+
hour?: string;
|
|
12
|
+
minute?: string;
|
|
13
|
+
second?: string;
|
|
14
|
+
};
|
|
15
|
+
testId?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function TimePicker({ value, onChange, step, min, max, disabled, readOnly, labels, testId, }: TimePickerProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { cn } from "../../../lib/utils.js";
|
|
2
|
+
import { buildTimeColumns } from "./time-picker-columns.js";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
//#region resources/js/form/components/fields/time-picker.tsx
|
|
6
|
+
function TimePicker({ value, onChange, step, min, max, disabled = false, readOnly = false, labels, testId }) {
|
|
7
|
+
const containerRef = useRef(null);
|
|
8
|
+
const columns = buildTimeColumns(step, {
|
|
9
|
+
min,
|
|
10
|
+
max,
|
|
11
|
+
current: value
|
|
12
|
+
});
|
|
13
|
+
const current = value ?? {
|
|
14
|
+
hour: 0,
|
|
15
|
+
minute: 0,
|
|
16
|
+
second: 0
|
|
17
|
+
};
|
|
18
|
+
const interactive = !disabled && !readOnly;
|
|
19
|
+
const columnList = [
|
|
20
|
+
{
|
|
21
|
+
key: "hour",
|
|
22
|
+
label: labels?.hour ?? "Hour",
|
|
23
|
+
options: columns.hours,
|
|
24
|
+
selected: current.hour
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: "minute",
|
|
28
|
+
label: labels?.minute ?? "Minute",
|
|
29
|
+
options: columns.minutes,
|
|
30
|
+
selected: current.minute
|
|
31
|
+
},
|
|
32
|
+
...columns.seconds ? [{
|
|
33
|
+
key: "second",
|
|
34
|
+
label: labels?.second ?? "Second",
|
|
35
|
+
options: columns.seconds,
|
|
36
|
+
selected: current.second
|
|
37
|
+
}] : []
|
|
38
|
+
];
|
|
39
|
+
function focusColumn(index) {
|
|
40
|
+
const target = (containerRef.current?.querySelectorAll("[role=\"listbox\"]"))?.[index];
|
|
41
|
+
if (!target) return;
|
|
42
|
+
(target.querySelector("[data-active=\"true\"]") ?? target.querySelector("[role=\"option\"]"))?.focus();
|
|
43
|
+
}
|
|
44
|
+
return /* @__PURE__ */ jsx("div", {
|
|
45
|
+
ref: containerRef,
|
|
46
|
+
className: "flex gap-1",
|
|
47
|
+
"data-test": testId,
|
|
48
|
+
children: columnList.map((column, index) => /* @__PURE__ */ jsx(TimeColumn, {
|
|
49
|
+
label: column.label,
|
|
50
|
+
options: column.options,
|
|
51
|
+
selected: value ? column.selected : null,
|
|
52
|
+
disabled: !interactive,
|
|
53
|
+
onSelect: (optionValue) => interactive && onChange({
|
|
54
|
+
...current,
|
|
55
|
+
[column.key]: optionValue
|
|
56
|
+
}),
|
|
57
|
+
onHorizontal: (direction) => focusColumn(index + direction)
|
|
58
|
+
}, column.key))
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function TimeColumn({ label, options, selected, disabled, onSelect, onHorizontal }) {
|
|
62
|
+
const listRef = useRef(null);
|
|
63
|
+
const enabledValues = options.filter((option) => !option.disabled).map((option) => option.value);
|
|
64
|
+
const activeValue = selected ?? enabledValues[0] ?? options[0]?.value ?? 0;
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
(listRef.current?.querySelector("[data-active=\"true\"]"))?.scrollIntoView?.({ block: "nearest" });
|
|
67
|
+
}, [selected]);
|
|
68
|
+
function moveTo(nextValue) {
|
|
69
|
+
if (nextValue == null) return;
|
|
70
|
+
listRef.current?.querySelector(`[data-value="${nextValue}"]`)?.focus();
|
|
71
|
+
onSelect(nextValue);
|
|
72
|
+
}
|
|
73
|
+
function handleKeyDown(event) {
|
|
74
|
+
if (disabled) return;
|
|
75
|
+
const index = enabledValues.indexOf(activeValue);
|
|
76
|
+
switch (event.key) {
|
|
77
|
+
case "ArrowDown":
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
moveTo(enabledValues[Math.min(index + 1, enabledValues.length - 1)]);
|
|
80
|
+
break;
|
|
81
|
+
case "ArrowUp":
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
moveTo(enabledValues[Math.max(index - 1, 0)]);
|
|
84
|
+
break;
|
|
85
|
+
case "Home":
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
moveTo(enabledValues[0]);
|
|
88
|
+
break;
|
|
89
|
+
case "End":
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
moveTo(enabledValues[enabledValues.length - 1]);
|
|
92
|
+
break;
|
|
93
|
+
case "ArrowRight":
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
onHorizontal(1);
|
|
96
|
+
break;
|
|
97
|
+
case "ArrowLeft":
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
onHorizontal(-1);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return /* @__PURE__ */ jsx("div", {
|
|
104
|
+
ref: listRef,
|
|
105
|
+
role: "listbox",
|
|
106
|
+
"aria-label": label,
|
|
107
|
+
"aria-orientation": "vertical",
|
|
108
|
+
tabIndex: -1,
|
|
109
|
+
className: "flex max-h-40 w-14 flex-col overflow-y-auto",
|
|
110
|
+
onKeyDown: handleKeyDown,
|
|
111
|
+
children: options.map((option) => {
|
|
112
|
+
const isSelected = selected === option.value;
|
|
113
|
+
return /* @__PURE__ */ jsx("button", {
|
|
114
|
+
type: "button",
|
|
115
|
+
role: "option",
|
|
116
|
+
"aria-selected": isSelected,
|
|
117
|
+
"aria-label": `${label} ${option.label}`,
|
|
118
|
+
"data-value": option.value,
|
|
119
|
+
"data-active": activeValue === option.value,
|
|
120
|
+
disabled: disabled || option.disabled,
|
|
121
|
+
tabIndex: activeValue === option.value ? 0 : -1,
|
|
122
|
+
onClick: () => onSelect(option.value),
|
|
123
|
+
className: cn("shrink-0 rounded-lt-sm px-2 py-1 text-sm text-lt-fg hover:bg-lt-muted", isSelected && "bg-lt-primary text-lt-primary-fg", (disabled || option.disabled) && "cursor-not-allowed opacity-40"),
|
|
124
|
+
children: option.label
|
|
125
|
+
}, option.value);
|
|
126
|
+
})
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { TimePicker };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=time-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-picker.js","names":[],"sources":["../../../../resources/js/form/components/fields/time-picker.tsx"],"sourcesContent":["import { type KeyboardEvent, useEffect, useRef } from \"react\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { buildTimeColumns, type TimeColumnOption, type TimeValue } from \"./time-picker-columns\";\n\nexport type TimePickerProps = {\n value: TimeValue | null;\n onChange: (next: TimeValue) => void;\n step?: number | null;\n min?: string | null;\n max?: string | null;\n disabled?: boolean;\n readOnly?: boolean;\n labels?: { hour?: string; minute?: string; second?: string };\n testId?: string;\n};\n\nexport function TimePicker({\n value,\n onChange,\n step,\n min,\n max,\n disabled = false,\n readOnly = false,\n labels,\n testId,\n}: TimePickerProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const columns = buildTimeColumns(step, { min, max, current: value });\n const current: TimeValue = value ?? { hour: 0, minute: 0, second: 0 };\n const interactive = !disabled && !readOnly;\n\n const columnList = [\n {\n key: \"hour\" as const,\n label: labels?.hour ?? \"Hour\",\n options: columns.hours,\n selected: current.hour,\n },\n {\n key: \"minute\" as const,\n label: labels?.minute ?? \"Minute\",\n options: columns.minutes,\n selected: current.minute,\n },\n ...(columns.seconds\n ? [\n {\n key: \"second\" as const,\n label: labels?.second ?? \"Second\",\n options: columns.seconds,\n selected: current.second,\n },\n ]\n : []),\n ];\n\n function focusColumn(index: number) {\n const listboxes = containerRef.current?.querySelectorAll<HTMLElement>('[role=\"listbox\"]');\n const target = listboxes?.[index];\n\n if (!target) {\n return;\n }\n\n const active =\n target.querySelector<HTMLElement>('[data-active=\"true\"]') ??\n target.querySelector<HTMLElement>('[role=\"option\"]');\n\n active?.focus();\n }\n\n return (\n <div ref={containerRef} className=\"flex gap-1\" data-test={testId}>\n {columnList.map((column, index) => (\n <TimeColumn\n key={column.key}\n label={column.label}\n options={column.options}\n selected={value ? column.selected : null}\n disabled={!interactive}\n onSelect={(optionValue) =>\n interactive && onChange({ ...current, [column.key]: optionValue })\n }\n onHorizontal={(direction) => focusColumn(index + direction)}\n />\n ))}\n </div>\n );\n}\n\nfunction TimeColumn({\n label,\n options,\n selected,\n disabled,\n onSelect,\n onHorizontal,\n}: {\n label: string;\n options: TimeColumnOption[];\n selected: number | null;\n disabled: boolean;\n onSelect: (value: number) => void;\n onHorizontal: (direction: 1 | -1) => void;\n}) {\n const listRef = useRef<HTMLDivElement>(null);\n const enabledValues = options.filter((option) => !option.disabled).map((option) => option.value);\n const activeValue = selected ?? enabledValues[0] ?? options[0]?.value ?? 0;\n\n useEffect(() => {\n const active = listRef.current?.querySelector<HTMLElement>('[data-active=\"true\"]');\n\n active?.scrollIntoView?.({ block: \"nearest\" });\n }, [selected]);\n\n function moveTo(nextValue: number | undefined) {\n if (nextValue == null) {\n return;\n }\n\n listRef.current?.querySelector<HTMLElement>(`[data-value=\"${nextValue}\"]`)?.focus();\n onSelect(nextValue);\n }\n\n function handleKeyDown(event: KeyboardEvent) {\n if (disabled) {\n return;\n }\n\n const index = enabledValues.indexOf(activeValue);\n\n switch (event.key) {\n case \"ArrowDown\":\n event.preventDefault();\n moveTo(enabledValues[Math.min(index + 1, enabledValues.length - 1)]);\n break;\n case \"ArrowUp\":\n event.preventDefault();\n moveTo(enabledValues[Math.max(index - 1, 0)]);\n break;\n case \"Home\":\n event.preventDefault();\n moveTo(enabledValues[0]);\n break;\n case \"End\":\n event.preventDefault();\n moveTo(enabledValues[enabledValues.length - 1]);\n break;\n case \"ArrowRight\":\n event.preventDefault();\n onHorizontal(1);\n break;\n case \"ArrowLeft\":\n event.preventDefault();\n onHorizontal(-1);\n break;\n }\n }\n\n return (\n <div\n ref={listRef}\n role=\"listbox\"\n aria-label={label}\n aria-orientation=\"vertical\"\n tabIndex={-1}\n className=\"flex max-h-40 w-14 flex-col overflow-y-auto\"\n onKeyDown={handleKeyDown}\n >\n {options.map((option) => {\n const isSelected = selected === option.value;\n\n return (\n <button\n key={option.value}\n type=\"button\"\n role=\"option\"\n aria-selected={isSelected}\n aria-label={`${label} ${option.label}`}\n data-value={option.value}\n data-active={activeValue === option.value}\n disabled={disabled || option.disabled}\n tabIndex={activeValue === option.value ? 0 : -1}\n onClick={() => onSelect(option.value)}\n className={cn(\n \"shrink-0 rounded-lt-sm px-2 py-1 text-sm text-lt-fg hover:bg-lt-muted\",\n isSelected && \"bg-lt-primary text-lt-primary-fg\",\n (disabled || option.disabled) && \"cursor-not-allowed opacity-40\",\n )}\n >\n {option.label}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { numericValue } from "./numeric.js";
|
|
2
|
+
//#region resources/js/format/number.ts
|
|
3
|
+
function formatNumber(value, format, locale) {
|
|
4
|
+
const number = numericValue(value);
|
|
5
|
+
if (number === null) return String(value ?? "");
|
|
6
|
+
const options = {
|
|
7
|
+
notation: format.notation,
|
|
8
|
+
minimumFractionDigits: format.minimumFractionDigits ?? void 0,
|
|
9
|
+
maximumFractionDigits: format.maximumFractionDigits ?? void 0
|
|
10
|
+
};
|
|
11
|
+
if (format.currency) {
|
|
12
|
+
options.style = "currency";
|
|
13
|
+
options.currency = format.currency;
|
|
14
|
+
} else if (format.unit) {
|
|
15
|
+
options.style = "unit";
|
|
16
|
+
options.unit = format.unit;
|
|
17
|
+
}
|
|
18
|
+
return new Intl.NumberFormat(locale, options).format(number);
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { formatNumber };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=number.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number.js","names":[],"sources":["../../resources/js/format/number.ts"],"sourcesContent":["import type { NumberFormat } from \"@lattice-php/lattice/types\";\nimport { numericValue } from \"./numeric\";\n\nexport function formatNumber(value: unknown, format: NumberFormat, locale: string): string {\n const number = numericValue(value);\n\n if (number === null) {\n return String(value ?? \"\");\n }\n\n const options: Intl.NumberFormatOptions = {\n notation: format.notation as Intl.NumberFormatOptions[\"notation\"],\n minimumFractionDigits: format.minimumFractionDigits ?? undefined,\n maximumFractionDigits: format.maximumFractionDigits ?? undefined,\n };\n\n if (format.currency) {\n options.style = \"currency\";\n options.currency = format.currency;\n } else if (format.unit) {\n options.style = \"unit\";\n options.unit = format.unit;\n }\n\n return new Intl.NumberFormat(locale, options).format(number);\n}\n"],"mappings":";;AAGA,SAAgB,aAAa,OAAgB,QAAsB,QAAwB;CACzF,MAAM,SAAS,aAAa,KAAK;CAEjC,IAAI,WAAW,MACb,OAAO,OAAO,SAAS,EAAE;CAG3B,MAAM,UAAoC;EACxC,UAAU,OAAO;EACjB,uBAAuB,OAAO,yBAAyB,KAAA;EACvD,uBAAuB,OAAO,yBAAyB,KAAA;CACzD;CAEA,IAAI,OAAO,UAAU;EACnB,QAAQ,QAAQ;EAChB,QAAQ,WAAW,OAAO;CAC5B,OAAO,IAAI,OAAO,MAAM;EACtB,QAAQ,QAAQ;EAChB,QAAQ,OAAO,OAAO;CACxB;CAEA,OAAO,IAAI,KAAK,aAAa,QAAQ,OAAO,EAAE,OAAO,MAAM;AAC7D"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region resources/js/
|
|
1
|
+
//#region resources/js/format/numeric.ts
|
|
2
2
|
function numericValue(value) {
|
|
3
3
|
const number = typeof value === "number" ? value : Number(value);
|
|
4
4
|
return value !== null && value !== void 0 && value !== "" && !Number.isNaN(number) ? number : null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"numeric.js","names":[],"sources":["../../resources/js/format/numeric.ts"],"sourcesContent":["export function numericValue(value: unknown): number | null {\n const number = typeof value === \"number\" ? value : Number(value);\n\n return value !== null && value !== undefined && value !== \"\" && !Number.isNaN(number)\n ? number\n : null;\n}\n"],"mappings":";AAAA,SAAgB,aAAa,OAA+B;CAC1D,MAAM,SAAS,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;CAE/D,OAAO,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,MAAM,CAAC,OAAO,MAAM,MAAM,IAChF,SACA;AACN"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { formatDateValue } from "../table/format.js";
|
|
2
|
+
import { formatNumber } from "./number.js";
|
|
3
|
+
//#region resources/js/format/value.ts
|
|
4
|
+
function isDateFormat(format) {
|
|
5
|
+
return format.kind === "date";
|
|
6
|
+
}
|
|
7
|
+
function formatValue(value, format, ctx) {
|
|
8
|
+
if (format === null) return String(value ?? "");
|
|
9
|
+
return isDateFormat(format) ? formatDateValue(value, format, {
|
|
10
|
+
locale: ctx.locale,
|
|
11
|
+
timeZone: ctx.timezone
|
|
12
|
+
}) : formatNumber(value, format, ctx.locale);
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { formatValue };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=value.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"value.js","names":[],"sources":["../../resources/js/format/value.ts"],"sourcesContent":["import type { DateFormat, NumberFormat } from \"@lattice-php/lattice/types\";\nimport { formatDateValue } from \"../table/format\";\nimport { formatNumber } from \"./number\";\n\nexport type Format = NumberFormat | DateFormat;\n\nfunction isDateFormat(format: Format): format is DateFormat {\n return format.kind === \"date\";\n}\n\nexport function formatValue(\n value: unknown,\n format: Format | null,\n ctx: { locale: string; timezone: string },\n): string {\n if (format === null) {\n return String(value ?? \"\");\n }\n\n return isDateFormat(format)\n ? formatDateValue(value, format, { locale: ctx.locale, timeZone: ctx.timezone })\n : formatNumber(value, format, ctx.locale);\n}\n"],"mappings":";;;AAMA,SAAS,aAAa,QAAsC;CAC1D,OAAO,OAAO,SAAS;AACzB;AAEA,SAAgB,YACd,OACA,QACA,KACQ;CACR,IAAI,WAAW,MACb,OAAO,OAAO,SAAS,EAAE;CAG3B,OAAO,aAAa,MAAM,IACtB,gBAAgB,OAAO,QAAQ;EAAE,QAAQ,IAAI;EAAQ,UAAU,IAAI;CAAS,CAAC,IAC7E,aAAa,OAAO,QAAQ,IAAI,MAAM;AAC5C"}
|
package/dist/index.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export type { ButtonVariant } from './core/components/button';
|
|
|
25
25
|
export type { ReloadComponentEvent } from './events/event-names';
|
|
26
26
|
export type { ComponentProps, KnownPageContainer, LayoutPayload, Node, NodeOfType, NodeProps, NodeType, PageContainer, PageBreadcrumb, PagePayload, PropsOf, RendererComponent, RendererComponentModule, RendererComponentProps, Schema, UnknownComponent, WireNode, } from './core/types';
|
|
27
27
|
export { RealtimeListeners } from './realtime/listeners';
|
|
28
|
-
export type { ChannelVisibility, ListenerPayload } from './types/generated';
|
|
28
|
+
export type { ChannelVisibility, DateFormat, ListenerPayload, NumberFormat, } from './types/generated';
|
|
29
29
|
export { columnCell } from './table/registry';
|
|
30
30
|
export type { ColumnCellArgs, ColumnCellComponent, ColumnRegistry } from './table/registry';
|
|
31
31
|
export type { ColumnProps, ColumnPropsOf } from './table/types';
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import { useLocale } from "../../../i18n/locale.js";
|
|
2
2
|
import { formatCell } from "../../format.js";
|
|
3
|
-
import { numericValue } from "
|
|
3
|
+
import { numericValue } from "../../../format/numeric.js";
|
|
4
|
+
import { formatNumber } from "../../../format/number.js";
|
|
4
5
|
import { jsx } from "react/jsx-runtime";
|
|
5
6
|
//#region resources/js/table/components/cells/money-cell.tsx
|
|
6
7
|
var MoneyCell = ({ column, props, row, value }) => {
|
|
7
8
|
const { locale } = useLocale();
|
|
8
|
-
|
|
9
|
-
if (number === null) return /* @__PURE__ */ jsx("span", { children: formatCell(value, column) });
|
|
9
|
+
if (numericValue(value) === null) return /* @__PURE__ */ jsx("span", { children: formatCell(value, column) });
|
|
10
10
|
const rawCode = props.currencyField ? row[props.currencyField] : void 0;
|
|
11
|
-
const code = props.currency ?? (typeof rawCode === "string" ? rawCode :
|
|
12
|
-
const fractionDigits = {
|
|
13
|
-
minimumFractionDigits: props.minimumFractionDigits ?? void 0,
|
|
14
|
-
maximumFractionDigits: props.maximumFractionDigits ?? void 0
|
|
15
|
-
};
|
|
11
|
+
const code = props.currency ?? (typeof rawCode === "string" ? rawCode : null);
|
|
16
12
|
return /* @__PURE__ */ jsx("span", {
|
|
17
13
|
className: "tabular-nums",
|
|
18
|
-
children:
|
|
19
|
-
|
|
14
|
+
children: formatNumber(value, {
|
|
15
|
+
kind: "number",
|
|
16
|
+
notation: "standard",
|
|
17
|
+
minimumFractionDigits: props.minimumFractionDigits,
|
|
18
|
+
maximumFractionDigits: props.maximumFractionDigits,
|
|
20
19
|
currency: code,
|
|
21
|
-
|
|
22
|
-
}
|
|
20
|
+
unit: null
|
|
21
|
+
}, locale)
|
|
23
22
|
});
|
|
24
23
|
};
|
|
25
24
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"money-cell.js","names":[],"sources":["../../../../resources/js/table/components/cells/money-cell.tsx"],"sourcesContent":["import { useLocale } from \"@lattice-php/lattice/i18n\";\nimport {
|
|
1
|
+
{"version":3,"file":"money-cell.js","names":[],"sources":["../../../../resources/js/table/components/cells/money-cell.tsx"],"sourcesContent":["import { useLocale } from \"@lattice-php/lattice/i18n\";\nimport { formatNumber } from \"../../../format/number\";\nimport { numericValue } from \"../../../format/numeric\";\nimport { formatCell } from \"../../format\";\nimport type { ColumnCellComponent } from \"../../registry\";\n\nexport const MoneyCell: ColumnCellComponent<\"column.money\"> = ({ column, props, row, value }) => {\n const { locale } = useLocale();\n\n if (numericValue(value) === null) {\n return <span>{formatCell(value, column)}</span>;\n }\n\n const rawCode = props.currencyField ? row[props.currencyField] : undefined;\n const code = props.currency ?? (typeof rawCode === \"string\" ? rawCode : null);\n\n const text = formatNumber(\n value,\n {\n kind: \"number\",\n notation: \"standard\",\n minimumFractionDigits: props.minimumFractionDigits,\n maximumFractionDigits: props.maximumFractionDigits,\n currency: code,\n unit: null,\n },\n locale,\n );\n\n return <span className=\"tabular-nums\">{text}</span>;\n};\n"],"mappings":";;;;;;AAMA,IAAa,aAAkD,EAAE,QAAQ,OAAO,KAAK,YAAY;CAC/F,MAAM,EAAE,WAAW,UAAU;CAE7B,IAAI,aAAa,KAAK,MAAM,MAC1B,OAAO,oBAAC,QAAD,EAAA,UAAO,WAAW,OAAO,MAAM,EAAQ,CAAA;CAGhD,MAAM,UAAU,MAAM,gBAAgB,IAAI,MAAM,iBAAiB,KAAA;CACjE,MAAM,OAAO,MAAM,aAAa,OAAO,YAAY,WAAW,UAAU;CAexE,OAAO,oBAAC,QAAD;EAAM,WAAU;YAbV,aACX,OACA;GACE,MAAM;GACN,UAAU;GACV,uBAAuB,MAAM;GAC7B,uBAAuB,MAAM;GAC7B,UAAU;GACV,MAAM;EACR,GACA,MAGqC;CAAW,CAAA;AACpD"}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { useLocale } from "../../../i18n/locale.js";
|
|
2
2
|
import { formatCell } from "../../format.js";
|
|
3
|
-
import { numericValue } from "
|
|
3
|
+
import { numericValue } from "../../../format/numeric.js";
|
|
4
|
+
import { formatNumber } from "../../../format/number.js";
|
|
4
5
|
import { jsx } from "react/jsx-runtime";
|
|
5
6
|
//#region resources/js/table/components/cells/number-cell.tsx
|
|
6
7
|
var NumberCell = ({ column, props, value }) => {
|
|
7
8
|
const { locale } = useLocale();
|
|
8
|
-
|
|
9
|
-
if (number === null) return /* @__PURE__ */ jsx("span", { children: formatCell(value, column) });
|
|
9
|
+
if (numericValue(value) === null) return /* @__PURE__ */ jsx("span", { children: formatCell(value, column) });
|
|
10
10
|
return /* @__PURE__ */ jsx("span", {
|
|
11
11
|
className: "tabular-nums",
|
|
12
|
-
children:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
})
|
|
12
|
+
children: formatNumber(value, {
|
|
13
|
+
kind: "number",
|
|
14
|
+
notation: props.compact ? "compact" : "standard",
|
|
15
|
+
minimumFractionDigits: props.minimumFractionDigits,
|
|
16
|
+
maximumFractionDigits: props.maximumFractionDigits,
|
|
17
|
+
currency: null,
|
|
18
|
+
unit: props.unit
|
|
19
|
+
}, locale)
|
|
20
20
|
});
|
|
21
21
|
};
|
|
22
22
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-cell.js","names":[],"sources":["../../../../resources/js/table/components/cells/number-cell.tsx"],"sourcesContent":["import { useLocale } from \"@lattice-php/lattice/i18n\";\nimport {
|
|
1
|
+
{"version":3,"file":"number-cell.js","names":[],"sources":["../../../../resources/js/table/components/cells/number-cell.tsx"],"sourcesContent":["import { useLocale } from \"@lattice-php/lattice/i18n\";\nimport { formatNumber } from \"../../../format/number\";\nimport { numericValue } from \"../../../format/numeric\";\nimport { formatCell } from \"../../format\";\nimport type { ColumnCellComponent } from \"../../registry\";\n\nexport const NumberCell: ColumnCellComponent<\"column.number\"> = ({ column, props, value }) => {\n const { locale } = useLocale();\n\n if (numericValue(value) === null) {\n return <span>{formatCell(value, column)}</span>;\n }\n\n const text = formatNumber(\n value,\n {\n kind: \"number\",\n notation: props.compact ? \"compact\" : \"standard\",\n minimumFractionDigits: props.minimumFractionDigits,\n maximumFractionDigits: props.maximumFractionDigits,\n currency: null,\n unit: props.unit,\n },\n locale,\n );\n\n return <span className=\"tabular-nums\">{text}</span>;\n};\n"],"mappings":";;;;;;AAMA,IAAa,cAAoD,EAAE,QAAQ,OAAO,YAAY;CAC5F,MAAM,EAAE,WAAW,UAAU;CAE7B,IAAI,aAAa,KAAK,MAAM,MAC1B,OAAO,oBAAC,QAAD,EAAA,UAAO,WAAW,OAAO,MAAM,EAAQ,CAAA;CAgBhD,OAAO,oBAAC,QAAD;EAAM,WAAU;YAbV,aACX,OACA;GACE,MAAM;GACN,UAAU,MAAM,UAAU,YAAY;GACtC,uBAAuB,MAAM;GAC7B,uBAAuB,MAAM;GAC7B,UAAU;GACV,MAAM,MAAM;EACd,GACA,MAGqC;CAAW,CAAA;AACpD"}
|
package/dist/table/format.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export type FormatOptions = {
|
|
|
6
6
|
export type DateConfig = {
|
|
7
7
|
dateStyle: string | null;
|
|
8
8
|
timeStyle: string | null;
|
|
9
|
+
month?: string | null;
|
|
10
|
+
year?: string | null;
|
|
9
11
|
};
|
|
10
12
|
export declare function formatCell(value: unknown, column?: TableColumn, options?: FormatOptions): string;
|
|
11
13
|
export declare function formatDateValue(value: unknown, date: DateConfig, options?: FormatOptions): string;
|
package/dist/table/format.js
CHANGED
|
@@ -12,6 +12,8 @@ function formatDateValue(value, date, options) {
|
|
|
12
12
|
const intl = { timeZone: options?.timeZone };
|
|
13
13
|
if (date.dateStyle) intl.dateStyle = date.dateStyle;
|
|
14
14
|
if (date.timeStyle) intl.timeStyle = date.timeStyle;
|
|
15
|
+
if (date.month) intl.month = date.month;
|
|
16
|
+
if (date.year) intl.year = date.year;
|
|
15
17
|
return new Intl.DateTimeFormat(options?.locale, intl).format(parsed);
|
|
16
18
|
}
|
|
17
19
|
function preciseDateTime(value, options) {
|
package/dist/table/format.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","names":[],"sources":["../../resources/js/table/format.ts"],"sourcesContent":["import type { ColumnPropsOf, TableColumn, TableRow } from \"./types\";\n\nexport type FormatOptions = {\n locale?: string;\n timeZone?: string;\n};\n\nexport type DateConfig = {
|
|
1
|
+
{"version":3,"file":"format.js","names":[],"sources":["../../resources/js/table/format.ts"],"sourcesContent":["import type { ColumnPropsOf, TableColumn, TableRow } from \"./types\";\n\nexport type FormatOptions = {\n locale?: string;\n timeZone?: string;\n};\n\nexport type DateConfig = {\n dateStyle: string | null;\n timeStyle: string | null;\n month?: string | null;\n year?: string | null;\n};\n\nexport function formatCell(value: unknown, column?: TableColumn, options?: FormatOptions): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n\n const date = (column?.props as ColumnPropsOf<\"column.text\"> | null)?.date;\n\n if (date) {\n return formatDateValue(value, date, options);\n }\n\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n\n return JSON.stringify(value);\n}\n\nexport function formatDateValue(value: unknown, date: DateConfig, options?: FormatOptions): string {\n const parsed = new Date(String(value));\n\n if (Number.isNaN(parsed.getTime())) {\n return String(value ?? \"\");\n }\n\n const intl: Intl.DateTimeFormatOptions = { timeZone: options?.timeZone };\n\n if (date.dateStyle) {\n intl.dateStyle = date.dateStyle as Intl.DateTimeFormatOptions[\"dateStyle\"];\n }\n\n if (date.timeStyle) {\n intl.timeStyle = date.timeStyle as Intl.DateTimeFormatOptions[\"timeStyle\"];\n }\n\n if (date.month) {\n intl.month = date.month as Intl.DateTimeFormatOptions[\"month\"];\n }\n\n if (date.year) {\n intl.year = date.year as Intl.DateTimeFormatOptions[\"year\"];\n }\n\n return new Intl.DateTimeFormat(options?.locale, intl).format(parsed);\n}\n\nexport function preciseDateTime(value: unknown, options?: FormatOptions): string {\n const date = new Date(String(value));\n\n if (Number.isNaN(date.getTime())) {\n return \"\";\n }\n\n const formatted = new Intl.DateTimeFormat(options?.locale, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n timeZone: options?.timeZone,\n timeZoneName: \"short\",\n }).format(date);\n\n return options?.timeZone ? `${formatted} (${options.timeZone})` : formatted;\n}\n\nexport function resolveLink(column: TableColumn, row: TableRow, value: unknown): string | null {\n const link = (column.props as ColumnPropsOf<\"column.text\"> | null)?.link;\n\n if (!link) {\n return null;\n }\n\n const href = link.href ?? String(value ?? \"\");\n\n if (href === \"\") {\n return null;\n }\n\n return href.replace(/\\{([^}]+)\\}/g, (_, key: string) => {\n if (key === \"value\") {\n return encodeURIComponent(String(value ?? \"\"));\n }\n\n return encodeURIComponent(String(row[key] ?? \"\"));\n });\n}\n"],"mappings":";AAcA,SAAgB,WAAW,OAAgB,QAAsB,SAAiC;CAChG,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,OAAO;CAGT,MAAM,QAAQ,QAAQ,QAA+C;CAErE,IAAI,MACF,OAAO,gBAAgB,OAAO,MAAM,OAAO;CAG7C,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,WAC7E,OAAO,OAAO,KAAK;CAGrB,OAAO,KAAK,UAAU,KAAK;AAC7B;AAEA,SAAgB,gBAAgB,OAAgB,MAAkB,SAAiC;CACjG,MAAM,SAAS,IAAI,KAAK,OAAO,KAAK,CAAC;CAErC,IAAI,OAAO,MAAM,OAAO,QAAQ,CAAC,GAC/B,OAAO,OAAO,SAAS,EAAE;CAG3B,MAAM,OAAmC,EAAE,UAAU,SAAS,SAAS;CAEvE,IAAI,KAAK,WACP,KAAK,YAAY,KAAK;CAGxB,IAAI,KAAK,WACP,KAAK,YAAY,KAAK;CAGxB,IAAI,KAAK,OACP,KAAK,QAAQ,KAAK;CAGpB,IAAI,KAAK,MACP,KAAK,OAAO,KAAK;CAGnB,OAAO,IAAI,KAAK,eAAe,SAAS,QAAQ,IAAI,EAAE,OAAO,MAAM;AACrE;AAEA,SAAgB,gBAAgB,OAAgB,SAAiC;CAC/E,MAAM,OAAO,IAAI,KAAK,OAAO,KAAK,CAAC;CAEnC,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAC7B,OAAO;CAGT,MAAM,YAAY,IAAI,KAAK,eAAe,SAAS,QAAQ;EACzD,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,UAAU,SAAS;EACnB,cAAc;CAChB,CAAC,EAAE,OAAO,IAAI;CAEd,OAAO,SAAS,WAAW,GAAG,UAAU,IAAI,QAAQ,SAAS,KAAK;AACpE;AAEA,SAAgB,YAAY,QAAqB,KAAe,OAA+B;CAC7F,MAAM,OAAQ,OAAO,OAA+C;CAEpE,IAAI,CAAC,MACH,OAAO;CAGT,MAAM,OAAO,KAAK,QAAQ,OAAO,SAAS,EAAE;CAE5C,IAAI,SAAS,IACX,OAAO;CAGT,OAAO,KAAK,QAAQ,iBAAiB,GAAG,QAAgB;EACtD,IAAI,QAAQ,SACV,OAAO,mBAAmB,OAAO,SAAS,EAAE,CAAC;EAG/C,OAAO,mBAAmB,OAAO,IAAI,QAAQ,EAAE,CAAC;CAClD,CAAC;AACH"}
|
|
@@ -127,6 +127,7 @@ export type Card = {
|
|
|
127
127
|
};
|
|
128
128
|
export type ChannelVisibility = "public" | "private" | "presence";
|
|
129
129
|
export type Chart = {
|
|
130
|
+
categoryFormat: NumberFormat | DateFormat | null;
|
|
130
131
|
categoryKey: string | null;
|
|
131
132
|
data: Record<string, unknown>[];
|
|
132
133
|
description: string | null;
|
|
@@ -136,6 +137,7 @@ export type Chart = {
|
|
|
136
137
|
series: ChartSeries[];
|
|
137
138
|
title: string | null;
|
|
138
139
|
tooltip: boolean;
|
|
140
|
+
valueFormat: NumberFormat | null;
|
|
139
141
|
xAxis: boolean;
|
|
140
142
|
yAxis: boolean;
|
|
141
143
|
};
|
|
@@ -384,6 +386,13 @@ export type DataList = {
|
|
|
384
386
|
emptyLabel: string | null;
|
|
385
387
|
remote: RemoteAccess | null;
|
|
386
388
|
};
|
|
389
|
+
export type DateFormat = {
|
|
390
|
+
kind: string;
|
|
391
|
+
dateStyle: string | null;
|
|
392
|
+
timeStyle: string | null;
|
|
393
|
+
month: string | null;
|
|
394
|
+
year: string | null;
|
|
395
|
+
};
|
|
387
396
|
export type DateInput = {
|
|
388
397
|
autoFocus: boolean;
|
|
389
398
|
columnWidth: ColumnWidth;
|
|
@@ -774,10 +783,19 @@ export type MoneyColumn = {
|
|
|
774
783
|
export type Node = FormNode | CoreNode | ActionNode | FragmentNode | RemoteNode | TableNode | LayoutNode | ChatNode;
|
|
775
784
|
export type NodeType = Node["type"];
|
|
776
785
|
export type NumberColumn = {
|
|
786
|
+
compact: boolean;
|
|
777
787
|
maximumFractionDigits: number | null;
|
|
778
788
|
minimumFractionDigits: number | null;
|
|
779
789
|
unit: NumberFormatUnit | null;
|
|
780
790
|
};
|
|
791
|
+
export type NumberFormat = {
|
|
792
|
+
kind: string;
|
|
793
|
+
notation: string;
|
|
794
|
+
minimumFractionDigits: number | null;
|
|
795
|
+
maximumFractionDigits: number | null;
|
|
796
|
+
currency: string | null;
|
|
797
|
+
unit: NumberFormatUnit | null;
|
|
798
|
+
};
|
|
781
799
|
export type NumberFormatUnit = "percent" | "kilogram" | "gram" | "kilometer" | "meter" | "byte" | "kilobyte" | "megabyte" | "gigabyte" | "millisecond" | "second" | "minute" | "hour" | "celsius" | "fahrenheit";
|
|
782
800
|
export type NumberInput = {
|
|
783
801
|
autoFocus: boolean;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { ComponentProps, Node, NodeProps, PageContainer, PageBreadcrumb, PagePayload, PropsOf, RendererComponent, } from '..';
|
|
1
|
+
export type { ComponentProps, DateFormat, Node, NodeProps, NumberFormat, PageContainer, PageBreadcrumb, PagePayload, PropsOf, RendererComponent, } from '..';
|
|
2
2
|
export type { Method } from '@inertiajs/core';
|
package/package.json
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.18.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-clock"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<circle cx="12" cy="12" r="10" />
|
|
15
|
+
<path d="M12 6v6l4 2" />
|
|
16
|
+
</svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"numeric.js","names":[],"sources":["../../../../resources/js/table/components/cells/numeric.ts"],"sourcesContent":["export function numericValue(value: unknown): number | null {\n const number = typeof value === \"number\" ? value : Number(value);\n\n return value !== null && value !== undefined && value !== \"\" && !Number.isNaN(number)\n ? number\n : null;\n}\n"],"mappings":";AAAA,SAAgB,aAAa,OAA+B;CAC1D,MAAM,SAAS,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;CAE/D,OAAO,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,MAAM,CAAC,OAAO,MAAM,MAAM,IAChF,SACA;AACN"}
|
|
File without changes
|