@jsenv/navi 0.15.3 → 0.15.5

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/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # @jsenv/navi
2
+
3
+ > ⚠️ **Work in Progress** - This framework is being actively developed and APIs may change.
4
+
5
+ > Helps to build modern web application
6
+
7
+ **@jsenv/navi** is a comprehensive frontend framework designed to simplify navigation, state management, and UI development. Named after Navi, the fairy guide from Zelda, it helps you navigate through the complexities of building modern web applications.
8
+
9
+ ## What it provides
10
+
11
+ ### 🧭 Navigation & Routing
12
+
13
+ - **Client-side routing** with URL synchronization and code splitting
14
+ - **Link components** that enhance standard anchor tags
15
+ - **Route components** with nested routing support
16
+ - **History management** and navigation state hooks
17
+ - **Keyboard navigation** with keyboard shortcuts
18
+
19
+ ### 🔄 State Management & Actions
20
+
21
+ - **Signal-based reactive state** with local storage integration
22
+ - **Action system** for async operations with lifecycle management
23
+ - **Resource management** with caching and request deduplication
24
+ - **Form validation** with custom constraints and real-time feedback
25
+ - **State synchronization** utilities and debugging tools
26
+
27
+ ### 🎨 UI Components
28
+
29
+ #### Form & Input Controls
30
+
31
+ - **Input, Button, Label** - Enhanced form elements with validation
32
+ - **Radio/RadioList, Checkbox/CheckboxList** - Selection controls with icons and custom appearances
33
+ - **Select, Form** - Complete form building blocks
34
+ - **Field validation** with constraint validation API integration
35
+ - **Editable components** with inline editing capabilities
36
+
37
+ #### Data Visualization
38
+
39
+ - **Table** - Complete table system with selection, sorting, and column management
40
+ - **Selection system** - Multi-selection with keyboard shortcuts
41
+ - **Error boundaries** - Graceful error handling components
42
+
43
+ #### Layout & Structure
44
+
45
+ - **Box** - Flexible layout container with spacing and alignment
46
+ - **Details** - Collapsible content with navigation state persistence
47
+ - **Dialog, Viewport layouts** - Modal and full-screen layouts
48
+ - **Separator** - Visual content dividers
49
+ - **UI Transitions** - Smooth component transitions
50
+
51
+ #### Typography & Graphics
52
+
53
+ - **Text, Title, Paragraph** - Typography components with theming
54
+ - **Code, Caption** - Specialized text displays
55
+ - **Icon, Image, Svg** - Graphics with built-in icon library
56
+ - **Badge, MessageBox** - Status and notification displays
57
+ - **Address** - Semantic contact information component
58
+
59
+ #### Interactive Features
60
+
61
+ - **Keyboard shortcuts** - Global and component-level hotkeys
62
+ - **Focus management** - Accessibility-focused navigation
63
+ - **Callouts & popovers** - Contextual overlays and tooltips
64
+ - **Copy to clipboard** - One-click content copying
65
+
66
+ ## Quick Example
67
+
68
+ ```jsx
69
+ import { render } from "preact";
70
+ import { Link, Button, Box } from "@jsenv/navi";
71
+
72
+ const userSignal = signal();
73
+ const requestUser = async ({ id }) => {
74
+ const response = await fetch(`/api/users/${id}`);
75
+ const user = response.json();
76
+ userSignal.value = user;
77
+ };
78
+
79
+ const App = () => {
80
+ const user = userSignal.value;
81
+
82
+ return (
83
+ <Box row spacing="lg">
84
+ <Link href="/profile">Go to Profile</Link>
85
+
86
+ <Button
87
+ action={async () => {
88
+ await requestUser();
89
+ }}
90
+ >
91
+ Load User Data
92
+ </Button>
93
+
94
+ {user && <div>Welcome, {user.name}!</div>}
95
+ </Box>
96
+ );
97
+ };
98
+
99
+ render(<App />, document.querySelector("#root"));
100
+ ```
101
+
102
+ ## Architecture
103
+
104
+ The framework is built around three core concepts:
105
+
106
+ 1. **Signals** - Reactive state primitives that automatically update the UI
107
+ 2. **Actions** - Async operations with built-in lifecycle management
108
+ 3. **Components** - Composable UI building blocks with consistent APIs
109
+
110
+ This combination provides a powerful yet simple foundation for building interactive web applications that scale from simple pages to complex SPAs.
@@ -11401,10 +11401,9 @@ import.meta.css = /* css */ `
11401
11401
 
11402
11402
  .navi_callout_message {
11403
11403
  position: relative;
11404
- display: inline-flex;
11404
+ display: block;
11405
11405
  box-sizing: border-box;
11406
11406
  box-decoration-break: clone;
11407
- min-width: 0;
11408
11407
  align-self: center;
11409
11408
  word-break: break-word;
11410
11409
  overflow-wrap: anywhere;
@@ -13044,11 +13043,13 @@ const TYPE_NUMBER_CONSTRAINT = {
13044
13043
  if (field.type !== "number") {
13045
13044
  return null;
13046
13045
  }
13047
- if (field.value === "" && !field.required) {
13046
+ if (field.validity.valueMissing) {
13047
+ // let required handle that
13048
13048
  return null;
13049
13049
  }
13050
- const value = field.valueAsNumber;
13051
- if (isNaN(value)) {
13050
+ const valueAsNumber = field.valueAsNumber;
13051
+ const valueAsNumberIsNaN = isNaN(valueAsNumber);
13052
+ if (valueAsNumberIsNaN) {
13052
13053
  return generateFieldInvalidMessage(`{field} doit être un nombre.`, {
13053
13054
  field,
13054
13055
  });
@@ -13132,7 +13133,7 @@ const MAX_CONSTRAINT = {
13132
13133
  }
13133
13134
  if (valueAsNumber > maxNumber) {
13134
13135
  return generateFieldInvalidMessage(
13135
- `{field} être <strong>${maxAttribute}</strong> ou plus.`,
13136
+ `{field} doit être <strong>${maxAttribute}</strong> ou moins.`,
13136
13137
  { field },
13137
13138
  );
13138
13139
  }
@@ -18754,6 +18755,11 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
18754
18755
  border-style: solid;
18755
18756
  border-color: var(--x-border-color);
18756
18757
  border-radius: var(--button-border-radius);
18758
+
18759
+ .navi_icon,
18760
+ img {
18761
+ border-radius: inherit;
18762
+ }
18757
18763
  }
18758
18764
 
18759
18765
  &[data-hover] {
@@ -19695,6 +19701,9 @@ const InputTextualBasic = props => {
19695
19701
  let inputValue;
19696
19702
  if (type === "number") {
19697
19703
  inputValue = e.target.valueAsNumber;
19704
+ if (isNaN(inputValue)) {
19705
+ inputValue = e.target.value;
19706
+ }
19698
19707
  } else if (type === "datetime-local") {
19699
19708
  inputValue = convertToUTCTimezone(e.target.value);
19700
19709
  } else {
@@ -20165,8 +20174,8 @@ const FormBasic = props => {
20165
20174
  const defaultRef = useRef();
20166
20175
  const ref = props.ref || defaultRef;
20167
20176
 
20168
- // instantiation validation to:
20169
- // - receive "requestsubmit" custom event ensure submit is prevented
20177
+ // instantiate validation via useConstraints hook:
20178
+ // - receive "actionrequested" custom event ensure submit is prevented
20170
20179
  // (and also execute action without validation if form.submit() is ever called)
20171
20180
  const remainingProps = useConstraints(ref, rest);
20172
20181
  const innerReadOnly = readOnly || loading;
@@ -23130,10 +23139,10 @@ const initMoveStickyFrontierViaPointer = (pointerdownEvent, {
23130
23139
  installImportMetaCss(import.meta);import.meta.css = /* css */`
