@globalbrain/sefirot 3.39.2 → 3.40.1

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,16 +1,16 @@
1
1
  <script setup lang="ts">
2
- import { onErrorCaptured, ref } from 'vue'
2
+ import { onErrorCaptured, shallowRef } from 'vue'
3
3
  import { useRouter } from 'vue-router'
4
4
 
5
5
  const emit = defineEmits<{
6
6
  (e: 'error', value: any): void
7
7
  }>()
8
8
 
9
- const error = ref<Error | null>(null)
9
+ const error = shallowRef<Error>()
10
10
 
11
11
  onErrorCaptured((e) => {
12
12
  if (import.meta.env.DEV) {
13
- console.error(e)
13
+ console.error(e, { '[cause]': e?.cause })
14
14
  }
15
15
  if (!import.meta.env.SSR) {
16
16
  error.value = e
@@ -23,7 +23,7 @@ async function clearError(options: { redirect?: string } = {}) {
23
23
  if (options.redirect) {
24
24
  await useRouter().replace(options.redirect)
25
25
  }
26
- error.value = null
26
+ error.value = undefined
27
27
  }
28
28
  </script>
29
29
 
@@ -4,12 +4,14 @@ import { computed } from 'vue'
4
4
  const props = defineProps<{
5
5
  cols?: string | number
6
6
  gap?: string | number
7
+ gapRow?: string | number
8
+ gapCol?: string | number
7
9
  }>()
8
10
 
9
11
  const styles = computed(() => {
10
12
  return {
11
- '--grid-template-columns': `repeat(${props.cols ?? 1}, minmax(0, 1fr))`,
12
- '--gap': `${props.gap ?? 0}px`
13
+ '--grid-template-columns': `repeat(${props.cols || 1}, minmax(0, 1fr))`,
14
+ '--gap': `${props.gapRow || props.gap || 0}px ${props.gapCol || props.gap || 0}px`
13
15
  }
14
16
  })
15
17
  </script>
@@ -2,7 +2,6 @@
2
2
  import { onKeyStroke } from '@vueuse/core'
3
3
  import { computed, ref, shallowRef } from 'vue'
4
4
  import { type Position, useTooltip } from '../composables/Tooltip'
5
- import SMarkdown from './SMarkdown.vue'
6
5
 
7
6
  const props = withDefaults(defineProps<{
8
7
  tag?: string
@@ -107,7 +106,7 @@ function onBlur() {
107
106
  <transition name="fade">
108
107
  <span v-show="on" class="container" :class="containerClasses" ref="tip">
109
108
  <span v-if="$slots.text" class="tip"><slot name="text" /></span>
110
- <SMarkdown v-else-if="text" tag="span" class="tip" :content="text" inline />
109
+ <span v-else-if="text" class="tip" v-html="text" />
111
110
  </span>
112
111
  </transition>
113
112
  </component>
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Adapted from
3
+ * @see https://github.com/vuejs/core/blob/76c9c742e9b045d1342f5952866972498e59f00b/packages/runtime-core/src/component.ts
4
+ * @see https://github.com/vuejs/core/blob/bc37258caa2f6f67f4554ab8587aca3798d92124/packages/runtime-core/src/warning.ts
5
+ * @see https://github.com/getsentry/sentry-javascript/blob/2cfb0ef3fa5c40f90c317267a4d10b969994d021/packages/vue/src/errorhandler.ts
6
+ *
7
+ * Original licenses:
8
+ *
9
+ * (c) 2018-present Yuxi (Evan) You and Vue contributors
10
+ * @license MIT
11
+ *
12
+ * (c) 2019 Sentry (https://sentry.io) and individual contributors
13
+ * @license MIT
14
+ */
15
+
16
+ import * as Sentry from '@sentry/browser'
17
+ import { pauseTracking, resetTracking } from '@vue/reactivity'
18
+ import {
19
+ type ComponentInternalInstance,
20
+ type ComponentOptions,
21
+ type ComponentPublicInstance,
22
+ type ConcreteComponent,
23
+ type MaybeRefOrGetter,
24
+ type VNode,
25
+ toValue
26
+ } from 'vue'
27
+ import { useError } from '../stores/Error'
28
+ import { isFunction } from '../support/Utils'
29
+
30
+ export interface User {
31
+ id?: string | number
32
+ email?: string
33
+ username?: string
34
+ }
35
+
36
+ type TraceEntry = { vnode: VNode; recurseCount: number }
37
+
38
+ type ComponentTraceStack = TraceEntry[]
39
+
40
+ const classifyRE = /(?:^|[-_])(\w)/g
41
+ function classify(str: string): string {
42
+ return str.replace(classifyRE, (c) => c.toUpperCase()).replace(/[-_]/g, '')
43
+ }
44
+
45
+ function getComponentName(Component: ConcreteComponent): string | undefined {
46
+ return isFunction(Component)
47
+ ? Component.displayName || Component.name
48
+ : Component.name || Component.__name
49
+ }
50
+
51
+ function formatComponentName(instance: ComponentInternalInstance | null): string {
52
+ if (!instance) {
53
+ return 'Anonymous'
54
+ }
55
+
56
+ const Component = instance.type
57
+ const isRoot = instance.parent == null
58
+
59
+ let name = getComponentName(Component)
60
+ if (!name && Component.__file) {
61
+ const match = Component.__file.match(/([^/\\]+)\.\w+$/)
62
+ if (match) {
63
+ name = match[1]
64
+ }
65
+ }
66
+
67
+ if (!name && instance && instance.parent) {
68
+ const inferFromRegistry = (registry: Record<string, unknown> | undefined) => {
69
+ for (const key in registry) {
70
+ if (registry[key] === Component) {
71
+ return key
72
+ }
73
+ }
74
+ }
75
+ name
76
+ = inferFromRegistry(
77
+ // @ts-expect-error internal api
78
+ instance.components || (instance.parent.type as ComponentOptions).components
79
+ ) || inferFromRegistry(instance.appContext.components)
80
+ }
81
+
82
+ return name ? classify(name) : isRoot ? 'App' : 'Anonymous'
83
+ }
84
+
85
+ function getComponentTrace(currentVNode: VNode | null): ComponentTraceStack {
86
+ if (!currentVNode) {
87
+ return []
88
+ }
89
+
90
+ const normalizedStack: ComponentTraceStack = []
91
+
92
+ while (currentVNode) {
93
+ const last = normalizedStack[0]
94
+ if (last && last.vnode === currentVNode) {
95
+ last.recurseCount++
96
+ } else {
97
+ normalizedStack.push({ vnode: currentVNode, recurseCount: 0 })
98
+ }
99
+ const parentInstance: ComponentInternalInstance | null
100
+ = currentVNode.component && currentVNode.component.parent
101
+ currentVNode = parentInstance && parentInstance.vnode
102
+ }
103
+
104
+ return normalizedStack
105
+ }
106
+
107
+ function formatTrace(instance: ComponentInternalInstance | null): string {
108
+ return getComponentTrace(instance && instance.vnode)
109
+ .map(formatTraceEntry)
110
+ .join('\n')
111
+ }
112
+
113
+ function formatTraceEntry({ vnode, recurseCount }: TraceEntry): string {
114
+ return `at <${formatComponentName(vnode.component)}${
115
+ vnode.props ? ` ${formatProps(vnode.props)}` : ''
116
+ }>${
117
+ recurseCount > 0 ? ` ... (${recurseCount} recursive call${recurseCount > 1 ? 's' : ''})` : ''
118
+ }`
119
+ }
120
+
121
+ function formatProps(props: Record<string, unknown>): string {
122
+ return Object.keys(props)
123
+ .map((key) => {
124
+ let value = props[key]
125
+ if (typeof value === 'string') {
126
+ value = JSON.stringify(value)
127
+ return `${key}=${value}`
128
+ } else if (typeof value === 'function') {
129
+ return `:${key}="fn${value.name ? `<${value.name}>` : ''}"`
130
+ } else if (typeof value !== 'object' || value === null) {
131
+ return `:${key}="${String(value)}"`
132
+ } else {
133
+ return `${key}="..."`
134
+ }
135
+ })
136
+ .join(' ')
137
+ }
138
+
139
+ export function useErrorHandler({
140
+ dsn,
141
+ environment,
142
+ user
143
+ }: {
144
+ dsn?: string
145
+ environment?: string
146
+ user?: MaybeRefOrGetter<User | null>
147
+ }) {
148
+ const error = useError()
149
+
150
+ const enabled = !!dsn && import.meta.env.PROD
151
+
152
+ // No need to manually report these errors as they are automatically
153
+ // captured by Sentry. And skip registering these in SSR. We can't use
154
+ // onMounted because it's not available outside component lifecycle.
155
+ if (typeof document !== 'undefined') {
156
+ addEventListener('error', (event) => {
157
+ error.set(event.error)
158
+ })
159
+ addEventListener('unhandledrejection', (event) => {
160
+ error.set(event.reason)
161
+ })
162
+ }
163
+
164
+ if (enabled) {
165
+ Sentry.init({
166
+ dsn,
167
+ environment,
168
+ ignoreErrors: [
169
+ 'ResizeObserver loop limit exceeded',
170
+ 'ResizeObserver loop completed with undelivered notifications'
171
+ ]
172
+ })
173
+ }
174
+
175
+ return function errorHandler(
176
+ error: any,
177
+ instance: ComponentPublicInstance | null = null,
178
+ info: string = ''
179
+ ) {
180
+ error.set(error)
181
+
182
+ if (enabled) {
183
+ pauseTracking()
184
+
185
+ let userValue = toValue(user) || null
186
+ if (!userValue?.id && !userValue?.email && !userValue?.username) {
187
+ userValue = null
188
+ }
189
+
190
+ if (![403, 404].includes(error?.cause?.statusCode)) {
191
+ const $ = instance && instance.$
192
+ const metadata = $ && {
193
+ componentName: formatComponentName($),
194
+ lifecycleHook: info,
195
+ trace: formatTrace($),
196
+ propsData: $ && $.props
197
+ }
198
+
199
+ setTimeout(() => {
200
+ Sentry.withScope((scope) => {
201
+ scope.setUser(userValue)
202
+ scope.setContext('vue', metadata)
203
+ Sentry.captureException(error)
204
+ })
205
+ })
206
+ }
207
+
208
+ resetTracking()
209
+ }
210
+ }
211
+ }
@@ -0,0 +1,5 @@
1
+ export class AuthorizationError extends Error {
2
+ constructor() {
3
+ super('Authorization failed', { cause: { statusCode: 403 } })
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ export class PageNotFoundError extends Error {
2
+ constructor() {
3
+ super('Page not found', { cause: { statusCode: 404 } })
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ export class UnexpectedError extends Error {
2
+ constructor() {
3
+ super('Unexpected error occurred', { cause: { statusCode: 500 } })
4
+ }
5
+ }
@@ -0,0 +1,3 @@
1
+ export { AuthorizationError } from './AuthorizationError'
2
+ export { PageNotFoundError } from './PageNotFoundError'
3
+ export { UnexpectedError } from './UnexpectedError'
@@ -0,0 +1,24 @@
1
+ import { defineStore } from 'pinia'
2
+ import { shallowRef } from 'vue'
3
+ import { useRouter } from 'vue-router'
4
+
5
+ export const useError = defineStore('sefirot-error', () => {
6
+ const data = shallowRef<unknown>()
7
+
8
+ function set(err: unknown): void {
9
+ data.value = err
10
+ }
11
+
12
+ async function clear(options: { redirect?: string } = {}): Promise<void> {
13
+ if (options.redirect) {
14
+ await useRouter().replace(options.redirect)
15
+ }
16
+ data.value = undefined
17
+ }
18
+
19
+ return {
20
+ data,
21
+ set,
22
+ clear
23
+ }
24
+ })
@@ -17,7 +17,7 @@ export interface SnackbarAction {
17
17
  onClick(): void
18
18
  }
19
19
 
20
- export const useSnackbars = defineStore('snackbars', () => {
20
+ export const useSnackbars = defineStore('sefirot-snackbars', () => {
21
21
  const maxItemSize = 4
22
22
 
23
23
  let nextId = 0
@@ -21,3 +21,8 @@ export function isObject(value: unknown): value is Record<string, any> {
21
21
  export function isFile(value: unknown): value is File {
22
22
  return value instanceof File
23
23
  }
24
+
25
+ // eslint-disable-next-line @typescript-eslint/ban-types
26
+ export function isFunction(value: unknown): value is Function {
27
+ return typeof value === 'function'
28
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@globalbrain/sefirot",
3
- "version": "3.39.2",
4
- "packageManager": "pnpm@8.15.4",
3
+ "version": "3.40.1",
4
+ "packageManager": "pnpm@8.15.5",
5
5
  "description": "Vue Components for Global Brain Design System.",
6
6
  "author": "Kia Ishii <ka.ishii@globalbrains.com>",
7
7
  "license": "MIT",
@@ -45,35 +45,37 @@
45
45
  "@types/body-scroll-lock": "^3.1.2",
46
46
  "@types/lodash-es": "^4.17.12",
47
47
  "@types/markdown-it": "^13.0.7",
48
+ "@vue/reactivity": "^3.4.21",
48
49
  "@vuelidate/core": "^2.0.3",
49
50
  "@vuelidate/validators": "^2.0.4",
50
- "@vueuse/core": "^10.8.0",
51
+ "@vueuse/core": "^10.9.0",
51
52
  "body-scroll-lock": "4.0.0-beta.0",
52
53
  "fuse.js": "^7.0.0",
53
54
  "lodash-es": "^4.17.21",
54
- "markdown-it": "^14.0.0",
55
+ "markdown-it": "^14.1.0",
55
56
  "normalize.css": "^8.0.1",
56
57
  "pinia": "^2.1.7",
57
- "postcss": "^8.4.35",
58
+ "postcss": "^8.4.38",
58
59
  "postcss-nested": "^6.0.1",
59
60
  "v-calendar": "^3.1.2",
60
- "vue": "^3.4.20",
61
+ "vue": "^3.4.21",
61
62
  "vue-router": "^4.3.0"
62
63
  },
63
64
  "dependencies": {
65
+ "@sentry/browser": "^8.0.0-alpha.7",
64
66
  "@tanstack/vue-virtual": "3.0.0-beta.62",
65
67
  "@tinyhttp/content-disposition": "^2.2.0",
66
68
  "@tinyhttp/cookie": "^2.1.0",
67
69
  "@types/file-saver": "^2.0.7",
68
- "@types/qs": "^6.9.11",
70
+ "@types/qs": "^6.9.14",
69
71
  "dayjs": "^1.11.10",
70
72
  "file-saver": "^2.0.5",
71
- "ofetch": "^1.3.3",
72
- "qs": "^6.11.2"
73
+ "ofetch": "^1.3.4",
74
+ "qs": "^6.12.0"
73
75
  },
74
76
  "devDependencies": {
75
- "@globalbrain/eslint-config": "^1.5.2",
76
- "@histoire/plugin-vue": "^0.16.5",
77
+ "@globalbrain/eslint-config": "^1.6.0",
78
+ "@histoire/plugin-vue": "^0.17.14",
77
79
  "@iconify-icons/ph": "^1.2.5",
78
80
  "@iconify-icons/ri": "^1.2.10",
79
81
  "@iconify/vue": "^4.1.1",
@@ -81,32 +83,33 @@
81
83
  "@types/body-scroll-lock": "^3.1.2",
82
84
  "@types/lodash-es": "^4.17.12",
83
85
  "@types/markdown-it": "^13.0.7",
84
- "@types/node": "^20.11.20",
86
+ "@types/node": "^20.11.30",
85
87
  "@vitejs/plugin-vue": "^5.0.4",
86
- "@vitest/coverage-v8": "^1.3.1",
87
- "@vue/test-utils": "^2.4.4",
88
+ "@vitest/coverage-v8": "^1.4.0",
89
+ "@vue/reactivity": "^3.4.21",
90
+ "@vue/test-utils": "^2.4.5",
88
91
  "@vuelidate/core": "^2.0.3",
89
92
  "@vuelidate/validators": "^2.0.4",
90
- "@vueuse/core": "^10.8.0",
93
+ "@vueuse/core": "^10.9.0",
91
94
  "body-scroll-lock": "4.0.0-beta.0",
92
95
  "eslint": "^8.57.0",
93
96
  "fuse.js": "^7.0.0",
94
- "happy-dom": "^13.6.0",
95
- "histoire": "^0.16.5",
97
+ "happy-dom": "^14.3.9",
98
+ "histoire": "^0.17.14",
96
99
  "lodash-es": "^4.17.21",
97
- "markdown-it": "^14.0.0",
100
+ "markdown-it": "^14.1.0",
98
101
  "normalize.css": "^8.0.1",
99
102
  "pinia": "^2.1.7",
100
- "postcss": "^8.4.35",
103
+ "postcss": "^8.4.38",
101
104
  "postcss-nested": "^6.0.1",
102
105
  "punycode": "^2.3.1",
103
106
  "release-it": "^17.1.1",
104
- "typescript": "~5.3.3",
107
+ "typescript": "~5.4.3",
105
108
  "v-calendar": "^3.1.2",
106
- "vite": "^5.1.4",
107
- "vitepress": "1.0.0-rc.44",
108
- "vitest": "^1.3.1",
109
- "vue": "^3.4.20",
109
+ "vite": "^5.2.6",
110
+ "vitepress": "1.0.1",
111
+ "vitest": "^1.4.0",
112
+ "vue": "^3.4.21",
110
113
  "vue-router": "^4.3.0",
111
114
  "vue-tsc": "^1.8.27"
112
115
  }