@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 +110 -0
- package/dist/jsenv_navi.js +30 -20
- package/dist/jsenv_navi.js.map +15 -15
- package/package.json +2 -2
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.
|
package/dist/jsenv_navi.js
CHANGED
|
@@ -11401,10 +11401,9 @@ import.meta.css = /* css */ `
|
|
|
11401
11401
|
|
|
11402
11402
|
.navi_callout_message {
|
|
11403
11403
|
position: relative;
|
|
11404
|
-
display:
|
|
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.
|
|
13046
|
+
if (field.validity.valueMissing) {
|
|
13047
|
+
// let required handle that
|
|
13048
13048
|
return null;
|
|
13049
13049
|
}
|
|
13050
|
-
const
|
|
13051
|
-
|
|
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
|
|
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
|
-
//
|
|
20169
|
-
// - receive "
|
|
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
|
-
|
|
23786
|
-
|
|
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 = ({
|