23131
23140
  .navi_table_ui {
23132
23141
  position: fixed;
23133
- z-index: ${Z_INDEX_TABLE_UI};
23134
- overflow: hidden; /* Ensure UI elements cannot impact scrollbars of the document */
23135
23142
  inset: 0;
23143
+ z-index: ${Z_INDEX_TABLE_UI};
23136
23144
  pointer-events: none; /* UI elements must use pointer-events: auto if they need to be interactive */
23145
+ overflow: hidden; /* Ensure UI elements cannot impact scrollbars of the document */
23137
23146
  /* background: rgba(0, 255, 0, 0.2); */
23138
23147
  }
23139
23148
  `;
@@ -23774,16 +23783,17 @@ const TableCell = forwardRef((props, ref) => {
23774
23783
  });
23775
23784
  const RowNumberCol = ({
23776
23785
  width = 50,
23777
- minWidth = 30,
23778
- maxWidth = 100,
23786
+ // minWidth = 30,
23787
+ // maxWidth = 100,
23779
23788
  immovable = true,
23780
23789
  ...rest
23781
23790
  }) => {
23782
23791
  return jsx(Col, {
23783
23792
  id: "row_number",
23784
- width: width,
23785
- minWidth: minWidth,
23786
- maxWidth: maxWidth,
23793
+ width: width
23794
+ // minWidth={minWidth}
23795
+ // maxWidth={maxWidth}
23796
+ ,
23787
23797
  immovable: immovable,
23788
23798
  ...rest
23789
23799
  });
@@ -23954,8 +23964,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
23954
23964
  position: absolute;
23955
23965
  width: 1px;
23956
23966
  height: 1px;
23957
- padding: 0;
23958
23967
  margin: -1px;
23968
+ padding: 0;
23959
23969
  overflow: hidden;
23960
23970
  clip: rect(0, 0, 0, 0);
23961
23971
  white-space: nowrap;
@@ -23971,8 +23981,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
23971
23981
  position: absolute;
23972
23982
  width: 1px;
23973
23983
  height: 1px;
23974
- padding: 0;
23975
23984
  margin: -1px;
23985
+ padding: 0;
23976
23986
  overflow: hidden;
23977
23987
  clip: rect(0, 0, 0, 0);
23978
23988
  white-space: nowrap;
@@ -24689,12 +24699,12 @@ const Svg = props => {
24689
24699
 
24690
24700
  installImportMetaCss(import.meta);import.meta.css = /* css */`
24691
24701
  .svg_mask_content * {
24702
+ color: black !important;
24703
+ opacity: 1 !important;
24692
24704
  fill: black !important;
24693
- stroke: black !important;
24694
24705
  fill-opacity: 1 !important;
24706
+ stroke: black !important;
24695
24707
  stroke-opacity: 1 !important;
24696
- color: black !important;
24697
- opacity: 1 !important;
24698
24708
  }
24699
24709
  `;
24700
24710
  const SVGMaskOverlay = ({