@vuu-ui/vuu-utils 3.5.0-beta.1 → 3.5.0-beta.2

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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.5.0-beta.1",
2
+ "version": "3.5.0-beta.2",
3
3
  "author": "heswell",
4
4
  "main": "./src/index.js",
5
5
  "license": "Apache-2.0",
@@ -15,14 +15,14 @@
15
15
  },
16
16
  "devDependencies": {
17
17
  "@dnd-kit/react": "^0.1.21",
18
- "@vuu-ui/vuu-data-types": "3.5.0-beta.1",
19
- "@vuu-ui/vuu-table-types": "3.5.0-beta.1",
20
- "@vuu-ui/vuu-filter-types": "3.5.0-beta.1",
21
- "@vuu-ui/vuu-protocol-types": "3.5.0-beta.1"
18
+ "@vuu-ui/vuu-data-types": "3.5.0-beta.2",
19
+ "@vuu-ui/vuu-table-types": "3.5.0-beta.2",
20
+ "@vuu-ui/vuu-filter-types": "3.5.0-beta.2",
21
+ "@vuu-ui/vuu-protocol-types": "3.5.0-beta.2"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "@internationalized/date": "^3.0.0",
25
- "@vuu-ui/vuu-filter-parser": "3.5.0-beta.1",
25
+ "@vuu-ui/vuu-filter-parser": "3.5.0-beta.2",
26
26
  "clsx": "^2.0.0",
27
27
  "react": "^19.2.3",
28
28
  "react-dom": "^19.2.3"
@@ -1,3 +1,4 @@
1
+ import { getTemporalInfo } from "./temporal.js";
1
2
  import { isDateTimePattern } from "./types.js";
2
3
  const defaultPatternsByType = {
3
4
  time: "hh:mm:ss",
@@ -26,4 +27,19 @@ function dateTimePattern(type, kind = kindFromType(type)) {
26
27
  }
27
28
  return kind ? defaultDateTimePatternByKind[kind] : fallbackDateTimePattern;
28
29
  }
29
- export { dateTimePattern, defaultDateTimePatternByKind, defaultPatternsByType, fallbackDateTimePattern };
30
+ const withDateTimePattern = (descriptor, pattern)=>{
31
+ if (void 0 === pattern || void 0 === getTemporalInfo(descriptor)) return descriptor;
32
+ const name = pattern.date && pattern.time ? "date/time" : pattern.date ? "date" : "time";
33
+ const formatting = "object" == typeof descriptor.type ? descriptor.type.formatting : void 0;
34
+ return {
35
+ ...descriptor,
36
+ type: {
37
+ name,
38
+ formatting: {
39
+ ...formatting,
40
+ pattern
41
+ }
42
+ }
43
+ };
44
+ };
45
+ export { dateTimePattern, defaultDateTimePatternByKind, defaultPatternsByType, fallbackDateTimePattern, withDateTimePattern };
package/src/date/index.js CHANGED
@@ -2,6 +2,6 @@ export * from "./formatter.js";
2
2
  export * from "./temporal.js";
3
3
  export * from "./time-zone.js";
4
4
  export { Time, asTimeString, asTimeStringMillis, decrementTimeUnitValue, incrementTimeUnitValue, isValidTimeString, isValidTimeStringMillis, normaliseTimeString, toCalendarDate, updateTimeString, zeroTime, zeroTimeMillis, zeroTimeUnit } from "./date-utils.js";
5
- export { dateTimePattern, defaultDateTimePatternByKind, defaultPatternsByType, fallbackDateTimePattern } from "./dateTimePattern.js";
5
+ export { dateTimePattern, defaultDateTimePatternByKind, defaultPatternsByType, fallbackDateTimePattern, withDateTimePattern } from "./dateTimePattern.js";
6
6
  export { dateTimeLabelByType, isDatePattern, isTimePattern, supportedDateTimePatterns } from "./types.js";
7
7
  export { EpochTimestamp } from "./EpochTimestamp.js";
@@ -51,15 +51,19 @@ function singleValueFilterAsQuery(f, opts) {
51
51
  }
52
52
  const ONE_DAY_IN_MILLIS = 86400000;
53
53
  const timeOfDayPattern = /^\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?$/;
54
+ const NANOS_PER_SECOND = 1000000000;
55
+ const NANOS_PER_MILLI = 1000000;
54
56
  const resolveTemporalFilterValue = (value, { encoding, kind, timeZone }, date = "today")=>{
55
57
  if ("string" == typeof value && timeOfDayPattern.test(value)) {
56
58
  const [hms, fraction = ""] = value.split(".");
57
59
  const millis = timeOfDayToEpochMillis(hms, date, timeZone);
58
60
  const nanos = parseInt(fraction.padEnd(9, "0"), 10);
59
- const ts = EpochTimestamp.fromMillis(millis + Math.floor(nanos / 1000000), "epochNanos" === encoding ? nanos % 1000000 : 0);
61
+ const ts = EpochTimestamp.fromMillis(millis + Math.floor(nanos / NANOS_PER_MILLI), "epochNanos" === encoding ? nanos % NANOS_PER_MILLI : 0);
62
+ const period = 10 ** (9 - Math.min(fraction.length, 9));
63
+ const unit = "epochNanos" === encoding ? 1 : NANOS_PER_MILLI;
60
64
  return [
61
65
  ts,
62
- fraction ? "exact" : "second"
66
+ period > unit ? period : "exact"
63
67
  ];
64
68
  }
65
69
  const ts = EpochTimestamp.fromWire(value instanceof ScaledDecimal ? value.asLong : value, encoding);
@@ -74,7 +78,7 @@ const resolveTemporalFilterValue = (value, { encoding, kind, timeZone }, date =
74
78
  ];
75
79
  if ("time" === kind && 0 === ts.subMilliNanos && ts.epochMillis % 1000 === 0) return [
76
80
  ts,
77
- "second"
81
+ NANOS_PER_SECOND
78
82
  ];
79
83
  return [
80
84
  ts,
@@ -92,7 +96,7 @@ function temporalFilterAsQuery(f, temporalInfo, options) {
92
96
  EpochTimestamp.fromMillis(startOfNextDay(ts.epochMillis, timeZone))
93
97
  ] : [
94
98
  ts,
95
- EpochTimestamp.fromMillis(ts.epochMillis + 1000)
99
+ EpochTimestamp.fromNanos(ts.epochNanos + BigInt(granularity))
96
100
  ];
97
101
  const from = start.toLiteral(encoding);
98
102
  const to = end.toLiteral(encoding);
@@ -1,2 +1,3 @@
1
1
  export * from "./filter-utils.js";
2
2
  export * from "./filterAsQuery.js";
3
+ export * from "./temporal-cell-filter.js";
@@ -0,0 +1,60 @@
1
+ import { dateTimePattern, defaultPatternsByType } from "../date/dateTimePattern.js";
2
+ import { EpochTimestamp } from "../date/EpochTimestamp.js";
3
+ import { formatTimestamp } from "../date/formatter.js";
4
+ import { getTemporalInfo } from "../date/temporal.js";
5
+ import { startOfDay } from "../date/time-zone.js";
6
+ import { getFractionalSecondDigits, temporalFormatter } from "../formatting-utils.js";
7
+ const pad2 = (n)=>`${n}`.padStart(2, "0");
8
+ const getTemporalCellFilter = (column, value, dateOnly = false)=>{
9
+ const temporalInfo = getTemporalInfo(column);
10
+ if (void 0 === temporalInfo) return;
11
+ const { encoding, kind, timeZone } = temporalInfo;
12
+ const ts = EpochTimestamp.fromWire(value, encoding);
13
+ if (void 0 === ts) return;
14
+ const pattern = dateTimePattern(column.type, kind);
15
+ if ("date" === kind || dateOnly && "datetime" === kind || void 0 === pattern.time) {
16
+ const { date = defaultPatternsByType.date } = pattern;
17
+ const start = EpochTimestamp.fromMillis(startOfDay(ts.epochMillis, timeZone));
18
+ return {
19
+ label: formatTimestamp({
20
+ date
21
+ }, {
22
+ timeZone
23
+ })(ts),
24
+ op: "=",
25
+ value: `${start.toWire(encoding)}`
26
+ };
27
+ }
28
+ const digits = Math.max(0, Math.min(9, getFractionalSecondDigits(column, temporalInfo)));
29
+ const unit = "epochNanos" === encoding ? 1n : 1000000n;
30
+ const period = 10n ** BigInt(9 - digits);
31
+ const startNanos = ts.epochNanos - ts.epochNanos % period;
32
+ const start = EpochTimestamp.fromNanos(startNanos);
33
+ const label = temporalFormatter(column, temporalInfo)(start.toWire(encoding));
34
+ if ("time" === kind) {
35
+ const { hour, minute, second } = ts.getFields(timeZone);
36
+ const fraction = ts.fractionalSecond(digits);
37
+ return {
38
+ label,
39
+ op: "=",
40
+ value: `${pad2(hour)}:${pad2(minute)}:${pad2(second)}${fraction ? `.${fraction}` : ""}`
41
+ };
42
+ }
43
+ {
44
+ if (period <= unit) return {
45
+ label,
46
+ op: "=",
47
+ value: `${ts.toWire(encoding)}`
48
+ };
49
+ const end = EpochTimestamp.fromNanos(startNanos + period - unit);
50
+ return {
51
+ label,
52
+ op: "between-inclusive",
53
+ value: [
54
+ `${start.toWire(encoding)}`,
55
+ `${end.toWire(encoding)}`
56
+ ]
57
+ };
58
+ }
59
+ };
60
+ export { getTemporalCellFilter };
@@ -8,12 +8,18 @@ import { roundDecimal, roundScaledDecimal } from "./round-decimal.js";
8
8
  import { isNumericType } from "./protocol-message-utils.js";
9
9
  const DEFAULT_NUMERIC_FORMAT = {};
10
10
  const defaultValueFormatter = (value)=>null == value ? "" : "string" == typeof value ? value : value.toString();
11
+ const getFractionalSecondDigits = (column, temporalInfo = getTemporalInfo(column))=>{
12
+ if (void 0 === temporalInfo) return 0;
13
+ const pattern = dateTimePattern(column.type, temporalInfo.kind);
14
+ const formatting = isTypeDescriptor(column.type) ? column.type.formatting : void 0;
15
+ return formatting?.fractionalSecondDigits ?? ("hh:mm:ss.ms" === pattern.time ? 3 : "ns" === temporalInfo.precision && pattern.time ? 9 : 0);
16
+ };
11
17
  const temporalFormatter = (column, temporalInfo = getTemporalInfo(column))=>{
12
18
  if (void 0 === temporalInfo) return defaultValueFormatter;
13
- const { encoding, kind, precision, timeZone } = temporalInfo;
19
+ const { encoding, kind, timeZone } = temporalInfo;
14
20
  const formatting = isTypeDescriptor(column.type) ? column.type.formatting ?? DEFAULT_NUMERIC_FORMAT : DEFAULT_NUMERIC_FORMAT;
15
21
  const pattern = dateTimePattern(column.type, kind);
16
- const fractionalSecondDigits = formatting.fractionalSecondDigits ?? ("hh:mm:ss.ms" === pattern.time ? 3 : "ns" === precision && pattern.time ? 9 : 0);
22
+ const fractionalSecondDigits = getFractionalSecondDigits(column, temporalInfo);
17
23
  const formatter = formatTimestamp(pattern, {
18
24
  fractionalSecondDigits,
19
25
  locale: formatting.locale,
@@ -70,4 +76,4 @@ const getValueFormatter = (column)=>{
70
76
  return defaultValueFormatter;
71
77
  };
72
78
  const lowerCase = (str)=>str.toLowerCase();
73
- export { defaultValueFormatter, formatTemporalFilterValue, getValueFormatter, lowerCase, numericFormatter, temporalFormatter };
79
+ export { defaultValueFormatter, formatTemporalFilterValue, getFractionalSecondDigits, getValueFormatter, lowerCase, numericFormatter, temporalFormatter };
@@ -1,5 +1,5 @@
1
- import type { DataValueType, DateTimeDataValueDescriptor } from "@vuu-ui/vuu-data-types";
2
- import type { TemporalKind } from "./temporal";
1
+ import type { DataValueDescriptor, DataValueType, DateTimeDataValueDescriptor } from "@vuu-ui/vuu-data-types";
2
+ import { type TemporalKind } from "./temporal";
3
3
  import { DateTimePattern } from "./types";
4
4
  export declare const defaultPatternsByType: {
5
5
  readonly time: "hh:mm:ss";
@@ -13,3 +13,14 @@ export declare const defaultDateTimePatternByKind: Record<TemporalKind, DateTime
13
13
  * is not provided, it is inferred from type name.
14
14
  */
15
15
  export declare function dateTimePattern(type?: DataValueType | DateTimeDataValueDescriptor["type"], kind?: TemporalKind | undefined): DateTimePattern;
16
+ /**
17
+ * Returns a copy of a temporal descriptor, with its type replaced so that the
18
+ * value is displayed (or edited) using the given pattern. The type name follows
19
+ * the pattern: 'date/time' if both date and time are specified, else 'date' or
20
+ * 'time'. Other formatting (e.g. timeZone, locale) is preserved. The descriptor
21
+ * is returned unchanged if no pattern is provided or the descriptor is not
22
+ * temporal.
23
+ * Use this to present a column differently in different contexts, e.g. a
24
+ * ColumnFilter using a different pattern to the Table.
25
+ */
26
+ export declare const withDateTimePattern: <T extends DataValueDescriptor>(descriptor: T, pattern?: DateTimePattern) => T;
@@ -1,5 +1,5 @@
1
1
  export { asTimeString, asTimeStringMillis, decrementTimeUnitValue, incrementTimeUnitValue, isValidTimeString, isValidTimeStringMillis, normaliseTimeString, Time, toCalendarDate, type DateStringISO, type Hours, type Milliseconds, type Minutes, type Seconds, type TimeString, type TimeStringMillis, type TimeUnit, type TimeUnitValue, updateTimeString, zeroTime, zeroTimeMillis, zeroTimeUnit, } from "./date-utils";
2
- export { dateTimePattern, defaultPatternsByType, fallbackDateTimePattern, } from "./dateTimePattern";
2
+ export { dateTimePattern, defaultPatternsByType, fallbackDateTimePattern, withDateTimePattern, } from "./dateTimePattern";
3
3
  export * from "./formatter";
4
4
  export { dateTimeLabelByType, isDatePattern, isTimePattern, supportedDateTimePatterns, type DatePattern, type DateTimePattern, type TimePattern, } from "./types";
5
5
  export { defaultDateTimePatternByKind } from "./dateTimePattern";
@@ -21,7 +21,10 @@ export interface TemporalFilterAsQueryOptions {
21
21
  * day (in the column time zone), e.g as selected from a DatePicker. The clause
22
22
  * applies to the whole day: '=' becomes a [startOfDay, startOfNextDay) range,
23
23
  * '>' becomes '>= startOfNextDay' etc. Day boundaries are DST safe.
24
- * - second: time of day values (TimeString or whole second 'time' values)
24
+ * - period: time of day values (TimeString or whole second 'time' values). A
25
+ * TimeString represents the period implied by its precision, e.g hh:mm:ss is
26
+ * the whole second, hh:mm:ss.fff the whole millisecond (on a nanosecond
27
+ * column), and is treated like the day granularity above.
25
28
  * - exact: all other values
26
29
  * Values are encoded according to the column encoding (millis or nanos).
27
30
  */
@@ -1,2 +1,3 @@
1
1
  export * from "./filter-utils";
2
2
  export * from "./filterAsQuery";
3
+ export * from "./temporal-cell-filter";
@@ -0,0 +1,24 @@
1
+ import type { DataValueDescriptor } from "@vuu-ui/vuu-data-types";
2
+ export interface TemporalCellFilter {
3
+ /** The value, formatted as displayed */
4
+ label: string;
5
+ op: "=" | "between-inclusive";
6
+ value: string | [string, string];
7
+ }
8
+ /**
9
+ * Describes a filter that matches a temporal value at the precision with which
10
+ * it is displayed by the given column descriptor, e.g. where the descriptor
11
+ * displays milliseconds, the filter matches the whole millisecond.
12
+ * - date (or dateOnly) => the whole day, as a start of day value
13
+ * - time => a time of day (TimeString), truncated to the displayed precision
14
+ * - datetime => a range, unless the displayed precision is that of the encoding
15
+ * The filter is suitable for FilterAggregator.add (with the same descriptor) and
16
+ * matches the value a ColumnFilter with this descriptor would commit.
17
+ *
18
+ * @param column the descriptor of the filter (or table cell) displaying the value
19
+ * @param value a value as received from the server
20
+ * @param dateOnly a 'datetime' value is displayed as a date (e.g. by a
21
+ * DatePicker). Ignored for 'time' descriptors.
22
+ * @returns undefined if value is empty or column is not temporal
23
+ */
24
+ export declare const getTemporalCellFilter: (column: DataValueDescriptor, value: unknown, dateOnly?: boolean) => TemporalCellFilter | undefined;
@@ -1,9 +1,16 @@
1
1
  import { DataValueDescriptor } from "@vuu-ui/vuu-data-types";
2
- import { ColumnDescriptor, ValueFormatter } from "@vuu-ui/vuu-table-types";
2
+ import { ColumnDescriptor, FractionalSecondDigits, ValueFormatter } from "@vuu-ui/vuu-table-types";
3
3
  export type ValueFormatters = {
4
4
  [key: string]: ValueFormatter;
5
5
  };
6
6
  export declare const defaultValueFormatter: (value: unknown) => string;
7
+ /**
8
+ * The number of fractional second digits displayed for a temporal value:
9
+ * type.formatting.fractionalSecondDigits if set, otherwise 3 for an
10
+ * 'hh:mm:ss.ms' time pattern, 9 for a nanosecond column with any other time
11
+ * pattern, else 0.
12
+ */
13
+ export declare const getFractionalSecondDigits: (column: DataValueDescriptor, temporalInfo?: import("./date/temporal").TemporalInfo | undefined) => FractionalSecondDigits;
7
14
  /**
8
15
  * Creates a formatter for a temporal value (see getTemporalInfo). Handles
9
16
  * millisecond and nanosecond encodings, the configured (or default) pattern,