@svgrid/enterprise 2.2.1 → 2.3.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.
- package/README.md +95 -81
- package/dist/cdn/svgrid-enterprise.svelte-external.js +26642 -11252
- package/dist/designer/assets/GridMenus-BtWVk9Ab.js +7 -0
- package/dist/designer/assets/SvGridChartPanel-DWlBO2SD.js +10 -0
- package/dist/designer/assets/SvGridChartView-8c8Mb4j8.js +1 -0
- package/dist/designer/assets/SvGridChartView-DX9HfkBR.css +1 -0
- package/dist/designer/assets/index-DsDgp9Xq.js +78758 -0
- package/dist/designer/assets/index-tTY_Dx4P.css +1 -0
- package/dist/designer/assets/jszip.min-fkJdmAmj.js +2 -0
- package/dist/designer/assets/pdfmake-DeCsnyl9.js +242 -0
- package/dist/designer/assets/smart.export-BZlSCE8T.js +35 -0
- package/dist/designer/assets/vfs_fonts-eX2NpmfX.js +1 -0
- package/dist/designer/index.html +13 -0
- package/dist/node/studio.js +15304 -2708
- package/package.json +35 -11
- package/src/SvAlertRuleEditor.svelte +294 -0
- package/src/SvAlertsManager.svelte +210 -0
- package/src/SvAlertsPanel.svelte +129 -0
- package/src/SvBoard.svelte +3 -1
- package/src/SvExpressionEditor.svelte +341 -0
- package/src/SvGridAlerts.dom.test.ts +113 -0
- package/src/SvGridAlerts.svelte +265 -0
- package/src/SvGridBoard.svelte +2358 -0
- package/src/SvGridEditPanel.svelte +849 -794
- package/src/SvGridScheduler.svelte +5358 -4410
- package/src/SvPivotDesigner.svelte +3 -3
- package/src/SvRecordDetail.svelte +6 -2
- package/src/SvSchedule.svelte +3 -1
- package/src/{ai-export-pdf.test.ts → ai-export-pdf.dom.test.ts} +77 -74
- package/src/{ai-export-xlsx.test.ts → ai-export-xlsx.dom.test.ts} +90 -87
- package/src/{ai-export.test.ts → ai-export.dom.test.ts} +114 -110
- package/src/alerts/alert-engine-attach.ts +165 -0
- package/src/alerts/alert-engine.test.ts +135 -0
- package/src/alerts/alert-engine.ts +260 -0
- package/src/alerts/alert-formats.test.ts +87 -0
- package/src/alerts/alert-formats.ts +77 -0
- package/src/alerts/alert-observer.test.ts +189 -0
- package/src/alerts/alert-observer.ts +208 -0
- package/src/alerts/alert-scheduler.ts +96 -0
- package/src/alerts/alert-storage.test.ts +54 -0
- package/src/alerts/alert-storage.ts +116 -0
- package/src/alerts/alert-store.svelte.ts +80 -0
- package/src/alerts/alert-types.ts +94 -0
- package/src/alerts.ts +28 -0
- package/src/board.dom.test.ts +941 -0
- package/src/board.ts +36 -0
- package/src/export-ooxml.ts +4 -0
- package/src/export-xls.ts +3 -0
- package/src/export.ts +6 -0
- package/src/expressions/evaluate.test.ts +111 -0
- package/src/expressions/evaluate.ts +237 -0
- package/src/expressions/expression-columns.ts +133 -0
- package/src/expressions/expression-types.ts +84 -0
- package/src/expressions/parse.test.ts +106 -0
- package/src/expressions/parse.ts +610 -0
- package/src/import.test.ts +1 -1
- package/src/import.ts +3 -1
- package/src/index.ts +192 -46
- package/src/install.ts +12 -2
- package/src/pivot-enable.ts +44 -0
- package/src/pivot.test.ts +0 -1
- package/src/scheduler-assignments.test.ts +97 -0
- package/src/scheduler-assignments.ts +134 -0
- package/src/scheduler-axis.test.ts +108 -0
- package/src/scheduler-axis.ts +238 -0
- package/src/scheduler-booking.test.ts +57 -0
- package/src/scheduler-booking.ts +63 -0
- package/src/scheduler-config.ts +179 -0
- package/src/scheduler-dependencies.test.ts +155 -0
- package/src/scheduler-dependencies.ts +223 -0
- package/src/scheduler-freebusy.test.ts +41 -0
- package/src/scheduler-freebusy.ts +36 -0
- package/src/scheduler-heatmap.test.ts +39 -0
- package/src/scheduler-heatmap.ts +55 -0
- package/src/scheduler-resource-tree.test.ts +84 -0
- package/src/scheduler-resource-tree.ts +107 -0
- package/src/scheduler-slots.test.ts +53 -0
- package/src/scheduler-slots.ts +94 -0
- package/src/scheduler-summary.test.ts +47 -0
- package/src/scheduler-summary.ts +81 -0
- package/src/schema-designer.ts +1 -1
- package/src/sources/index.ts +1 -1
- package/src/sources/introspect-supabase.test.ts +13 -1
- package/src/sources/introspect-supabase.ts +20 -0
- package/src/studio/copilot-core.test.ts +45 -0
- package/src/studio/copilot-core.ts +65 -0
- package/src/studio/deploy-cli.test.ts +56 -0
- package/src/studio/deploy-cli.ts +100 -0
- package/src/studio/emit-project.test.ts +800 -24
- package/src/studio/emit-project.ts +1307 -135
- package/src/studio/emit-schema.test.ts +17 -0
- package/src/studio/emit-schema.ts +182 -41
- package/src/studio/index.ts +59 -1
- package/src/studio/init-flow.test.ts +239 -0
- package/src/studio/init-flow.ts +358 -0
- package/src/studio/introspect-openapi.test.ts +84 -0
- package/src/studio/introspect-openapi.ts +252 -0
- package/src/studio/project.test.ts +57 -1
- package/src/studio/project.ts +302 -8
- package/src/studio/samples/crm.ts +282 -258
- package/src/studio/samples/datasets.test.ts +84 -0
- package/src/studio/samples/datasets.ts +340 -0
- package/src/studio/samples/fleet.ts +213 -186
- package/src/studio/samples/insurance.ts +223 -195
- package/src/studio/samples/inventory.ts +213 -182
- package/src/studio/samples/live-data.test.ts +99 -98
- package/src/studio/samples/live-data.ts +8 -10
- package/src/studio/samples/projects.ts +212 -190
- package/src/studio/samples/samples.test.ts +268 -216
- package/src/studio/samples/shared.ts +22 -145
- package/src/studio/samples/starter.ts +251 -0
- package/src/studio/samples/support.ts +209 -184
- package/src/studio/screen-suites.test.ts +226 -0
- package/src/studio/screen-suites.ts +445 -0
- package/src/studio/ui-components-surface.test.ts +185 -0
- package/src/studio/ui-components.generated.ts +7154 -0
- package/src/studio/ui-components.ts +273 -3
- package/src/sveltekit/index.ts +1 -0
- package/src/sveltekit/sql-source.test.ts +13 -0
- package/src/sveltekit/sql-source.ts +12 -7
- package/src/sveltekit/transport-scope.test.ts +125 -0
- package/src/sveltekit/transport.ts +76 -2
- package/src/upgrade-prompt.ts +2 -2
- package/src/watermark.ts +2 -2
- package/src/ai.test.ts +0 -522
- package/src/ai.ts +0 -1388
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
<script lang="ts" generics="TRow extends Record<string, unknown>">
|
|
2
|
+
/**
|
|
3
|
+
* SvGridAlerts - the runtime overlay. It observes the grid's data, runs the
|
|
4
|
+
* alert engine off the render frame (never blocking paint), and:
|
|
5
|
+
* - raises toasts + records fired events in the shared log (the badge count),
|
|
6
|
+
* - emits `formats` (bindable) that the consumer spreads into the grid's
|
|
7
|
+
* `conditionalFormats` so matching rows light up and fired cells flash,
|
|
8
|
+
* - hosts the alerts badge, the fired-alerts panel, and the rule manager.
|
|
9
|
+
*
|
|
10
|
+
* All evaluation goes through `createAlertObserver`, which defers work to a
|
|
11
|
+
* post-paint frame and coalesces bursts. Two ways to feed it:
|
|
12
|
+
* - WATCH (default) - the overlay reactively `scan`s `data` on change. Deferred,
|
|
13
|
+
* and O(changed) via an index-diff (Svelte 5 replaces changed rows immutably).
|
|
14
|
+
* This covers streaming feeds / a programmatic `data = ...`, which the grid's
|
|
15
|
+
* `onCellValueChange` (edits only) never signals.
|
|
16
|
+
* - PUSH (`watch={false}`) - a consumer that already knows what changed drives
|
|
17
|
+
* `handle.pushChanged(changedRows)` (via `bind:this` or `onReady`), so cost is
|
|
18
|
+
* O(rows that changed) regardless of dataset size - the path for 100k+ feeds.
|
|
19
|
+
*/
|
|
20
|
+
import { onMount, untrack } from 'svelte'
|
|
21
|
+
import type { ConditionalFormat } from '@svgrid/grid/format'
|
|
22
|
+
import { createAlertEngine } from './alerts/alert-engine'
|
|
23
|
+
import { createAlertObserver } from './alerts/alert-observer'
|
|
24
|
+
import { applyAlertEvents, type FlashTarget } from './alerts/alert-engine-attach'
|
|
25
|
+
import { rulesToConditionalFormats } from './alerts/alert-formats'
|
|
26
|
+
import { alertStore } from './alerts/alert-store.svelte'
|
|
27
|
+
import {
|
|
28
|
+
createAlertRules,
|
|
29
|
+
localStorageAlertRules,
|
|
30
|
+
memoryAlertRules,
|
|
31
|
+
type AlertRulesManager,
|
|
32
|
+
} from './alerts/alert-storage'
|
|
33
|
+
import SvAlertsPanel from './SvAlertsPanel.svelte'
|
|
34
|
+
import SvAlertsManager from './SvAlertsManager.svelte'
|
|
35
|
+
import type { ExprColumn } from './expressions/expression-columns'
|
|
36
|
+
import type { AlertEvent, AlertRule } from './alerts/alert-types'
|
|
37
|
+
|
|
38
|
+
/** The imperative handle handed to push-mode consumers. */
|
|
39
|
+
type AlertsHandle<T> = {
|
|
40
|
+
/** Feed only the rows that changed (O(changed) evaluation, deferred). */
|
|
41
|
+
pushChanged: (rows: ReadonlyArray<T>) => void
|
|
42
|
+
/** Silently re-arm edges from the full row set (after a data reset). */
|
|
43
|
+
reseed: (rows: ReadonlyArray<T>) => void
|
|
44
|
+
/** Run any pending evaluation immediately. */
|
|
45
|
+
flush: () => void
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type Props = {
|
|
49
|
+
/** The same rows bound to the grid. In watch mode, observed for changes. */
|
|
50
|
+
data?: ReadonlyArray<TRow>
|
|
51
|
+
/** Columns offered in the rule/expression editors. */
|
|
52
|
+
columns: ReadonlyArray<ExprColumn>
|
|
53
|
+
/** Stable row identity (defaults to `row.id`). */
|
|
54
|
+
getRowId?: (row: TRow) => string
|
|
55
|
+
/** Bindable output: spread into `<SvGrid conditionalFormats={[...]}>`. */
|
|
56
|
+
formats?: ConditionalFormat<TRow>[]
|
|
57
|
+
/** Seed rules when storage is empty. */
|
|
58
|
+
rules?: AlertRule[]
|
|
59
|
+
/** Persist + share rules under this localStorage key (else in-memory). */
|
|
60
|
+
storageKey?: string
|
|
61
|
+
getValue?: (row: TRow, columnId: string) => unknown
|
|
62
|
+
locale?: string | ReadonlyArray<string>
|
|
63
|
+
/**
|
|
64
|
+
* Reactively scan `data` for changes (default). Set `false` when a consumer
|
|
65
|
+
* drives evaluation through `pushChanged` (large / streaming feeds) - the
|
|
66
|
+
* overlay then never reads `data` on the hot path.
|
|
67
|
+
*/
|
|
68
|
+
watch?: boolean
|
|
69
|
+
/** Receive the push-mode handle on mount (alternative to `bind:this`). */
|
|
70
|
+
onReady?: (handle: AlertsHandle<TRow>) => void
|
|
71
|
+
/**
|
|
72
|
+
* Rate-limit toasts to at most one per rule per this many ms. Every event is
|
|
73
|
+
* still logged (the badge stays accurate); this only stops a busy live feed
|
|
74
|
+
* from turning into a toast storm. `0` (default) = no throttle.
|
|
75
|
+
*/
|
|
76
|
+
toastCooldownMs?: number
|
|
77
|
+
/** Show the built-in badge + manage buttons. */
|
|
78
|
+
controls?: boolean
|
|
79
|
+
panelOpen?: boolean
|
|
80
|
+
managerOpen?: boolean
|
|
81
|
+
/** Called when the user clicks "Go to row" in the panel. */
|
|
82
|
+
onJump?: (event: AlertEvent) => void
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let {
|
|
86
|
+
data = [],
|
|
87
|
+
columns,
|
|
88
|
+
getRowId = (row) => String((row as { id?: unknown }).id ?? ''),
|
|
89
|
+
formats = $bindable<ConditionalFormat<TRow>[]>([]),
|
|
90
|
+
rules: initialRules = [],
|
|
91
|
+
storageKey,
|
|
92
|
+
getValue,
|
|
93
|
+
locale,
|
|
94
|
+
watch = true,
|
|
95
|
+
onReady,
|
|
96
|
+
toastCooldownMs = 0,
|
|
97
|
+
controls = true,
|
|
98
|
+
panelOpen = $bindable(false),
|
|
99
|
+
managerOpen = $bindable(false),
|
|
100
|
+
onJump,
|
|
101
|
+
}: Props = $props()
|
|
102
|
+
|
|
103
|
+
const manager: AlertRulesManager = createAlertRules(
|
|
104
|
+
storageKey ? localStorageAlertRules(storageKey) : memoryAlertRules(),
|
|
105
|
+
)
|
|
106
|
+
// Seed once if the store is empty.
|
|
107
|
+
if (initialRules.length && manager.list().length === 0) {
|
|
108
|
+
for (const r of initialRules) manager.save(r)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let rules = $state<AlertRule[]>(manager.list())
|
|
112
|
+
let rulesVersion = $state(0)
|
|
113
|
+
let flashFormats = $state<ConditionalFormat<TRow>[]>([])
|
|
114
|
+
|
|
115
|
+
const engine = createAlertEngine<TRow>({ rules, getRowId, getValue, locale })
|
|
116
|
+
|
|
117
|
+
// The observer owns all scheduling + change tracking. It runs evaluation on a
|
|
118
|
+
// post-paint frame (never inside the grid's render flush) and only clones prev
|
|
119
|
+
// snapshots when a rule actually reads prev (`relativeChange`). Fired events
|
|
120
|
+
// come back here as a batch to raise toasts, log, and flash cells.
|
|
121
|
+
// Per-rule toast cooldown: suppress the visible toast (not the log) when a rule
|
|
122
|
+
// fired within `toastCooldownMs`. Keeps a busy feed from storming the screen.
|
|
123
|
+
const lastToastAt = new Map<string, number>()
|
|
124
|
+
function allowToast(e: AlertEvent): boolean {
|
|
125
|
+
if (!toastCooldownMs) return true
|
|
126
|
+
const now = Date.now()
|
|
127
|
+
const last = lastToastAt.get(e.ruleId) ?? -Infinity
|
|
128
|
+
if (now - last < toastCooldownMs) return false
|
|
129
|
+
lastToastAt.set(e.ruleId, now)
|
|
130
|
+
return true
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const observer = createAlertObserver<TRow>({
|
|
134
|
+
engine,
|
|
135
|
+
getRowId,
|
|
136
|
+
schedule: 'raf',
|
|
137
|
+
onEvents: (events) => {
|
|
138
|
+
const { flashes } = applyAlertEvents(events, getRowId, { shouldToast: allowToast })
|
|
139
|
+
for (const f of flashes) addFlash(f)
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
let seededVersion = -1
|
|
144
|
+
|
|
145
|
+
function addFlash(target: FlashTarget) {
|
|
146
|
+
const fmt: ConditionalFormat<TRow> = {
|
|
147
|
+
type: 'rule',
|
|
148
|
+
...(target.columnId ? { columns: [target.columnId] } : {}),
|
|
149
|
+
when: ({ row }: { value: unknown; row: TRow }) => getRowId(row) === target.rowId,
|
|
150
|
+
background: 'var(--sg-cell-flash, #fde68a)',
|
|
151
|
+
}
|
|
152
|
+
flashFormats = [...flashFormats, fmt]
|
|
153
|
+
setTimeout(() => {
|
|
154
|
+
flashFormats = flashFormats.filter((f) => f !== fmt)
|
|
155
|
+
}, 900)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Seed + rule changes. Depends ONLY on `rulesVersion` so it never re-runs on a
|
|
159
|
+
// data tick; the seed reads `data` untracked. This silently arms edges so
|
|
160
|
+
// pre-existing matches under the (new) rules do not spam toasts.
|
|
161
|
+
$effect(() => {
|
|
162
|
+
const version = rulesVersion
|
|
163
|
+
if (version === seededVersion) return
|
|
164
|
+
observer.setRules(rules)
|
|
165
|
+
observer.seed(untrack(() => data))
|
|
166
|
+
seededVersion = version
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// Watch mode: reactively scan `data` on every change (deferred + O(changed)).
|
|
170
|
+
// When `watch` is false the effect returns before touching `data`, so it takes
|
|
171
|
+
// NO data dependency and never re-runs on a tick - the push-mode hot path.
|
|
172
|
+
$effect(() => {
|
|
173
|
+
if (!watch) return
|
|
174
|
+
const rows = data
|
|
175
|
+
observer.scan(() => rows)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// Persistent (predicate-driven) styling + transient flashes -> the grid.
|
|
179
|
+
$effect(() => {
|
|
180
|
+
formats = [...rulesToConditionalFormats<TRow>(rules, { getValue, locale }), ...flashFormats]
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
// --- Push-mode API (bind:this or onReady) --------------------------------
|
|
184
|
+
export function pushChanged(rows: ReadonlyArray<TRow>) {
|
|
185
|
+
observer.pushChanged(rows)
|
|
186
|
+
}
|
|
187
|
+
export function reseed(rows: ReadonlyArray<TRow>) {
|
|
188
|
+
observer.seed(rows)
|
|
189
|
+
}
|
|
190
|
+
export function flush() {
|
|
191
|
+
observer.flush()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
onMount(() => {
|
|
195
|
+
onReady?.({ pushChanged, reseed, flush })
|
|
196
|
+
return () => observer.detach()
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
function onRulesChanged(next: AlertRule[]) {
|
|
200
|
+
rules = next
|
|
201
|
+
rulesVersion += 1
|
|
202
|
+
}
|
|
203
|
+
</script>
|
|
204
|
+
|
|
205
|
+
{#if controls}
|
|
206
|
+
<div class="sg-alerts-controls">
|
|
207
|
+
<button
|
|
208
|
+
type="button"
|
|
209
|
+
class="sg-alerts-bell"
|
|
210
|
+
class:sg-active={alertStore.activeCount > 0}
|
|
211
|
+
onclick={() => (panelOpen = true)}
|
|
212
|
+
aria-label={`Open alerts${alertStore.activeCount ? ` (${alertStore.activeCount} unread)` : ''}`}>
|
|
213
|
+
<svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
214
|
+
<path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9" />
|
|
215
|
+
<path d="M13.7 21a2 2 0 0 1-3.4 0" />
|
|
216
|
+
</svg>
|
|
217
|
+
{#if alertStore.activeCount > 0}
|
|
218
|
+
<span class="sg-alerts-count">{alertStore.activeCount > 99 ? '99+' : alertStore.activeCount}</span>
|
|
219
|
+
{/if}
|
|
220
|
+
</button>
|
|
221
|
+
<button type="button" class="sg-alerts-manage" onclick={() => (managerOpen = true)}>
|
|
222
|
+
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
223
|
+
<path d="M12 3v3m0 12v3M5.6 5.6l2.1 2.1m8.6 8.6l2.1 2.1M3 12h3m12 0h3M5.6 18.4l2.1-2.1m8.6-8.6l2.1-2.1" />
|
|
224
|
+
</svg>
|
|
225
|
+
Manage alerts
|
|
226
|
+
</button>
|
|
227
|
+
</div>
|
|
228
|
+
{/if}
|
|
229
|
+
|
|
230
|
+
<SvAlertsPanel bind:open={panelOpen} onJump={(e) => onJump?.(e)} />
|
|
231
|
+
<SvAlertsManager bind:open={managerOpen} {manager} {columns} rows={data} onChange={onRulesChanged} />
|
|
232
|
+
|
|
233
|
+
<style>
|
|
234
|
+
/* Theme-agnostic: these controls render OUTSIDE the grid, where the full
|
|
235
|
+
--sg-* token set may not be in scope. Derive every surface from
|
|
236
|
+
`currentColor` + `inherit` so text is always the page's readable colour. */
|
|
237
|
+
.sg-alerts-controls { display: inline-flex; align-items: center; gap: 8px; font-family: var(--sg-font, inherit); color: inherit; }
|
|
238
|
+
.sg-alerts-bell,
|
|
239
|
+
.sg-alerts-manage {
|
|
240
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 7px;
|
|
241
|
+
height: 34px; border-radius: 9px; color: inherit; cursor: pointer;
|
|
242
|
+
border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
|
|
243
|
+
background: color-mix(in srgb, currentColor 5%, transparent);
|
|
244
|
+
transition: background 0.14s ease, border-color 0.14s ease, box-shadow 0.14s ease, transform 0.08s ease;
|
|
245
|
+
}
|
|
246
|
+
.sg-alerts-bell:hover,
|
|
247
|
+
.sg-alerts-manage:hover { background: color-mix(in srgb, currentColor 13%, transparent); border-color: color-mix(in srgb, currentColor 36%, transparent); }
|
|
248
|
+
.sg-alerts-bell:active,
|
|
249
|
+
.sg-alerts-manage:active { transform: translateY(0.5px); }
|
|
250
|
+
.sg-alerts-bell:focus-visible,
|
|
251
|
+
.sg-alerts-manage:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--sg-accent, #4f46e5) 35%, transparent); }
|
|
252
|
+
|
|
253
|
+
.sg-alerts-bell { position: relative; width: 34px; padding: 0; }
|
|
254
|
+
.sg-alerts-bell.sg-active { color: var(--sg-accent, #4f46e5); border-color: color-mix(in srgb, var(--sg-accent, #4f46e5) 50%, transparent); background: color-mix(in srgb, var(--sg-accent, #4f46e5) 12%, transparent); }
|
|
255
|
+
.sg-alerts-count {
|
|
256
|
+
position: absolute; top: -6px; right: -6px; min-width: 17px; height: 17px; padding: 0 4px;
|
|
257
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
258
|
+
font-size: 10.5px; font-weight: 700; line-height: 1; color: #fff;
|
|
259
|
+
background: var(--sg-danger, #dc2626); border-radius: 999px;
|
|
260
|
+
border: 1.5px solid color-mix(in srgb, currentColor 100%, transparent); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.sg-alerts-manage { padding: 0 13px 0 11px; font-size: 13px; font-weight: 500; }
|
|
264
|
+
.sg-alerts-manage svg { opacity: 0.6; }
|
|
265
|
+
</style>
|