@globalbrain/sefirot 3.29.1 → 3.30.0

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.
@@ -1,128 +1,141 @@
1
1
  import isEqual from 'lodash-es/isEqual'
2
- import isPlainObject from 'lodash-es/isPlainObject'
3
- import { type MaybeRef, unref, watch } from 'vue'
4
- import { useRoute, useRouter } from 'vue-router'
2
+ import { type MaybeRef, nextTick, unref, watch } from 'vue'
3
+ import { type LocationQuery, useRoute, useRouter } from 'vue-router'
5
4
 
6
5
  export interface UseUrlQuerySyncOptions {
7
6
  casts?: Record<string, (value: any) => any>
8
7
  exclude?: string[]
9
8
  }
10
9
 
10
+ /**
11
+ * Sync between the given state and the URL query params.
12
+ *
13
+ * Caveats:
14
+ * - Vulnerable to prototype pollution.
15
+ * - Does not support objects inside arrays.
16
+ */
11
17
  export function useUrlQuerySync(
12
18
  state: MaybeRef<Record<string, any>>,
13
- { casts = {}, exclude }: UseUrlQuerySyncOptions = {}
19
+ { casts = {}, exclude = [] }: UseUrlQuerySyncOptions = {}
14
20
  ): void {
15
- const router = useRouter()
16
21
  const route = useRoute()
22
+ const router = useRouter()
17
23
 
18
- const flattenInitialState = flattenObject(
19
- JSON.parse(JSON.stringify(unref(state)))
20
- )
21
-
22
- setStateFromQuery()
23
-
24
- watch(() => unref(state), setQueryFromState, {
25
- deep: true,
26
- immediate: true
27
- })
24
+ const flattenedDefaultState = flattenObject(unref(state))
28
25
 
29
- function setStateFromQuery() {
30
- const flattenState = flattenObject(unref(state))
31
- const flattenQuery = flattenObject(route.query)
26
+ let isSyncing = false
32
27
 
33
- Object.keys(flattenQuery).forEach((key) => {
34
- if (exclude?.includes(key)) {
35
- return
28
+ watch(
29
+ () => route.query,
30
+ async () => {
31
+ if (!isSyncing) {
32
+ isSyncing = true
33
+ await setState()
34
+ isSyncing = false
36
35
  }
36
+ },
37
+ { deep: true, immediate: true }
38
+ )
37
39
 
38
- const value = flattenQuery[key]
39
- if (value === undefined) {
40
- return
40
+ watch(
41
+ () => unref(state),
42
+ async () => {
43
+ if (!isSyncing) {
44
+ isSyncing = true
45
+ await setQuery()
46
+ isSyncing = false
41
47
  }
48
+ },
49
+ { deep: true }
50
+ )
42
51
 
43
- const cast = casts[key]
44
- flattenState[key] = cast ? cast(value) : value
45
- })
52
+ async function setState() {
53
+ const newState = unflattenObject({ ...flattenedDefaultState, ...normalizeQuery(route.query) })
54
+ deepAssign(unref(state), newState)
46
55
 
47
- deepAssign(unref(state), unflattenObject(flattenState))
56
+ await nextTick()
57
+ await setQuery()
48
58
  }
49
59
 
50
- async function setQueryFromState() {
51
- const flattenState = flattenObject(unref(state))
52
- const flattenQuery = flattenObject(route.query)
60
+ async function setQuery() {
61
+ const flattenedState = flattenObject(unref(state))
62
+ const newQuery: Record<string, any> = {}
53
63
 
54
- Object.keys(flattenState).forEach((key) => {
55
- if (exclude?.includes(key)) {
56
- return
64
+ for (const key in flattenedState) {
65
+ if (!exclude.includes(key) && flattenedDefaultState[key] !== flattenedState[key]) {
66
+ newQuery[key] = flattenedState[key]
57
67
  }
68
+ }
58
69
 
59
- const value = flattenState[key]
60
- const initialValue = flattenInitialState[key]
70
+ const currentQuery = normalizeQuery(route.query)
61
71
 
62
- if (isEqual(value, initialValue)) {
63
- delete flattenQuery[key]
64
- } else {
65
- flattenQuery[key] = value
66
- }
72
+ if (!isEqual(newQuery, currentQuery)) {
73
+ await router.replace({ query: unflattenObject(newQuery) })
74
+ }
75
+ }
76
+
77
+ function normalizeQuery(query: LocationQuery): Record<string, any> {
78
+ const flattenedQuery = flattenObject(query)
79
+ const result: Record<string, any> = {}
67
80
 
68
- if (flattenQuery[key] === undefined) {
69
- delete flattenQuery[key]
81
+ for (const key in flattenedQuery) {
82
+ if (!exclude.includes(key)) {
83
+ result[key] = casts[key] ? casts[key](flattenedQuery[key]) : flattenedQuery[key]
70
84
  }
71
- })
85
+ }
72
86
 
73
- await router.replace({ query: unflattenObject(flattenQuery) })
87
+ return result
74
88
  }
75
89
  }
76
90
 
77
- function flattenObject(obj: Record<string, any>, prefix = '') {
78
- return Object.keys(obj).reduce((acc, k) => {
79
- const pre = prefix.length ? `${prefix}.` : ''
80
- if (isPlainObject(obj[k])) {
81
- Object.assign(acc, flattenObject(obj[k], pre + k))
91
+ function flattenObject(obj: Record<string, any>, path: string[] = []): Record<string, any> {
92
+ const result: Record<string, any> = {}
93
+
94
+ for (const key in obj) {
95
+ const value = obj[key]
96
+
97
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
98
+ Object.assign(result, flattenObject(value, [...path, key]))
82
99
  } else {
83
- acc[pre + k] = obj[k]
100
+ result[path.concat(key).join('.')] = value
84
101
  }
85
- return acc
86
- }, {} as Record<string, any>)
87
- }
102
+ }
88
103
 
89
- function unflattenObject(obj: Record<string, any>) {
90
- return Object.keys(obj).reduce((acc, k) => {
91
- const keys = k.split('.')
92
- keys.reduce((a, c, i) => {
93
- if (i === keys.length - 1) {
94
- a[c] = obj[k]
95
- } else {
96
- a[c] = a[c] || {}
97
- }
98
- return a[c]
99
- }, acc)
100
- return acc
101
- }, {} as Record<string, any>)
104
+ return result
102
105
  }
103
106
 
104
- function deepAssign(target: Record<string, any>, source: Record<string, any>) {
105
- const dest = target
106
- const src = source
107
-
108
- if (isPlainObject(src)) {
109
- Object.keys(src).forEach((key) => deepAssignBase(dest, src, key))
110
- } else if (Array.isArray(src)) {
111
- dest.length = src.length
112
- src.forEach((_, key) => deepAssignBase(dest, src, key))
113
- } else {
114
- throw new TypeError('[deepAssign] src must be an object or array')
107
+ function unflattenObject(obj: Record<string, any>): Record<string, any> {
108
+ const result: Record<string, any> = {}
109
+
110
+ for (const key in obj) {
111
+ const value = obj[key]
112
+
113
+ let target = result
114
+ const keys = key.split('.')
115
+
116
+ for (let i = 0; i < keys.length - 1; i++) {
117
+ const k = keys[i]
118
+ target = target[k] = target[k] || {}
119
+ }
120
+
121
+ target[keys[keys.length - 1]] = value
115
122
  }
123
+
124
+ return result
116
125
  }
117
126
 
118
- function deepAssignBase(
119
- dest: Record<string, any>,
120
- src: Record<string, any>,
121
- key: string | number
122
- ) {
123
- if (typeof src[key] === 'object' && src[key] !== null) {
124
- deepAssign(dest[key], src[key])
125
- } else {
126
- dest[key] = src[key]
127
+ function deepAssign(target: Record<string, any>, source: Record<string, any>) {
128
+ for (const key in source) {
129
+ const value = source[key]
130
+
131
+ if (Array.isArray(value)) {
132
+ target[key].splice(0, target[key].length, ...value)
133
+ } else if (value && typeof value === 'object') {
134
+ target[key] = deepAssign(target[key] || {}, value)
135
+ } else {
136
+ target[key] = value
137
+ }
127
138
  }
139
+
140
+ return target
128
141
  }
package/lib/http/Http.ts CHANGED
@@ -165,7 +165,8 @@ export class Http {
165
165
  ) {
166
166
  this.objectToFormData(obj[property], fd, property)
167
167
  } else {
168
- fd.append(formKey, obj[property])
168
+ const value = obj[property] === null ? '' : obj[property]
169
+ fd.append(formKey, value)
169
170
  }
170
171
  })
171
172
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@globalbrain/sefirot",
3
- "version": "3.29.1",
4
- "packageManager": "pnpm@8.15.1",
3
+ "version": "3.30.0",
4
+ "packageManager": "pnpm@8.15.2",
5
5
  "description": "Vue Components for Global Brain Design System.",
6
6
  "author": "Kia Ishii <ka.ishii@globalbrains.com>",
7
7
  "license": "MIT",
@@ -53,10 +53,10 @@
53
53
  "markdown-it": "^14.0.0",
54
54
  "normalize.css": "^8.0.1",
55
55
  "pinia": "^2.1.7",
56
- "postcss": "^8.4.34",
56
+ "postcss": "^8.4.35",
57
57
  "postcss-nested": "^6.0.1",
58
58
  "v-calendar": "^3.1.2",
59
- "vue": "^3.4.15",
59
+ "vue": "^3.4.18",
60
60
  "vue-router": "^4.2.5"
61
61
  },
62
62
  "dependencies": {
@@ -72,7 +72,7 @@
72
72
  },
73
73
  "devDependencies": {
74
74
  "@globalbrain/eslint-config": "^1.5.2",
75
- "@histoire/plugin-vue": "^0.17.9",
75
+ "@histoire/plugin-vue": "^0.17.11",
76
76
  "@iconify-icons/ph": "^1.2.5",
77
77
  "@iconify-icons/ri": "^1.2.10",
78
78
  "@iconify/vue": "^4.1.1",
@@ -80,8 +80,8 @@
80
80
  "@types/body-scroll-lock": "^3.1.2",
81
81
  "@types/lodash-es": "^4.17.12",
82
82
  "@types/markdown-it": "^13.0.7",
83
- "@types/node": "^20.11.16",
84
- "@vitejs/plugin-vue": "^5.0.3",
83
+ "@types/node": "^20.11.17",
84
+ "@vitejs/plugin-vue": "^5.0.4",
85
85
  "@vitest/coverage-v8": "^1.2.2",
86
86
  "@vue/test-utils": "^2.4.4",
87
87
  "@vuelidate/core": "^2.0.3",
@@ -96,16 +96,16 @@
96
96
  "markdown-it": "^14.0.0",
97
97
  "normalize.css": "^8.0.1",
98
98
  "pinia": "^2.1.7",
99
- "postcss": "^8.4.34",
99
+ "postcss": "^8.4.35",
100
100
  "postcss-nested": "^6.0.1",
101
101
  "punycode": "^2.3.1",
102
102
  "release-it": "^17.0.3",
103
103
  "typescript": "~5.3.3",
104
104
  "v-calendar": "^3.1.2",
105
- "vite": "^5.0.12",
106
- "vitepress": "1.0.0-rc.41",
105
+ "vite": "^5.1.1",
106
+ "vitepress": "1.0.0-rc.42",
107
107
  "vitest": "^1.2.2",
108
- "vue": "^3.4.15",
108
+ "vue": "^3.4.18",
109
109
  "vue-router": "^4.2.5",
110
110
  "vue-tsc": "^1.8.27"
111
111
  }