@olenbetong/appframe-ds 1.3.0 → 1.3.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @olenbetong/appframe-ds
2
2
 
3
+ ## 1.3.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [04ff05a]
8
+ - @olenbetong/appframe-core@2.15.1
9
+ - @olenbetong/appframe-data@1.7.4
10
+ - @olenbetong/appframe-react@2.2.4
11
+
12
+ ## 1.3.2
13
+
14
+ ### Patch Changes
15
+
16
+ - 48e6ac8: The filter builder adds the seconds to datetime values, which the datetime input leaves out. SQL Server rejected the value without them with "Conversion failed when converting date and/or time from character string".
17
+ - Updated dependencies [a63831a]
18
+ - @olenbetong/appframe-core@2.15.0
19
+ - @olenbetong/appframe-react@2.2.3
20
+ - @olenbetong/appframe-data@1.7.3
21
+
22
+ ## 1.3.1
23
+
24
+ ### Patch Changes
25
+
26
+ - 2c2ed02: `DataEditToolbar`: the previous/next buttons no longer stack (Designsystemet buttons are `display: flex`, so the navigation group is a flex row now), and the toolbar is a tinted bar with a bottom border in the theme's tokens instead of black-at-12% top and bottom borders, which were invisible in dark mode and doubled up against a tab list above it.
27
+ - c28f772: `InputAdornments`: the hover style is drawn on the wrapper like the focus style (Designsystemet's inner outline, on the whole control), and the inner input no longer shows its own hover outline.
28
+ - Updated dependencies [a4158e8]
29
+ - @olenbetong/appframe-react@2.2.2
30
+
3
31
  ## 1.3.0
4
32
 
5
33
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olenbetong/appframe-ds",
3
- "version": "1.3.0",
3
+ "version": "1.3.3",
4
4
  "description": "Components that bind Designsystemet components to Appframe data objects",
5
5
  "type": "module",
6
6
  "types": "./es/index.d.ts",
@@ -63,17 +63,17 @@
63
63
  "@atlaskit/pragmatic-drag-and-drop": "3.1.0",
64
64
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "2.2.2",
65
65
  "@base-ui/react": "1.8.0",
66
+ "@olenbetong/appframe-core": "2.15.1",
67
+ "@olenbetong/appframe-data": "1.7.4",
68
+ "@olenbetong/appframe-react": "2.2.4",
66
69
  "clsx": "^2.1.1",
67
70
  "dockview-react": "8.3.1",
68
71
  "localforage": "^1.10.0",
69
72
  "mitt": "^3.0.1",
70
73
  "react": "19.3.0",
71
74
  "react-dom": "19.3.0",
72
- "react-window": "2.3.1",
73
- "temporal-polyfill": "1.0.5",
74
- "@olenbetong/appframe-react": "2.2.1",
75
- "@olenbetong/appframe-core": "2.14.0",
76
- "@olenbetong/appframe-data": "1.7.2"
75
+ "react-window": "2.3.3",
76
+ "temporal-polyfill": "1.0.5"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@digdir/designsystemet-react": "1.22.0",
@@ -90,7 +90,7 @@
90
90
  "@types/react": "19.3.0",
91
91
  "@types/react-dom": "19.3.0",
92
92
  "happy-dom": "20.14.5",
93
- "react-error-boundary": "6.1.5",
93
+ "react-error-boundary": "6.1.6",
94
94
  "react-router": "8.4.0",
95
95
  "typescript": "7.0.2"
96
96
  },
@@ -0,0 +1,23 @@
1
+ /*
2
+ * A toolbar bar above a record form: tinted, so it reads as chrome rather than as part of the
3
+ * form below it. Designsystemet buttons are `display: flex`, so every group of them must be a
4
+ * flex row of its own or the buttons stack.
5
+ */
6
+ @layer ds {
7
+ .DataEditToolbar-root {
8
+ display: flex;
9
+ align-items: center;
10
+ gap: var(--ds-size-1);
11
+ min-height: 48px;
12
+ padding-inline: var(--ds-size-2);
13
+ background: var(--ds-color-neutral-background-tinted);
14
+ border-block-end: var(--ds-border-width-default) solid var(--ds-color-neutral-border-subtle);
15
+ }
16
+
17
+ .DataEditToolbar-navigation {
18
+ display: flex;
19
+ align-items: center;
20
+ gap: var(--ds-size-1);
21
+ margin-inline-start: auto;
22
+ }
23
+ }
@@ -1,3 +1,5 @@
1
+ import "./DataEditToolbar.css";
2
+
1
3
  import { Button, Tooltip } from "@digdir/designsystemet-react";
2
4
  import { ChevronLeftIcon, ChevronRightIcon } from "@navikt/aksel-icons";
3
5
  import type { DataObject } from "@olenbetong/appframe-data";
