atom.io 0.33.11 → 0.33.13

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,6 +1,6 @@
1
1
  {
2
2
  "name": "atom.io",
3
- "version": "0.33.11",
3
+ "version": "0.33.13",
4
4
  "description": "Composable and testable reactive data library.",
5
5
  "homepage": "https://atom.io.fyi",
6
6
  "sideEffects": false,
@@ -65,16 +65,16 @@
65
65
  "@storybook/addon-onboarding": "9.0.17",
66
66
  "@storybook/react-vite": "9.0.17",
67
67
  "@testing-library/react": "16.3.0",
68
- "@types/bun": "npm:bun-types@1.2.18",
68
+ "@types/bun": "npm:bun-types@1.2.19",
69
69
  "@types/eslint": "9.6.1",
70
70
  "@types/estree": "1.0.8",
71
71
  "@types/http-proxy": "1.17.16",
72
72
  "@types/npmlog": "7.0.0",
73
73
  "@types/react": "19.1.8",
74
74
  "@types/tmp": "0.2.6",
75
- "@typescript-eslint/parser": "8.37.0",
76
- "@typescript-eslint/rule-tester": "8.37.0",
77
- "@typescript-eslint/utils": "8.37.0",
75
+ "@typescript-eslint/parser": "8.38.0",
76
+ "@typescript-eslint/rule-tester": "8.38.0",
77
+ "@typescript-eslint/utils": "8.38.0",
78
78
  "@vitest/coverage-v8": "3.2.4",
79
79
  "@vitest/ui": "3.2.4",
80
80
  "concurrently": "9.2.0",
@@ -3,21 +3,29 @@ import type { FC } from "react"
3
3
 
4
4
  export const OpenClose: FC<{
5
5
  isOpen: boolean
6
- setIsOpen: (next: Modify<boolean> | boolean) => void
6
+ setIsOpen?: ((next: Modify<boolean> | boolean) => void) | undefined
7
+ onShiftClick?: (
8
+ event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
9
+ ) => boolean
7
10
  disabled?: boolean
8
11
  testid: string
9
- }> = ({ isOpen, setIsOpen, disabled, testid }) => {
12
+ }> = ({ isOpen, setIsOpen, onShiftClick, disabled, testid }) => {
10
13
  return (
11
14
  <button
12
15
  type="button"
13
16
  data-testid={testid}
14
17
  className={`carat ${isOpen ? `open` : `closed`}`}
15
- onClick={() => {
16
- setIsOpen((prev) => !prev)
18
+ onClick={(event) => {
19
+ if (onShiftClick && event.shiftKey) {
20
+ if (!onShiftClick(event)) {
21
+ return
22
+ }
23
+ }
24
+ setIsOpen?.((prev) => !prev)
17
25
  }}
18
26
  disabled={disabled}
19
27
  >
20
-
28
+ <span className="json_editor_icon json_editor_carat">▶</span>
21
29
  </button>
22
30
  )
23
31
  }
@@ -9,8 +9,16 @@ export const StateEditor: FC<{
9
9
  }> = ({ token }) => {
10
10
  const set = useI(token)
11
11
  const data = useO(token)
12
+ const metaPath = token.family
13
+ ? [token.family.key, token.family.subKey]
14
+ : [token.key]
12
15
  return (
13
- <JsonEditor testid={`${token.key}-state-editor`} data={data} set={set} />
16
+ <JsonEditor
17
+ testid={`${token.key}-state-editor`}
18
+ data={data}
19
+ set={set}
20
+ path={metaPath}
21
+ />
14
22
  )
15
23
  }
16
24
 
@@ -18,12 +26,16 @@ export const ReadonlySelectorViewer: FC<{
18
26
  token: ReadonlySelectorToken<unknown>
19
27
  }> = ({ token }) => {
20
28
  const data = useO(token)
29
+ const metaPath = token.family
30
+ ? [token.family.key, token.family.subKey]
31
+ : [token.key]
21
32
  return (
22
33
  <JsonEditor
23
34
  testid={`${token.key}-state-editor`}
24
35
  data={data}
25
36
  set={() => null}
26
37
  isReadonly={() => true}
38
+ path={metaPath}
27
39
  />
28
40
  )
29
41
  }
@@ -4,8 +4,12 @@ import type {
4
4
  ReadonlyPureSelectorToken,
5
5
  RegularAtomToken,
6
6
  } from "atom.io"
7
- import { getState } from "atom.io"
8
- import { findInStore } from "atom.io/internal"
7
+ import {
8
+ actUponStore,
9
+ arbitrary,
10
+ findInStore,
11
+ getFromStore,
12
+ } from "atom.io/internal"
9
13
  import type { FamilyNode, WritableTokenIndex } from "atom.io/introspection"
10
14
  import { primitiveRefinery } from "atom.io/introspection"
11
15
  import { useI, useO } from "atom.io/react"
@@ -22,6 +26,8 @@ export const StateIndexLeafNode: FC<{
22
26
  isOpenState: RegularAtomToken<boolean>
23
27
  typeState: ReadonlyPureSelectorToken<Loadable<string>>
24
28
  }> = ({ node, isOpenState, typeState }) => {
29
+ const { openCloseAllTX, store } = useContext(DevtoolsContext)
30
+
25
31
  const setIsOpen = useI(isOpenState)
26
32
  const isOpen = useO(isOpenState)
27
33
 
@@ -32,23 +38,29 @@ export const StateIndexLeafNode: FC<{
32
38
 
33
39
  const isPrimitive = Boolean(primitiveRefinery.refine(state))
34
40
 
41
+ const path = node.family ? [node.family.key, node.family.subKey] : [node.key]
42
+
35
43
  return (
36
44
  <>
37
45
  <header>
38
- <button.OpenClose
39
- isOpen={isOpen && !isPrimitive}
40
- testid={`open-close-state-${node.key}`}
41
- setIsOpen={setIsOpen}
42
- disabled={isPrimitive}
43
- />
44
46
  <main
45
47
  onClick={() => {
46
- console.log(node, getState(node))
48
+ console.log(node, getFromStore(store, node))
47
49
  }}
48
50
  onKeyUp={() => {
49
- console.log(node, getState(node))
51
+ console.log(node, getFromStore(store, node))
50
52
  }}
51
53
  >
54
+ <button.OpenClose
55
+ isOpen={isOpen && !isPrimitive}
56
+ testid={`open-close-state-${node.key}`}
57
+ onShiftClick={() => {
58
+ actUponStore(store, openCloseAllTX, arbitrary())(path, isOpen)
59
+ return false
60
+ }}
61
+ setIsOpen={setIsOpen}
62
+ disabled={isPrimitive}
63
+ />
52
64
  <h2>{node.family?.subKey ?? node.key}</h2>
53
65
  <span className="type detail">({stateType})</span>
54
66
  </main>
@@ -73,21 +85,30 @@ export const StateIndexTreeNode: FC<{
73
85
  const setIsOpen = useI(isOpenState)
74
86
  const isOpen = useO(isOpenState)
75
87
 
76
- const { typeSelectors, viewIsOpenAtoms, store } = useContext(DevtoolsContext)
88
+ const { typeSelectors, viewIsOpenAtoms, openCloseAllTX, store } =
89
+ useContext(DevtoolsContext)
77
90
 
78
91
  for (const [key, childNode] of node.familyMembers) {
79
- findInStore(store, viewIsOpenAtoms, key)
92
+ findInStore(store, viewIsOpenAtoms, [key])
80
93
  findInStore(store, typeSelectors, childNode.key)
81
94
  }
82
95
  return (
83
96
  <>
84
97
  <header>
85
- <button.OpenClose
86
- isOpen={isOpen}
87
- testid={`open-close-state-family-${node.key}`}
88
- setIsOpen={setIsOpen}
89
- />
90
98
  <main>
99
+ <button.OpenClose
100
+ isOpen={isOpen}
101
+ testid={`open-close-state-family-${node.key}`}
102
+ onShiftClick={() => {
103
+ actUponStore(
104
+ store,
105
+ openCloseAllTX,
106
+ arbitrary(),
107
+ )([node.key], isOpen)
108
+ return false
109
+ }}
110
+ setIsOpen={setIsOpen}
111
+ />
91
112
  <h2>{node.key}</h2>
92
113
  <span className="type detail"> (family)</span>
93
114
  </main>
@@ -97,7 +118,7 @@ export const StateIndexTreeNode: FC<{
97
118
  <StateIndexNode
98
119
  key={key}
99
120
  node={childNode}
100
- isOpenState={findInStore(store, viewIsOpenAtoms, childNode.key)}
121
+ isOpenState={findInStore(store, viewIsOpenAtoms, [node.key, key])}
101
122
  typeState={findInStore(store, typeSelectors, childNode.key)}
102
123
  />
103
124
  ))
@@ -135,7 +156,6 @@ export const StateIndex: FC<{
135
156
 
136
157
  const { typeSelectors, viewIsOpenAtoms, store } = useContext(DevtoolsContext)
137
158
 
138
- console.log(tokenIds)
139
159
  return (
140
160
  <article className="index state_index" data-testid="state-index">
141
161
  {[...tokenIds.entries()].map(([key, node]) => {
@@ -143,7 +163,7 @@ export const StateIndex: FC<{
143
163
  <StateIndexNode
144
164
  key={key}
145
165
  node={node}
146
- isOpenState={findInStore(store, viewIsOpenAtoms, node.key)}
166
+ isOpenState={findInStore(store, viewIsOpenAtoms, [node.key])}
147
167
  typeState={findInStore(store, typeSelectors, node.key)}
148
168
  />
149
169
  )
@@ -102,7 +102,7 @@ export const TimelineIndex: FC = () => {
102
102
  <TimelineLog
103
103
  key={token.key}
104
104
  token={token}
105
- isOpenState={findInStore(store, viewIsOpenAtoms, token.key)}
105
+ isOpenState={findInStore(store, viewIsOpenAtoms, [token.key])}
106
106
  timelineState={findInStore(store, timelineSelectors, token.key)}
107
107
  />
108
108
  )
@@ -66,7 +66,7 @@ export const TransactionIndex: FC = () => {
66
66
  <TransactionLog
67
67
  key={token.key}
68
68
  token={token}
69
- isOpenState={findInStore(store, viewIsOpenAtoms, token.key)}
69
+ isOpenState={findInStore(store, viewIsOpenAtoms, [token.key])}
70
70
  logState={findInStore(store, transactionLogSelectors, token.key)}
71
71
  />
72
72
  )
@@ -1,9 +1,9 @@
1
1
  main[data-css="atom_io_devtools"] {
2
2
  --fg-color: #ccc;
3
3
  --fg-light: #aaa;
4
- --fg-soft: #777;
5
- --fg-faint: #555;
6
- --fg-hint: #333;
4
+ --fg-soft: #888;
5
+ --fg-faint: #777;
6
+ --fg-hint: #4a4a4a;
7
7
  --fg-max: #fff;
8
8
  --bg-color: #111;
9
9
  --bg-accent: #00f;
@@ -12,6 +12,7 @@ main[data-css="atom_io_devtools"] {
12
12
  --bg-tint2: #333;
13
13
  --fg-border: 1px solid var(--fg-color);
14
14
  --fg-border-soft: 1px solid var(--fg-soft);
15
+ --fg-border-hint: 1px solid var(--fg-hint);
15
16
  @media (prefers-color-scheme: light) {
16
17
  --fg-color: #444;
17
18
  --fg-light: #777;
@@ -25,6 +26,9 @@ main[data-css="atom_io_devtools"] {
25
26
  --bg-tint1: #e3e3e3;
26
27
  --bg-tint2: #f3f3f3;
27
28
  }
29
+ * {
30
+ box-sizing: border-box;
31
+ }
28
32
  & {
29
33
  pointer-events: all;
30
34
  box-sizing: border-box;
@@ -43,7 +47,7 @@ main[data-css="atom_io_devtools"] {
43
47
  overflow-y: scroll;
44
48
  }
45
49
  * {
46
- font-size: 16px;
50
+ font-size: 14px;
47
51
  font-family: theia, monospace;
48
52
  line-height: 1em;
49
53
  color: var(--fg-color);
@@ -89,13 +93,15 @@ main[data-css="atom_io_devtools"] {
89
93
  background-color: black;
90
94
  height: 10px;
91
95
  }
92
- main {
96
+ > main {
93
97
  overflow-y: scroll;
94
98
  flex-grow: 1;
95
99
  display: flex;
96
100
  flex-flow: column;
97
101
  gap: 0;
98
102
  article.index {
103
+ margin-bottom: 24px;
104
+ border-bottom: var(--fg-border);
99
105
  .node .node {
100
106
  border-right: var(--fg-border);
101
107
  padding-right: 0;
@@ -105,47 +111,26 @@ main[data-css="atom_io_devtools"] {
105
111
  }
106
112
  }
107
113
  .node > .node {
108
- margin: 0px 2px 2px 18px;
114
+ margin: 0px 0px 0px 9px;
109
115
  border-left: var(--fg-border-soft);
110
- &:last-of-type {
111
- margin-bottom: 6px;
112
- }
113
116
  }
114
117
  .node {
115
118
  border-top: var(--fg-border);
116
119
  overflow: visible;
117
- &:last-of-type {
118
- border-bottom: var(--fg-border);
119
- }
120
+
120
121
  &.transaction_update {
121
122
  padding: 0;
122
123
  }
123
- header {
124
+ > header {
124
125
  display: flex;
125
126
  flex-flow: row;
127
+ justify-content: space-between;
126
128
  position: sticky;
127
129
  z-index: 999;
128
130
  top: 0;
129
131
  height: 22px;
130
132
  background: var(--bg-tint2);
131
- border-bottom: var(--fg-border-soft);
132
- button.carat {
133
- cursor: pointer;
134
- background: none;
135
- border: none;
136
- width: 20px;
137
- color: var(--fg-light);
138
- &.open {
139
- transform: rotate(90deg);
140
- }
141
- &:disabled {
142
- cursor: default;
143
- }
144
- &:focus-within {
145
- outline: 2px solid var(--fg-max);
146
- background: var(--bg-max);
147
- }
148
- }
133
+ border-bottom: none;
149
134
  > main {
150
135
  display: flex;
151
136
  flex-flow: row;
@@ -170,22 +155,31 @@ main[data-css="atom_io_devtools"] {
170
155
  }
171
156
  }
172
157
  }
158
+ > .json_editor {
159
+ border-left: var(--fg-border-soft);
160
+ padding: 3px 0px;
161
+ }
173
162
  > .json_viewer {
174
163
  color: var(--fg-light);
164
+ font-size: 14px;
165
+ flex-shrink: 1;
175
166
  }
176
167
  > .json_editor,
177
168
  > .json_viewer {
169
+ margin-left: 3px;
178
170
  display: flex;
179
171
  flex-shrink: 1;
180
172
  z-index: -1;
181
173
  overflow-x: scroll;
182
174
  align-items: center;
183
- border-left: var(--fg-border-soft);
184
175
  white-space: pre;
185
176
  background: var(--bg-tint2);
186
177
  &:focus-within {
187
178
  background-color: var(--bg-max);
188
179
  outline: 2px solid var(--fg-max);
180
+ * {
181
+ color: var(--fg-max);
182
+ }
189
183
  }
190
184
  &.nu * {
191
185
  display: flex;
@@ -197,6 +191,7 @@ main[data-css="atom_io_devtools"] {
197
191
  }
198
192
  input {
199
193
  outline: none;
194
+ min-width: 10px;
200
195
  }
201
196
  }
202
197
  }
@@ -295,7 +290,7 @@ main[data-css="atom_io_devtools"] {
295
290
  }
296
291
  }
297
292
  }
298
- footer {
293
+ > footer {
299
294
  display: flex;
300
295
  justify-content: flex-end;
301
296
  button {
@@ -310,6 +305,51 @@ main[data-css="atom_io_devtools"] {
310
305
  }
311
306
 
312
307
  .json_editor {
308
+ display: flex;
309
+ flex-flow: column;
310
+ align-items: flex-start;
311
+ > header {
312
+ width: 100%;
313
+ display: flex;
314
+ flex-flow: row;
315
+ align-items: center;
316
+ overflow: hidden;
317
+ white-space: nowrap;
318
+ justify-content: space-between;
319
+ &:has(> main > button.carat) {
320
+ height: 21px;
321
+ }
322
+ > main {
323
+ display: flex;
324
+ flex-flow: row;
325
+ align-items: center;
326
+ flex-shrink: 1;
327
+ overflow-x: hidden;
328
+ align-self: center;
329
+ > .json_viewer {
330
+ flex-shrink: 1;
331
+ overflow-x: scroll;
332
+ height: 21px;
333
+ font-size: 14px;
334
+ display: flex;
335
+ align-items: center;
336
+ margin-left: 0px;
337
+ color: var(--fg-soft);
338
+ }
339
+ > button {
340
+ padding: 0;
341
+ &.carat {
342
+ line-height: 0.5em;
343
+ font-size: 14px;
344
+ }
345
+ }
346
+ }
347
+ }
348
+ > .json_editor_array,
349
+ > .json_editor_object {
350
+ width: 100%;
351
+ flex-grow: 1;
352
+ }
313
353
  input {
314
354
  font-family: theia, monospace;
315
355
  background: none;
@@ -318,13 +358,14 @@ main[data-css="atom_io_devtools"] {
318
358
  }
319
359
  }
320
360
  input,
321
- button,
322
361
  select {
323
362
  &:focus-within {
324
363
  outline: 2px solid var(--fg-max);
325
364
  background: var(--bg-max);
365
+ color: var(--fg-max);
326
366
  }
327
367
  }
368
+
328
369
  button:disabled {
329
370
  cursor: default;
330
371
  > span.json_editor_icon {
@@ -345,13 +386,25 @@ main[data-css="atom_io_devtools"] {
345
386
  font-family: theia, monospace;
346
387
  font-size: 14px;
347
388
  height: 21px;
389
+ min-width: 21px;
348
390
  margin: none;
349
391
  padding: 4px;
350
392
  padding-bottom: 6px;
351
393
  cursor: pointer;
352
- &:hover {
353
- color: #333;
354
- background-color: #aaa;
394
+ &:hover,
395
+ &:focus-within {
396
+ background-color: var(--fg-faint);
397
+ &,
398
+ > * {
399
+ color: var(--bg-color);
400
+ }
401
+ }
402
+ &:active {
403
+ background: var(--fg-color);
404
+ &,
405
+ > * {
406
+ color: var(--bg-color);
407
+ }
355
408
  }
356
409
  }
357
410
  select {
@@ -377,32 +430,45 @@ main[data-css="atom_io_devtools"] {
377
430
  padding-right: 0px;
378
431
  &::after {
379
432
  content: ":";
433
+ color: var(--fg-soft);
380
434
  }
381
435
  input {
382
- color: #999;
383
- @media (prefers-color-scheme: light) {
384
- color: #777;
385
- }
436
+ color: var(--fg-color);
386
437
  }
387
438
  }
388
439
  .json_editor_object,
389
440
  .json_editor_array {
390
441
  border-left: var(--fg-border-soft);
391
- padding-left: 12px;
392
- margin-left: 8px;
442
+ margin-left: 9px;
443
+ width: calc(100% - 9px);
444
+ > footer {
445
+ display: flex;
446
+ flex-flow: row;
447
+ justify-content: flex-start;
448
+ justify-items: flex-start;
449
+ height: 21px;
450
+ align-items: baseline;
451
+ position: relative;
452
+ }
393
453
  .json_editor_properties,
394
454
  .json_editor_elements {
455
+ border-top: var(--fg-border-soft);
395
456
  .json_editor_property,
396
457
  .json_editor_element {
458
+ display: flex;
397
459
  border-bottom: var(--fg-border-soft);
398
- margin-bottom: 2px;
399
- min-height: 23px;
400
- > span {
401
- input {
402
- min-width: 10px;
403
- }
404
- > * {
405
- border: var(--fg-border-soft);
460
+ margin-bottom: 0;
461
+ min-height: 21px;
462
+ > header {
463
+ > main {
464
+ > span {
465
+ input {
466
+ min-width: 10px;
467
+ }
468
+ > * {
469
+ border: var(--fg-border-hint);
470
+ }
471
+ }
406
472
  }
407
473
  }
408
474
  }
@@ -412,7 +478,7 @@ main[data-css="atom_io_devtools"] {
412
478
  &:last-of-type {
413
479
  border-bottom: none;
414
480
  }
415
- > span > * {
481
+ span > * {
416
482
  border: 1px solid transparent;
417
483
  }
418
484
  }
@@ -420,4 +486,26 @@ main[data-css="atom_io_devtools"] {
420
486
  }
421
487
  }
422
488
  }
489
+
490
+ .json_editor_icon {
491
+ color: var(--fg-soft);
492
+ }
493
+
494
+ button.carat {
495
+ border: none;
496
+ cursor: pointer;
497
+ background: none;
498
+ > .json_editor_icon {
499
+ line-height: 4px;
500
+ }
501
+ &.open {
502
+ transform: rotate(90deg);
503
+ }
504
+ &:disabled {
505
+ > .json_editor_icon {
506
+ cursor: default;
507
+ color: var(--fg-hint);
508
+ }
509
+ }
510
+ }
423
511
  }
@@ -18,6 +18,7 @@ export type JsonEditorComponents = {
18
18
  disabled?: boolean
19
19
  }>
20
20
  DeleteIcon: FC
21
+ AddIcon: FC
21
22
 
22
23
  EditorWrapper: WC<{
23
24
  style?: CSSProperties | undefined
@@ -86,8 +87,10 @@ export const DEFAULT_JSON_EDITOR_COMPONENTS: JsonEditorComponents = {
86
87
  <span className="json_editor_null" data-testid={testid} />
87
88
  ),
88
89
  DeleteIcon: () => (
89
- <span className="json_editor_icon json_editor_delete">x</span>
90
+ <span className="json_editor_icon json_editor_delete">×</span>
90
91
  ),
92
+ // big plus
93
+ AddIcon: () => <span className="json_editor_icon json_editor_add">+</span>,
91
94
  MissingPropertyWrapper: ({ children, testid }) => (
92
95
  <span className="json_editor_missing_property" data-testid={testid}>
93
96
  {children}
@@ -48,6 +48,7 @@ export const JsonEditor = <T,>({
48
48
  name,
49
49
  rename,
50
50
  remove,
51
+ path = [],
51
52
  isReadonly = () => false,
52
53
  isHidden = () => false,
53
54
  className,
@@ -68,7 +69,7 @@ export const JsonEditor = <T,>({
68
69
  name={name}
69
70
  rename={rename}
70
71
  remove={remove}
71
- path={[]}
72
+ path={path}
72
73
  isReadonly={isReadonly}
73
74
  isHidden={isHidden}
74
75
  className={className}