You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Oct 1, 2024. It is now read-only.
Copy file name to clipboardExpand all lines: app/docs/javascript.md
+82-50Lines changed: 82 additions & 50 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,41 +1,44 @@
1
-
The API for Undernet's scripts is straightforward. The main rule of thumb is to use the scripts when you know the DOM is fully rendered.
1
+
The JS API for Undernetis fairly straightforward. The main rule of thumb is to only use the JS when you know the DOM is fully parsed and ready.
2
2
3
3
Enabling Undernet, including all its component plugins, is as easy as this:
4
4
5
5
```html
6
-
<scriptsrc="path/to/undernet.js"></script>
7
-
<script>
8
-
// Undernet will be attached to the `window`
9
-
if (document) document.addEventListener("DOMContentLoaded", Undernet.start)
10
-
</script>
6
+
<body>
7
+
<!-- at the end of the body tag -->
8
+
<scriptsrc="path/to/undernet.min.js"></script>
9
+
<script>
10
+
// Undernet will be attached to the `window`
11
+
if (document) document.addEventListener("DOMContentLoaded", Undernet.start)
12
+
</script>
13
+
</body>
11
14
```
12
15
13
16
## Core API
14
17
15
-
There are a few ways to use Undernet. The most direct way is using the `start` and `stop` methods off the default `Undernet` object:
18
+
You can enable and disable all components on the global `Undernet` object using the `start` and `stop` methods.
16
19
17
20
### start
18
21
19
22
```js
20
-
Undernet.start(scopeString, enableFocusRing)
23
+
Undernet.start(scopeString, useFocusRing)
21
24
```
22
25
23
26
#### `scopeString` (string)
24
27
25
28
Default: `undefined`
26
29
27
-
Limits Undernet's initialization to a specific DOM with selector string `scopeString`. E.g., `#my-element-id`.
30
+
Limits Undernet's initialization to a specific DOM with selector string `scopeString`. E.g., `#my-element-id`. Scroll down to learn more.
28
31
29
-
#### `enableFocusRing` (boolean)
32
+
#### `useFocusRing` (boolean)
30
33
31
34
Default: `false`
32
35
33
-
Enables a utility which adds a distinct focus ring on elements focused while using a keyboard.
36
+
Enables a utility which adds a distinct focus ring on elements focused while using a keyboard. Scroll down to learn more.
34
37
35
38
### stop
36
39
37
40
```js
38
-
Undernet.stop(scopeString, enableFocusRing)
41
+
Undernet.stop(scopeString, useFocusRing)
39
42
```
40
43
41
44
#### `scopeString` (string)
@@ -44,37 +47,57 @@ Default: `undefined`
44
47
45
48
Runs a teardown of components previously initialized via `start(scopeString)`.
46
49
47
-
#### `enableFocusRing` (boolean)
50
+
#### `useFocusRing` (boolean)
48
51
49
52
Default: `false`
50
53
51
54
Disables the focus ring utility.
52
55
53
-
## Using Modules
56
+
## Individual Components
57
+
58
+
You can use the same API above to enable or disable individual components, as well. The main difference is there isn't a second `useFocusRing` parameter.
54
59
55
-
If you use npm, the default export is the `Undernet` object. Importing this gives you everything, including all JS components and utilities.
60
+
### start
56
61
57
62
```js
58
-
importUndernetfrom"undernet"
63
+
Undernet.Modals.start()
64
+
Undernet.Accordions.start("#wrapper-element")
65
+
// or, if you're using named imports via npm:
66
+
Modals.start()
67
+
Accordions.start("#wrapper-element")
59
68
```
60
69
61
-
You can also do a named import of just one component. Bonus: it's tree-shakable if you use tools which enable it (webpack and rollup both offer it out of the box).
70
+
### stop
62
71
63
72
```js
64
-
import { Modals, createFocusRing } from"undernet"
73
+
Undernet.Modals.stop()
74
+
Undernet.Accordions.stop("#wrapper-element")
75
+
// or, if you're using named imports via npm:
76
+
Modals.stop()
77
+
Accordions.stop("#wrapper-element")
65
78
```
66
79
67
-
## Scope
80
+
## Using Modules
68
81
69
-
Undernet is a global framework by default. This means it will capture the entire document when searching for its component instances. Sadly that won't mesh well when you're using UI frameworks like React, for example, where React components could be tapping into Undernet with redundancy. If you do this in the ways described in the sections above, you'll inadvertently reset Undernet's internal trackers, resulting in components being changed outside the scope of your given React component.
82
+
If you use npm, the default export is the `Undernet` object, whose API is the same as above.
70
83
71
-
The solution? **Force initialization to a DOM fragment.** You can achieve this by passing a selector string to the `start` and `stop` methods of Undernet or one of its components. The selector string will be queried and Undernet will limit its search to within that element.
84
+
```js
85
+
importUndernetfrom"undernet"
86
+
Undernet.start()
87
+
```
72
88
73
-
Let's look at some examples, using React with hooks to demonstrate how scope works.
89
+
You can also do a named import of just one component or utility. Bonus: it's tree-shakable if you use tools which enable the feature (webpack and rollup, for example).
74
90
75
-
---
91
+
```js
92
+
import { Modals } from"undernet"
93
+
Modals.start()
94
+
```
76
95
77
-
As described, the DOM must be ready before Undernet can run. Run `.start` in a `useEffect` block with no dependencies (empty array) so it only runs once. Likewise, when the component(s) are about to removed from the DOM, stop Undernet by returning a function which calls `.stop`. Use the outer-most element's id as your scope string.
96
+
## Scope
97
+
98
+
By default, the `start` and `stop` methods will search the entire DOM to enable/disable components. This is undesirable in frameworks like React, which are fragment-based. To work around this issue, you can pass a selector string which will keep track of Undernet only within the scope specified.
99
+
100
+
As a practical but simple example, `start` Undernet or a single component when the React component is mounted, and then `stop` if the React component will be unmounted. Use the ID selector (or class, attribute, etc) of the outermost element for the scope:
78
101
79
102
```js
80
103
exportdefaultfunctionSidebar(props) {
@@ -88,30 +111,31 @@ export default function Sidebar(props) {
88
111
}
89
112
```
90
113
91
-
Now all Collapsibles used are scoped to the markup defined in `<Sidebar />`.
114
+
Now all Collapsibles used are scoped to the `#sidebar-wrapper` element!
92
115
93
-
NOTE: Don't initialize an Undernet scope within a child React component. This will duplicate events and cause unexpected behavior.
116
+
NOTE: Be careful about using Undernet this way if you have child components; calling Undernet in a child will duplicate events and cause bugs.
94
117
95
118
### Handling DOM State
96
119
97
-
If you're specifically removing nodes from the DOM or virtual DOM, you'll need to be careful. Undernet isn't smart enough to know that your DOM changed, but luckily most UI frameworks provide lifecycle methods that tell us the DOM is rendered or about to re-render, so we can piggy-back off that!
120
+
If you're removing/adding nodes from/to the DOM, you'll need to be careful. Undernet isn't smart enough to know that your DOM changed. Luckily most UI frameworks provide lifecycle functionality that tells us the DOM is rendered or about to re-render, so we can piggy-back off that!
98
121
99
122
Let's extend the sidebar example from before, but this time we'll toggle its visibility using a button:
100
123
101
124
```js
102
125
exportdefaultfunctionSidebar(props) {
126
+
// We'll use a state dependency to determine when to `start` Collapsibles
//This time, we'll remove `.start` and have this `.stop` during component unmount.
128
+
//No need to `start` here, but we do want to `stop` on unmount still
105
129
useEffect(() => {
106
130
return () =>Collapsibles.stop("#sidebar-wrapper")
107
131
}, [])
108
-
// Whenever sidebarIsVisible changes, we'll check its value
109
-
// If it's not visible, do nothing and return;
132
+
// Whenever sidebarIsVisible changes, we'll check its value:
133
+
// If it's not visible, do nothing
110
134
// Else, it's visible, so start collapsibles in the sidebar scope
111
135
useEffect(() => {
112
136
if (sidebarIsVisible) Collapsibles.start("#sidebar-wrapper")
113
137
}, [sidebarIsVisible])
114
-
// If the sidebar is visible, stop collapsibles before the sidebar is removed from the DOM
138
+
// If the sidebar is visible on click, stop collapsibles before the sidebar is removed from the DOM
115
139
consthandleClick= (e) => {
116
140
if (sidebarIsVisible) Collapsibles.stop("#sidebar-wrapper")
117
141
setSidebarIsVisible(!sidebarIsVisible)
@@ -127,53 +151,59 @@ export default function Sidebar(props) {
127
151
128
152
In this component, we have a button that when clicked will toggle visibility of the sidebar, which has some collapsible instances inside it.
129
153
130
-
When the button is clicked, we want to stop collapsibles in the DOM by checking if `sidebarIsVisible` is currently `true`, before its setter is called. If they are, set visibility to `false`.
154
+
`Collapsibles.start`is now dependent on `sidebarIsVisible`, and will call on initial render (and subsequent re-renders) if the state is `true`.
131
155
132
-
Then, when `sidebarIsVisible` is `true` again (sometime in the future), we know the sidebar DOM is ready, so we can start collapsibles again.
156
+
When the button is clicked, we want to stop collapsibles if `sidebarIsVisible` is currently `true`, but before state is flipped in the setter.
157
+
158
+
The cycle continues for each time the button is clicked.
133
159
134
160
## Utilities
135
161
136
-
Undernet comes with two utilities out of the box: `createFocusRing` and `createFocusTrap`. They can be initialized with `start` and `stop` methods like the rest of the component APIs. The only difference is there is no scope available.
162
+
Undernet comes with two utilities out of the box: `createFocusRing` and `createFocusTrap`. They can be initialized with `start` and `stop` methods. The only difference is there is no scope available.
137
163
138
164
### createFocusRing
139
165
140
166
This will create global event listeners on the page for keyboard and mouse behavior.
141
167
142
-
If tab, space, or arrow keys are being used, you're in "keyboard mode" so a bright focus ring will show when elements are focused.
168
+
```js
169
+
import { createFocusRing } from"undernet"
170
+
constfocusRing=createFocusRing()
171
+
focusRing.start()
172
+
```
173
+
174
+
If tab, space, or arrow keys are being used, you're in "keyboard mode," enabling a bright focus ring around the actively focused element.
143
175
144
176
As soon as a mouse is in use again, the ring goes away.
145
177
146
-
If you use the utility, whether through `Undernet.start()` or directly like in the previous examples, you should only initialize it once on a page. Enabling it multiple times will create inconsistent behavior between keyboard and mouse interactions, showing the ring when a mouse is used, and potentially hiding it for keyboard users.
178
+
If you use the utility, whether through this utility or `Undernet.start` or `Undernet.stop`, only initialize it **once** on a page. Enabling it multiple times will create inconsistent results.
147
179
148
180
### createFocusTrap
149
181
150
-
This is less of a common utility, and moreso offered to allow you the same focus trap behavior that the components use. Better to use a utility that's not implemented two times in different ways!
182
+
This utility is offered in case you need the functionality outside of the components provided in Undernet.
151
183
152
-
It's instantiated the same way as `createFocusRing`:
184
+
It's instantiated the same way as `createFocusRing`, but takes two parameters:
153
185
154
186
```js
155
187
import { createFocusTrap } from"undernet"
156
188
constfocusTrap=createFocusTrap()
157
189
focusTrap.start(selector, options)
158
190
```
159
191
160
-
Using it is slightly different than in previous APIs.
161
-
162
192
#### `selector` (string)
163
193
164
194
**Required**
165
195
166
-
A string to be queried in the DOM; it's the "container" of possible focusable elements.
196
+
A string to be queried in the DOM; it will be treated as the container of possible focusable elements. If this is the only parameter given, `tab` and `shift+tab` will be the key-bindings used for trapping.
167
197
168
198
```js
169
-
focusTrap.start(".my-element")
199
+
focusTrap.start(".wrapper-element")
170
200
```
171
201
172
202
#### `options` (object)
173
203
174
204
Default: `{}`
175
205
176
-
Change how focus behavior works or what elements to search for.
206
+
Customize trapping behavior using the below options.
NOTE: You should still pass a selector string for the wrapper as a fallback, in case `children` comes back empty and you aren't using a guard for that case explicitly.
0 commit comments