@@ -55,23 +57,13 @@ export function DataEditToolbar({
55
57
  children,
56
58
  navigationDataObject,
57
59
  showNavigation,
58
- style,
60
+ className,
59
61
  ...props
60
62
  }: DataEditToolbarProps) {
61
63
  let dataObject = useDataObject();
62
64
 
63
65
  return (
64
- <div
65
- style={{
66
- display: "flex",
67
- alignItems: "center",
68
- minHeight: "48px",
69
- borderBottom: "1px solid rgb(0 0 0 / 0.12)",
70
- borderTop: "1px solid rgb(0 0 0 / 0.12)",
71
- ...style,
72
- }}
73
- {...props}
74
- >
66
+ <div className={className ? `DataEditToolbar-root ${className}` : "DataEditToolbar-root"} {...props}>
75
67
  <Tooltip content={getLocalizedString("Save changes")}>
76
68
  <SaveIconButton />
77
69
  </Tooltip>
@@ -83,7 +75,7 @@ export function DataEditToolbar({
83
75
  </Tooltip>
84
76
  {children}
85
77
  {showNavigation && (
86
- <div style={{ marginLeft: "auto" }}>
78
+ <div className="DataEditToolbar-navigation">
87
79
  <IndexNavigator dataObject={navigationDataObject ?? dataObject} />
88
80
  </div>
89
81
  )}
@@ -1,5 +1,5 @@
1
1
  import type { FieldMetadata, FilterExpression, FilterTree } from "@olenbetong/appframe-core";
2
- import { render, screen } from "@testing-library/react";
2
+ import { fireEvent, render, screen } from "@testing-library/react";
3
3
  import userEvent from "@testing-library/user-event";
4
4
  import { describe, expect, it, vi } from "vitest";
5
5
 
@@ -131,6 +131,32 @@ describe("FilterEditor", () => {
131
131
  expect(firstExpression(onChange)).toMatchObject({ value: "Yesterday", valueType: "special" });
132
132
  });
133
133
 
134
+ it("adds the seconds a datetime input leaves out", () => {
135
+ let onChange = vi.fn();
136
+ render(
137
+ <FilterEditor fields={FIELDS} value={filterWith("Created", "equals", "", "datetime")} onChange={onChange} />,
138
+ );
139
+
140
+ fireEvent.change(screen.getByLabelText("Value"), { target: { value: "2026-09-25T00:00" } });
141
+
142
+ expect(firstExpression(onChange)).toMatchObject({ value: "2026-09-25T00:00:00" });
143
+ });
144
+
145
+ it("adds the seconds to both ends of a datetime range", () => {
146
+ let onChange = vi.fn();
147
+ render(
148
+ <FilterEditor
149
+ fields={FIELDS}
150
+ value={filterWith("Created", "between", ["2026-09-01T00:00:00", ""], "datetime")}
151
+ onChange={onChange}
152
+ />,
153
+ );
154
+
155
+ fireEvent.change(screen.getByLabelText("To"), { target: { value: "2026-09-30T23:59" } });
156
+
157
+ expect(firstExpression(onChange)).toMatchObject({ value: ["2026-09-01T00:00:00", "2026-09-30T23:59:00"] });
158
+ });
159
+
134
160
  it("does not offer relative values for numbers", () => {
135
161
  render(<FilterEditor fields={FIELDS} value={filterWith("Age", "equals", "1", "number")} />);
136
162
 
@@ -47,6 +47,25 @@ function toStringValue(value: FilterExpression["value"]): string {
47
47
  return String(value);
48
48
  }
49
49
 
50
+ /**
51
+ * A `datetime-local` input reports `2026-09-25T00:00`, leaving out the seconds
52
+ * when they are zero. The value reaches SQL Server as a string, and SQL Server
53
+ * only accepts the ISO 8601 form with a `T` when the seconds are present, so
54
+ * the filter fails with "Conversion failed when converting date and/or time
55
+ * from character string" unless they are added.
56
+ */
57
+ function withSeconds(value: string): string {
58
+ return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(value) ? `${value}:00` : value;
59
+ }
60
+
61
+ function toFilterValue(value: FilterExpression["value"], inputType: string): FilterExpression["value"] {
62
+ if (inputType !== "datetime-local") return value;
63
+ if (typeof value === "string") return withSeconds(value);
64
+ if (Array.isArray(value)) return value.map((item) => (typeof item === "string" ? withSeconds(item) : item));
65
+
66
+ return value;
67
+ }
68
+
50
69
  function toArrayValue(value: FilterExpression["value"], length?: number): string[] {
51
70
  let array = Array.isArray(value) ? value.map(toStringValue) : [];
52
71
  if (length === undefined) return array;
@@ -75,7 +94,7 @@ export function FilterValueEditor({
75
94
  let inputType = getInputType(field);
76
95
 
77
96
  function setValue(value: FilterExpression["value"]) {
78
- onChange({ ...expression, value });
97
+ onChange({ ...expression, value: isDynamic ? value : toFilterValue(value, inputType) });
79
98
  }
80
99
 
81
100
  function setDynamic(enabled: boolean) {
@@ -40,8 +40,13 @@
40
40
  --obInputAdornments-fontSize: var(--ds-font-size-4, 1.125rem);
41
41
  }
42
42
 
43
- .ObInputAdornments-root:hover:not(:focus-within) {
44
- border-color: var(--ds-color-neutral-border-strong, #767676);
43
+ /* The Designsystemet input hover (an inner outline doubling the border), drawn on the wrapper */
44
+ .ObInputAdornments-root:hover:not(
45
+ :focus-within,
46
+ :has(.ObInputAdornments-field:disabled, .ObInputAdornments-field[readonly])
47
+ ) {
48
+ outline: var(--ds-border-width-default, 1px) solid var(--ds-color-neutral-border-default, #b3b3b3);
49
+ outline-offset: -0.1px;
45
50
  }
46
51
 
47
52
  .ObInputAdornments-root:has(.ObInputAdornments-field:focus-visible) {
@@ -68,6 +73,10 @@
68
73
  opacity: 1;
69
74
  }
70
75
 
76
+ .ObInputAdornments-root .ObInputAdornments-field:hover {
77
+ outline: none;
78
+ }
79
+
71
80
  .ObInputAdornments-root .ObInputAdornments-field:focus-visible {
72
81
  box-shadow: none;
73
82
  outline: none;