@keyfunc/keybricks-ds 2.4.10 → 2.4.12

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/DEPLOY.md CHANGED
@@ -55,7 +55,7 @@ deploys the documentation site. Watch it under *Build → Pipelines*.
55
55
  **5. Cut the first release.**
56
56
 
57
57
  ```bash
58
- git tag v2.4.10 && git push --tags
58
+ git tag v2.4.12 && git push --tags
59
59
  ```
60
60
 
61
61
  That tag runs `generated:check` → `version:check` → `npm publish`. Confirm under *Deploy →
@@ -187,7 +187,7 @@ GitLab injects `CI_JOB_TOKEN` — use that instead and no secret is stored anywh
187
187
  **Install — straight from git** (no registry, no publish; works today)
188
188
 
189
189
  ```json
190
- "@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v2.4.10"
190
+ "@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v2.4.12"
191
191
  ```
192
192
 
193
193
  Because the package has no build step, a git dependency needs no `prepare` script — npm
@@ -237,7 +237,7 @@ semantic layer. That is what makes dark mode cost nothing.
237
237
  ## Runtime checks
238
238
 
239
239
  `_tests/` holds browser pages that exercise the real components: `react-runtime.html` (the compiled
240
- bundle), `table-selection.html` (Table loaded from source: checkbox spacing at every width, code chips, two-line rows, sorting), `smart-table-density.html` (SmartTable from source: default / compact rows, the Row height menu, controlled density), `relative-time-picker.html` (RelativeTimePicker from source: helpers, tiers, dialog, keyboard), `tree-reorder.html` (Tree from source: insert-between on leaves and folder edges, self/subtree rejection), `vue-runtime.html` (the `.vue` files, loaded with vue3-sfc-loader), `cards-interaction*.html`
240
+ bundle), `table-selection.html` (Table loaded from source: checkbox spacing at every width, code chips, two-line rows, sorting), `addrow-react.html` / `addrow-vue.html` (the SmartTable add row in both frameworks, from source: colour, weight, hover hooks, keyboard, `stickyAddRow`), `smart-table-density.html` (SmartTable from source: default / compact rows, the Row height menu, controlled density), `relative-time-picker.html` (RelativeTimePicker from source: helpers, tiers, dialog, keyboard), `tree-reorder.html` (Tree from source: insert-between on leaves and folder edges, self/subtree rejection), `vue-runtime.html` (the `.vue` files, loaded with vue3-sfc-loader), `cards-interaction*.html`
241
241
  (clicks through the documentation demos) and `icon-rules.html` (the Keybricks glyphs against the drawing rules). Serve the repo root (`npx serve .`), open a page and read the
242
242
  console — every line starts with PASS or FAIL. Run them after changing a component in either framework.
243
243
  `measure-cards.html` is a tool, not a test: it measures each documentation page at its card width so the
package/INTEGRATION.md CHANGED
@@ -31,7 +31,7 @@ Not using a registry yet? A git dependency works with no publishing at all — t
31
31
  has no build step, so npm can use the checkout as-is:
32
32
 
33
33
  ```json
34
- "@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v2.4.10"
34
+ "@keyfunc/keybricks-ds": "git+ssh://git@gitlab.epm2demo.cn/Felix/keybricks-ds.git#v2.4.12"
35
35
  ```
36
36
 
37
37
  **Source-only package.** `.jsx` and `.vue` files are published as authored, so your
@@ -521,6 +521,17 @@ Pin a minor range and update deliberately:
521
521
  - **Tokens outside CSS.** `tokens.json` / `tokens.js` / `tokens.d.ts` gained the tier tokens
522
522
  (`--control-*`, `--bar-h`, `--group-gap`, …) — 352 light tokens in all (2.0.2 adds `--danger-soft-hover`).
523
523
 
524
+ ### 2.4.12
525
+
526
+ - **Documentation site only — no component or token changes.** The View Code source text and the icon search indexes moved to `_src/` at the repo root, out of the compiled bundle the docs pages load (1.9 MB → 0.8 MB). `assets/icons/icon-index.json` and `unicons-index.json` stay for apps; the `.js` copies of those indexes are gone from `assets/icons/`.
527
+ - The home page loads the cards you jump to from the sidebar right away (they used to wait for a scroll), and the Pages job publishes gzip copies of text files.
528
+
529
+ ### 2.4.11
530
+
531
+ - **`SmartTable` add row** — the "+ New" row at the end of the grid is the system's add-row: `--action-primary` text and 13px plus at `--weight-medium`; hover `--bg-inset` with `--action-primary-hover` (`[data-addrow]` in `tokens/hover.css` — load `keybricks.css`, or add `tokens/hover.css` if you import `styles.css` alone). It is a focusable `role="button"` (Enter / Space add a row) with the global focus ring drawn inset.
532
+ - **`stickyAddRow`** — pins the add row to the bottom of the scroll area while the body scrolls (needs `height`): `position: sticky; bottom: 0`, `--bg-surface`, 1px `--border-subtle` top rule.
533
+ - **`labels.newRow`** — the add-row text (default "New").
534
+
524
535
  ### 2.4.10
525
536
 
526
537
  - **The npm package carries the documentation pages** — `index.html`, `gallery.html`, `guidelines/`, `changelog.js`, `site-nav.js`, `covers.js` — as readable text for AI coding agents in your app (the component cards were already under `components/_cards/`). `_ds_bundle.js` stays out: it is the compiled copy the pages run on, so the pages do not open from `node_modules` — view them on the Pages site. Apps still `import { … } from '@keyfunc/keybricks-ds'`.
@@ -16,7 +16,6 @@
16
16
  "patterns": [
17
17
  {
18
18
  "group": [
19
- "assets/icons/**",
20
19
  "changelog.js",
21
20
  "components/**",
22
21
  "components/_shared/**",
package/changelog.js CHANGED
@@ -3,6 +3,14 @@
3
3
  Each item: tag (new | improved | fixed | breaking | removed) and the text as [en, zh, ja].
4
4
  INTEGRATION.md keeps the full migration notes; this list is the short, reader-facing summary. */
5
5
  window.KB_CHANGELOG = [
6
+ { v: "2.4.12", date: "2026-10-28", items: [
7
+ { tag: "fixed", t: ["Home page: cards you jump to from the sidebar load right away instead of staying on “loading…” until you scroll.", "首页:从侧边栏跳转到的组件卡片会立即加载,不再一直显示“加载中…”直到滚动页面。", "ホーム:サイドバーからジャンプしたカードがすぐ読み込まれるように。スクロールするまで「読み込み中…」のままだった問題を修正。"] },
8
+ { tag: "improved", t: ["The documentation site loads faster: its component bundle is less than half the size (1.9 MB → 0.8 MB), and files are served compressed.", "文档站加载更快:组件包体积减少一半以上(1.9 MB → 0.8 MB),文件改为压缩传输。", "ドキュメントサイトの読み込みを高速化:コンポーネントバンドルを半分以下に(1.9 MB → 0.8 MB)、ファイルを圧縮配信。"] },
9
+ ] },
10
+ { v: "2.4.11", date: "2026-10-28", items: [
11
+ { tag: "improved", t: ["<code>SmartTable</code> “+ New” row: blue text and plus at medium weight, a grey hover, and keyboard support (Tab, then Enter or Space).", "<code>SmartTable</code> 的 “+ New” 行改为蓝色文字和加号、中等字重,悬停显示灰底,并支持键盘操作(Tab 选中后按 Enter 或空格)。", "<code>SmartTable</code> の「+ New」行を青い文字とプラス、ミディアムの太さに。ホバーでグレーの背景、キーボード操作(Tab のあと Enter / Space)に対応。"] },
12
+ { tag: "new", t: ["<code>SmartTable</code> <code>stickyAddRow</code> keeps the add row at the bottom while the table scrolls; <code>labels.newRow</code> sets its text.", "<code>SmartTable</code> 新增 <code>stickyAddRow</code>:表格滚动时新增行固定在底部;<code>labels.newRow</code> 可设置文字。", "<code>SmartTable</code> に <code>stickyAddRow</code> を追加:スクロール中も追加行を下端に固定。<code>labels.newRow</code> で文字を設定。"] },
13
+ ] },
6
14
  { v: "2.4.10", date: "2026-10-28", items: [
7
15
  { tag: "improved", t: ["The npm package now includes the documentation pages (<code>index.html</code>, <code>gallery.html</code>, <code>guidelines/</code>) as text that AI coding agents in your app can read. To view them, use the documentation site.", "npm 包现在包含文档网页(<code>index.html</code>、<code>gallery.html</code>、<code>guidelines/</code>),业务项目里的 AI 编程助手可以直接读取。浏览网页请继续使用文档站。", "npm パッケージにドキュメントページ(<code>index.html</code>、<code>gallery.html</code>、<code>guidelines/</code>)を同梱。アプリ側の AI コーディングエージェントがテキストとして読める。閲覧はドキュメントサイトで。"] },
8
16
  ] },
@@ -6,7 +6,7 @@
6
6
  <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
7
7
  <script src="../../_ds_bundle.js"></script>
8
8
  <script src="../_shared/svg-download.js"></script>
9
- <script src="../../assets/icons/icon-index.js"></script>
9
+ <script src="../../_src/icon-index.js"></script>
10
10
  <style>body{margin:0;background:var(--bg-surface);padding:14px 16px;box-sizing:border-box}</style></head>
11
11
  <body><div id="root"></div>
12
12
  <script type="text/babel">
@@ -6,7 +6,7 @@
6
6
  <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
7
7
  <script src="../../_ds_bundle.js"></script>
8
8
  <script src="../_shared/svg-download.js"></script>
9
- <script src="../../assets/icons/icon-index.js"></script>
9
+ <script src="../../_src/icon-index.js"></script>
10
10
  <style>body{margin:0;background:var(--bg-surface);padding:16px;font-family:var(--font-sans);box-sizing:border-box}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(96px,1fr));gap:2px;padding-top:12px}.tile{display:flex;flex-direction:column;align-items:center;gap:6px;padding:10px 2px 8px;border-radius:8px;cursor:pointer;border:1px solid transparent}.tile:hover{background:var(--bg-hover)}.tile span{font-size:var(--text-xs);line-height:16px;color:var(--text-secondary);max-width:100%;text-align:center;overflow-wrap:anywhere;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.meta{font-size:var(--text-xs);color:var(--text-muted);padding-top:8px}</style></head>
11
11
  <body><div id="root"></div>
12
12
  <script type="text/babel">
@@ -6,7 +6,7 @@
6
6
  <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
7
7
  <script src="../../_ds_bundle.js"></script>
8
8
  <script src="../_shared/svg-download.js"></script>
9
- <script src="../../assets/icons/unicons-index.js"></script>
9
+ <script src="../../_src/unicons-index.js"></script>
10
10
  <style>body{margin:0;background:var(--bg-surface);padding:16px;font-family:var(--font-sans);box-sizing:border-box}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(96px,1fr));gap:2px;padding-top:12px}.tile{display:flex;flex-direction:column;align-items:center;gap:6px;padding:10px 2px 8px;border-radius:8px;cursor:pointer;border:1px solid transparent}.tile:hover{background:var(--bg-hover)}.tile span{font-size:var(--text-xs);line-height:16px;color:var(--text-secondary);max-width:100%;text-align:center;overflow-wrap:anywhere;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.meta{font-size:var(--text-xs);color:var(--text-muted);padding-top:8px;line-height:1.5}</style></head>
11
11
  <body><div id="root"></div>
12
12
  <script type="text/babel">
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Components" viewport="700x1040" name="Smart Table (Database Grid)" subtitle="Frozen header and row numbers · default and compact density · typed fields · hue pills · aggregates · + New" -->
1
+ <!-- @dsCard group="Components" viewport="700x1360" name="Smart Table (Database Grid)" subtitle="Frozen header and row numbers · default and compact density · typed fields · hue pills · aggregates · + New" -->
2
2
  <!DOCTYPE html>
3
3
  <html><head><meta charset="utf-8"><script src="../_shared/frame-theme.js"></script><link rel="stylesheet" href="../../styles.css"><link rel="stylesheet" href="../../tokens/hover.css"><link rel="stylesheet" href="../_shared/card-kit.css">
4
4
  <script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
@@ -41,6 +41,8 @@ const rows=base.map((b,i)=>({...b,status:statuses[i%4],owner:owners[i%4],due:"Au
41
41
  function Sheet(){return <div>
42
42
  <SmartTable fields={fields} rows={rows} title="Launch tracker" height={300}/>
43
43
  <div className="hint" style={{marginBottom:20}}>Frozen edges, like a Spreadsheet: scroll down and the field header stays; scroll right and the row-number column stays; the corner cell holds on both axes. Sticky cells keep the <code>--bg-inset</code> fill and a <code>--border-subtle</code> hairline on the frozen edge. Default density — rows are the tier's bar height (36px at sm). The toolbar's <b>Row height</b> menu switches between Default and Compact.</div>
44
+ <SmartTable fields={fields} rows={rows} title="Launch tracker — sticky add row" stickyAddRow labels={{newRow:"New task"}} height={220}/>
45
+ <div className="hint" style={{marginBottom:20}}>Add row — <code>--action-primary</code> text and plus at medium weight; hover turns it <code>--bg-inset</code> with <code>--action-primary-hover</code>. It is a focusable button: Tab to it and press Enter or Space to add a row. <code>stickyAddRow</code> pins it to the bottom while the body scrolls (on <code>--bg-surface</code> with a hairline above); scroll to the end and it returns to its place above the totals. <code>labels.newRow</code> sets the text.</div>
44
46
  <SmartTable fields={fields} rows={rows} title="Launch tracker — compact" defaultDensity="compact" height={240}/>
45
47
  <div className="hint" style={{marginBottom:20}}>Compact density — rows are the tier's control height (28px at sm, the same as a Spreadsheet cell) with the Spreadsheet's 8px cell padding (the same as sm's <code>--cell-px</code>, so here only the height changes); header and footer follow, and so do the frozen edges. <code>density="compact"</code> controls it, <code>defaultDensity</code> just seeds it.</div>
46
48
  <div className="hint">Typed fields with per-type header icons; select/multi-select render theme-aware hue pills; hover a row number for the expand affordance; double-click text/number/date cells to edit; “+ New” appends a row; the footer aggregates per column (COUNT / SUM / AVG). Without <code>height</code> the grid grows with its rows and only the row-number column freezes.</div>
@@ -1,13 +1,17 @@
1
1
  (function(){
2
2
  var bundles = {};
3
- /* Component source is loaded on demand, one script per folder (components/_shared/src-<dir>.js).
3
+ /* Component source is loaded on demand, one script per folder (_src/src-<dir>.js at the project root).
4
+ It lives outside components/ so the design-system compiler keeps it out of _ds_bundle.js — ~870 KB of
5
+ source text every card and the home page would otherwise download and parse just to show View Code.
6
+ _src/ also holds the icon search indexes (icon-index.js, unicons-index.js) that only the icon gallery cards load;
7
+ assets/icons/*.json keep the same data for apps.
4
8
  A <script> tag works from file:// and from any host; fetch() of a sibling source file does not. */
5
9
  function loadBundle(dir, done){
6
10
  if (!dir || bundles[dir] === 'done') return done();
7
11
  if (bundles[dir]) { bundles[dir].push(done); return; }
8
12
  bundles[dir] = [done];
9
13
  var s = document.createElement('script');
10
- s.src = '../_shared/src-' + dir + '.js';
14
+ s.src = '../../_src/src-' + dir + '.js';
11
15
  s.onload = s.onerror = function(){
12
16
  var q = bundles[dir]; bundles[dir] = 'done';
13
17
  q.forEach(function(fn){ fn(); });
@@ -18,6 +18,7 @@ export interface SmartTableField {
18
18
  }
19
19
 
20
20
  /** Notion/Airtable-style database grid: field-type header icons, hue pills, person/date/checkbox/url/progress cells, row hover + expand, inline editing, + New row, footer aggregates, view toolbar.
21
+ * The "+ New" row is the system's add-row: --action-primary text and 13px plus at --weight-medium; hover --bg-inset with --action-primary-hover; a focusable role="button" (Enter / Space).
21
22
  * Frozen edges like Spreadsheet: the header row sticks to the top and the row-number column to the left of the scroll area (give `height` for vertical scrolling). */
22
23
  export interface SmartTableProps {
23
24
  fields?: SmartTableField[];
@@ -40,6 +41,10 @@ export interface SmartTableProps {
40
41
  columnWidths?: Record<string, number>;
41
42
  /** Fires on every drag frame with the new px width. Vue: `@column-resize`. */
42
43
  onColumnResize?: (key: string, width: number) => void;
44
+ /** Pin the "+ New" row to the bottom of the scroll area while the body scrolls (needs `height`): position sticky, --bg-surface fill, 1px --border-subtle top rule. Default false. */
45
+ stickyAddRow?: boolean;
46
+ /** UI strings. newRow — the add-row label (default "New"). */
47
+ labels?: { newRow?: string };
43
48
  /** Size tier of the grid region. Defaults to "sm" — a database grid is a dense surface (36px rows, 12px text, 28px toolbar buttons). */
44
49
  size?: "sm" | "md" | "lg";
45
50
  onChange?: (rows: object[]) => void;
@@ -21,7 +21,7 @@ function ResizeHandle({ onMouseDown, onEnter, onLeave }) {
21
21
  }
22
22
  const EDGE_LIT = { borderRight: "1px solid var(--action-primary)", boxShadow: "inset -1px 0 0 var(--action-primary)" }; // overrides the borderRight shorthand (a longhand would leave currentColor behind on leave)
23
23
  const DENSITY = { default: { h: "var(--bar-h)", px: "var(--cell-px)" }, compact: { h: "var(--control-h)", px: "8px" } };
24
- export function SmartTable({ fields = [], rows = [], title = "Grid view", toolbar = true, height, rowHeight, density: densityProp, defaultDensity = "default", onDensityChange, resizable = true, columnWidths, onColumnResize, size = "sm", onChange, style }) {
24
+ export function SmartTable({ fields = [], rows = [], title = "Grid view", toolbar = true, height, rowHeight, density: densityProp, defaultDensity = "default", onDensityChange, resizable = true, columnWidths, onColumnResize, stickyAddRow = false, labels, size = "sm", onChange, style }) {
25
25
  const [data, setData] = React.useState(rows);
26
26
  const [densityState, setDensityState] = React.useState(defaultDensity);
27
27
  const density = DENSITY[densityProp || densityState] ? (densityProp || densityState) : "default";
@@ -48,6 +48,7 @@ export function SmartTable({ fields = [], rows = [], title = "Grid view", toolba
48
48
  const grid = { display: "grid", gridTemplateColumns: widths.join(" "), width: "fit-content", minWidth: "100%" };
49
49
  const cellBase = { display: "flex", alignItems: "center", gap: 5, padding: "0 " + DENSITY[density].px, borderRight: "1px solid var(--border-subtle)", borderBottom: "1px solid var(--border-subtle)", height: rowH, boxSizing: "border-box", overflow: "hidden", whiteSpace: "nowrap", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", color: "var(--text-primary)" };
50
50
  const tierIcon = { width: "var(--control-icon)", height: "var(--control-icon)" };
51
+ const addRow = () => { const empty = {}; fields.forEach(f => empty[f.key] = f.type === "multiSelect" ? [] : f.type === "checkbox" ? false : ""); const next = [...data, empty]; setData(next); onChange && onChange(next); };
51
52
  // Frozen edges — zIndex 1 (row-number column) < 2 (header row) < 3 (corner, both axes); the editing input sits inside a body cell, under them.
52
53
  const stickyTop = { position: "sticky", top: 0, zIndex: 2, background: "var(--bg-inset)" };
53
54
  const stickyLeft = { position: "sticky", left: 0, zIndex: 1, background: "var(--bg-inset)" };
@@ -97,7 +98,9 @@ export function SmartTable({ fields = [], rows = [], title = "Grid view", toolba
97
98
  ...fields.map(f => <span key={r + f.key} onMouseEnter={() => setHover(r)} onMouseLeave={() => setHover(-1)} onDoubleClick={() => { if (f.type === "text" || f.type === "number" || f.type === "date" || f.type === "url") { setEditing({ r, key: f.key }); setDraft(String(row[f.key] == null ? "" : row[f.key])); } }} style={{ ...cellBase, position: "relative", background: hover === r ? "var(--bg-inset)" : "transparent" }}>{renderCell(f, row, r)}</span>),
98
99
  <span key={"a" + r} style={{ ...cellBase, background: hover === r ? "var(--bg-inset)" : "transparent" }}></span>,
99
100
  ])}
100
- <button type="button" onClick={() => { const empty = {}; fields.forEach(f => empty[f.key] = f.type === "multiSelect" ? [] : f.type === "checkbox" ? false : ""); const next = [...data, empty]; setData(next); onChange && onChange(next); }} style={{ gridColumn: "1 / -1", display: "flex", alignItems: "center", gap: 6, height: rowH, padding: "0 var(--edge-pad)", border: "none", borderBottom: "1px solid var(--border-subtle)", background: "transparent", cursor: "pointer", color: "var(--text-secondary)", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", letterSpacing: "var(--control-tracking)", textAlign: "left" }} onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-inset)"} onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}><Icon name="plus" size={13} />New</button>
101
+ {/* Add row — the same at the end of every table: --action-primary text + 13px plus, medium weight; hover --bg-inset with --action-primary-hover (tokens/hover.css [data-addrow]).
102
+ A focusable role="button": Enter / Space add a row; the global focus ring is drawn inset so the scroll area never clips it. stickyAddRow pins it to the bottom of the scroll area. */}
103
+ <span role="button" tabIndex={0} data-addrow="" onClick={addRow} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); addRow(); } }} style={{ gridColumn: "1 / -1", display: "flex", alignItems: "center", gap: 6, height: rowH, padding: "0 var(--edge-pad)", boxSizing: "border-box", borderBottom: "1px solid var(--border-subtle)", background: `var(--addrow-bg, ${stickyAddRow ? "var(--bg-surface)" : "transparent"})`, color: "var(--addrow-fg, var(--action-primary))", cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: "var(--control-text)", lineHeight: "var(--control-leading)", letterSpacing: "var(--control-tracking)", fontWeight: "var(--weight-medium)", whiteSpace: "nowrap", outlineOffset: -2, transition: "var(--t-hover)", ...(stickyAddRow ? { position: "sticky", bottom: 0, zIndex: 2, borderTop: "1px solid var(--border-subtle)" } : null) }}><Icon name="plus" size={13} style={{ width: 13, height: 13, flexShrink: 0 }} />{(labels && labels.newRow) || "New"}</span>
101
104
  <span style={{ ...cellBase, ...stickyLeft, borderBottom: "none" }}></span>
102
105
  {fields.map(f => <span key={"g" + f.key} style={{ ...cellBase, borderBottom: "none", background: "var(--bg-inset)", justifyContent: "flex-end", fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums", fontSize: "var(--text-xs)", letterSpacing: 0, color: "var(--text-secondary)" }}>{agg(f)}</span>)}
103
106
  <span style={{ ...cellBase, borderBottom: "none", background: "var(--bg-inset)" }}></span>
@@ -8,7 +8,7 @@ import SearchInput from '../forms/SearchInput.vue'
8
8
  import Menu from '../feedback/Menu.vue'
9
9
  // Notion/Airtable-style database grid: field-type header icons, hue pills, person/date/checkbox/url/progress cells, row hover + expand, inline editing, + New row, footer aggregates, view toolbar.
10
10
  const typeIcon = { text: 'type', longText: 'align-left', number: 'hash', select: 'chevron-down', multiSelect: 'tag', date: 'calendar', person: 'user', checkbox: 'check-square', url: 'link', progress: 'bar-chart-2' }
11
- const props = defineProps({ fields: { type: Array, default: () => [] }, rows: { type: Array, default: () => [] }, title: { type: String, default: 'Grid view' }, toolbar: { type: Boolean, default: true }, height: [Number, String], rowHeight: [Number, String], density: String, defaultDensity: { type: String, default: 'default' }, resizable: { type: Boolean, default: true }, columnWidths: Object, size: { type: String, default: 'sm' }, style: Object })
11
+ const props = defineProps({ fields: { type: Array, default: () => [] }, rows: { type: Array, default: () => [] }, title: { type: String, default: 'Grid view' }, toolbar: { type: Boolean, default: true }, height: [Number, String], rowHeight: [Number, String], density: String, defaultDensity: { type: String, default: 'default' }, resizable: { type: Boolean, default: true }, columnWidths: Object, stickyAddRow: Boolean, labels: Object, size: { type: String, default: 'sm' }, style: Object })
12
12
  // A size region that defaults to sm: a database grid is a dense surface, so rows are the sm bar height (36), cells and toolbar read the sm control text (12px, 13px in CJK) and the toolbar's buttons are 28px. Pass size="md" for a roomier grid.
13
13
  // density: 'default' rows are --bar-h (36 / 40 / 48) with --cell-px; 'compact' rows are --control-h (28 / 32 / 40, the Spreadsheet's cell height) with the Spreadsheet's 8px padding (so at sm only the height changes). Header and footer follow. The toolbar's "Row height" menu switches it (emits density-change; v-model:density to control); rowHeight (a raw height) overrides density.
14
14
  // Frozen edges (like Spreadsheet): the field header row is sticky to the top and the 40px row-number column to the left; the corner cell is sticky on both axes. Sticky cells keep the --bg-inset fill so body cells never show through, and the frozen edge is a --border-subtle hairline.
@@ -44,6 +44,7 @@ const cellBase = computed(() => ({ display: 'flex', alignItems: 'center', gap: '
44
44
  function setCell(r, key, v) { data.value[r][key] = v; emit('change', data.value) }
45
45
  function commitEdit(r, f) { setCell(r, f.key, f.type === 'number' && draft.value !== '' && !isNaN(Number(draft.value)) ? Number(draft.value) : draft.value); editing.value = null }
46
46
  function startEdit(r, f, row) { if (['text', 'number', 'date', 'url'].includes(f.type || 'text')) { editing.value = { r, key: f.key }; draft.value = String(row[f.key] == null ? '' : row[f.key]) } }
47
+ const addRowStyle = computed(() => ({ gridColumn: '1 / -1', display: 'flex', alignItems: 'center', gap: '6px', height: rowH.value, padding: '0 var(--edge-pad)', boxSizing: 'border-box', borderBottom: '1px solid var(--border-subtle)', background: `var(--addrow-bg, ${props.stickyAddRow ? 'var(--bg-surface)' : 'transparent'})`, color: 'var(--addrow-fg, var(--action-primary))', cursor: 'pointer', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', fontWeight: 'var(--weight-medium)', whiteSpace: 'nowrap', outlineOffset: '-2px', transition: 'var(--t-hover)', ...(props.stickyAddRow ? { position: 'sticky', bottom: 0, zIndex: 2, borderTop: '1px solid var(--border-subtle)' } : null) }))
47
48
  function addRow() { const empty = {}; props.fields.forEach(f => empty[f.key] = f.type === 'multiSelect' ? [] : f.type === 'checkbox' ? false : ''); data.value.push(empty); emit('change', data.value) }
48
49
  function agg(f) {
49
50
  if (!f.aggregate) return ''
@@ -86,7 +87,8 @@ function agg(f) {
86
87
  </span>
87
88
  <span :style="{ ...cellBase, height: rowH, background: hover === r ? 'var(--bg-inset)' : 'transparent' }" />
88
89
  </template>
89
- <button type="button" @click="addRow" class="kb-st-new" :style="{ gridColumn: '1 / -1', height: rowH }"><Icon name="plus" :size="14" :style="tierIcon" />New</button>
90
+ <!-- Add row: --action-primary + 13px plus, medium; hover --bg-inset / --action-primary-hover (tokens/hover.css [data-addrow]); focusable role="button", Enter / Space add; stickyAddRow pins it to the bottom -->
91
+ <span role="button" tabindex="0" data-addrow="" @click="addRow" @keydown.enter.prevent="addRow" @keydown.space.prevent="addRow" :style="addRowStyle"><Icon name="plus" :size="13" :style="{ width: '13px', height: '13px', flexShrink: 0 }" />{{ (labels && labels.newRow) || 'New' }}</span>
90
92
  <span :style="{ ...cellBase, height: rowH, ...stickyLeft, borderBottom: 'none' }" />
91
93
  <span v-for="f in fields" :key="'g' + f.key" :style="{ ...cellBase, height: rowH, borderBottom: 'none', background: 'var(--bg-inset)', justifyContent: 'flex-end', fontFamily: 'var(--font-sans)', fontVariantNumeric: 'tabular-nums', fontSize: 'var(--text-xs)', letterSpacing: 0, color: 'var(--text-secondary)' }">{{ agg(f) }}</span>
92
94
  <span :style="{ ...cellBase, height: rowH, borderBottom: 'none', background: 'var(--bg-inset)' }" />
@@ -98,6 +100,4 @@ function agg(f) {
98
100
  .kb-pill{display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:var(--radius-full);font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-xs);line-height:1;white-space:nowrap}
99
101
  .kb-st-btn{display:inline-flex;align-items:center;gap:5px;height:var(--control-h);padding:0 var(--control-px);border:none;border-radius:var(--radius-md);background:transparent;cursor:pointer;color:var(--text-body);font-family:var(--font-sans);font-size:var(--control-text);letter-spacing:var(--control-tracking)}
100
102
  .kb-st-btn:hover{background:var(--bg-hover)}
101
- .kb-st-new{display:flex;align-items:center;gap:6px;padding:0 var(--edge-pad);border:none;border-bottom:1px solid var(--border-subtle);background:transparent;cursor:pointer;color:var(--text-secondary);font-family:var(--font-sans);font-size:var(--control-text);letter-spacing:var(--control-tracking);text-align:left}
102
- .kb-st-new:hover{background:var(--bg-inset)}
103
103
  </style>
package/index.html CHANGED
@@ -192,7 +192,7 @@ section{margin-bottom:52px;scroll-margin-top:78px}
192
192
  const GROUPS=[["Brand","品牌","ブランド"],["Colors","颜色","カラー"],["Type","字体排印","タイポグラフィ"],["Foundations","基础规范","基盤仕様"],["Components","组件","コンポーネント"],["Charts","图表","チャート"],["Icons","图标库","アイコン"]];
193
193
 
194
194
  const S={
195
- brandsub:["Design System · v2.4.10","设计系统 · v2.4.10","デザインシステム · v2.4.10"],
195
+ brandsub:["Design System · v2.4.12","设计系统 · v2.4.12","デザインシステム · v2.4.12"],
196
196
  doc_readme:["Readme","说明文档","ドキュメント"],
197
197
  doc_design:["DESIGN.md","下载 DESIGN.md","DESIGN.md"],
198
198
  doc_integration:["Integration","接入指南","導入ガイド"],
@@ -355,7 +355,7 @@ function settleOn(target){
355
355
  const pin=()=>{const d=target.getBoundingClientRect().top-72;if(Math.abs(d)>1)window.scrollTo({top:sc.scrollTop+d,behavior:"instant"});markNav();};
356
356
  const ro=new ResizeObserver(pin);ro.observe(lib);document.querySelectorAll("#view-library .frame-wrap").forEach(w=>ro.observe(w));
357
357
  const evs=["wheel","touchmove","keydown","mousedown"];
358
- const off=()=>{ro.disconnect();markNav();clearTimeout(t);evs.forEach(e=>window.removeEventListener(e,off,true));if(settleOff===off)settleOff=null;};
358
+ const off=()=>{ro.disconnect();markNav();clearTimeout(t);evs.forEach(e=>window.removeEventListener(e,off,true));if(settleOff===off)settleOff=null;loadNearFrames();};
359
359
  evs.forEach(e=>window.addEventListener(e,off,{capture:true,passive:true}));
360
360
  const t=setTimeout(off,10000);settleOff=off;
361
361
  pin();requestAnimationFrame(pin);
@@ -527,21 +527,29 @@ function fit(f){
527
527
  f.style.transform=scale<1?"scale("+scale+")":"none";
528
528
  wrap.style.height=Math.round(h*scale)+"px";
529
529
  }
530
+ /* A card loads when it comes within 500px of the viewport — also while a jump is being pinned (settleOn): the pin
531
+ exists to absorb exactly those cards growing. (An early return here once left the jumped-to cards on "loading…" until
532
+ the reader scrolled, since an observer reports an element only when its intersection changes.) */
533
+ let frameIO=null;
534
+ function loadFrame(f){
535
+ if(!f.dataset.src)return;
536
+ if(frameIO)frameIO.unobserve(f);
537
+ f.addEventListener("load",()=>{
538
+ if(f.__ph)f.__ph.remove();
539
+ pushTheme(f);fit(f);
540
+ [120,400,1000,2000].forEach(d=>setTimeout(()=>{pushTheme(f);fit(f);},d));
541
+ try{new f.contentWindow.ResizeObserver(()=>fit(f)).observe(f.contentDocument.documentElement);}catch(err){}
542
+ });
543
+ f.src=f.dataset.src;f.removeAttribute("data-src");
544
+ }
545
+ /* Safety net after a pinned jump ends: load whatever pending card is now near the viewport. */
546
+ function loadNearFrames(){
547
+ document.querySelectorAll("iframe[data-src]").forEach(f=>{const r=f.__wrap?f.__wrap.getBoundingClientRect():f.getBoundingClientRect();if(r.bottom>-500&&r.top<innerHeight+500)loadFrame(f);});
548
+ }
530
549
  function watchFrames(){
531
550
  const frames=[...document.querySelectorAll("iframe[data-src]")];
532
- const io=new IntersectionObserver(es=>{
533
- if(settleOff)return;
534
- es.forEach(e=>{
535
- if(!e.isIntersecting)return;
536
- const f=e.target;io.unobserve(f);
537
- f.addEventListener("load",()=>{
538
- if(f.__ph)f.__ph.remove();
539
- pushTheme(f);fit(f);
540
- [120,400,1000,2000].forEach(d=>setTimeout(()=>{pushTheme(f);fit(f);},d));
541
- try{new f.contentWindow.ResizeObserver(()=>fit(f)).observe(f.contentDocument.documentElement);}catch(err){}
542
- });
543
- f.src=f.dataset.src;f.removeAttribute("data-src");
544
- });
551
+ const io=frameIO=new IntersectionObserver(es=>{
552
+ es.forEach(e=>{if(e.isIntersecting)loadFrame(e.target);});
545
553
  },{rootMargin:"500px 0px"});
546
554
  frames.forEach(f=>io.observe(f));
547
555
  addEventListener("resize",()=>document.querySelectorAll("iframe").forEach(f=>{if(f.__wrap&&!f.dataset.src)fit(f);}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyfunc/keybricks-ds",
3
- "version": "2.4.10",
3
+ "version": "2.4.12",
4
4
  "description": "Keybricks design system — design tokens, three size tiers and 121 UI components in React and Vue 3, source-only.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
package/tokens/hover.css CHANGED
@@ -20,6 +20,8 @@
20
20
  which darkens in light mode and lightens in dark. */
21
21
  [data-row]{--row-wash:initial}
22
22
  [data-row]:hover{--row-wash:var(--bg-inset)}
23
+ [data-addrow]{--addrow-bg:initial;--addrow-fg:initial}
24
+ [data-addrow]:hover{--addrow-bg:var(--bg-inset);--addrow-fg:var(--action-primary-hover)}
23
25
  [data-ink]{--ink:initial;--ink-op:initial}
24
26
  [data-ink]:hover:not(:disabled){--ink:var(--text-heading);--ink-op:1}
25
27
  [data-check]{--check-border:initial}
@@ -1 +0,0 @@
1
- window.KB_ICON_INDEX=[{"name":"search","set":"core","tags":["search","find","magnifier","magnifying glass"]},{"name":"bell","set":"core","tags":["bell","alarm","notification","sound"]},{"name":"panel-left","set":"core","tags":["panel","left"]},{"name":"house","set":"core","tags":["house"]},{"name":"file","set":"core","tags":["file"]},{"name":"file-text","set":"core","tags":["file","text","data","txt","pdf"]},{"name":"file-check","set":"core","tags":["file","check"]},{"name":"layout-grid","set":"core","tags":["layout","grid"]},{"name":"database","set":"core","tags":["database","storage","memory"]},{"name":"scale","set":"core","tags":["scale"]},{"name":"network","set":"core","tags":["network"]},{"name":"folder","set":"core","tags":["folder","directory"]},{"name":"folder-open","set":"core","tags":["folder","open"]},{"name":"folder-cog","set":"core","tags":["folder","cog"]},{"name":"settings","set":"core","tags":["settings","cog","edit","gear","preferences"]},{"name":"settings-2","set":"core","tags":["settings","2"]},{"name":"moon","set":"core","tags":["moon","dark","night"]},{"name":"circle-help","set":"core","tags":["circle","help"]},{"name":"model","set":"core","tags":["model","modeling","cube","box","package","3d","dimension"]},{"name":"workflow","set":"core","tags":["workflow"]},{"name":"shield","set":"core","tags":["shield","security","secure"]},{"name":"refresh-cw","set":"core","tags":["refresh","cw","synchronise","arrows"]},{"name":"chart-line","set":"core","tags":["chart","line"]},{"name":"chart-column","set":"core","tags":["chart","column"]},{"name":"chart-no-axes-column","set":"core","tags":["chart","no","axes","column"]},{"name":"list-todo","set":"core","tags":["list","todo"]},{"name":"circle-check","set":"core","tags":["circle","check"]},{"name":"target","set":"core","tags":["target","logo","bullseye"]},{"name":"book-open","set":"core","tags":["book","open","read","library"]},{"name":"chevron-down","set":"core","tags":["chevron","down","expand"]},{"name":"chevron-right","set":"core","tags":["chevron","right"]},{"name":"chevron-left","set":"core","tags":["chevron","left"]},{"name":"chevron-up","set":"core","tags":["chevron","up","collapse"]},{"name":"arrow-left","set":"core","tags":["arrow","left"]},{"name":"arrow-right","set":"core","tags":["arrow","right"]},{"name":"arrow-right-left","set":"core","tags":["arrow","right","left"]},{"name":"table","set":"core","tags":["table"]},{"name":"layers","set":"core","tags":["layers","stack","hierarchy"]},{"name":"grid-3x3","set":"core","tags":["grid","3x3"]},{"name":"text-search","set":"core","tags":["text","search"]},{"name":"shuffle","set":"core","tags":["shuffle","music"]},{"name":"split","set":"core","tags":["split"]},{"name":"menu","set":"core","tags":["menu","bars","navigation","hamburger"]},{"name":"list","set":"core","tags":["list","options"]},{"name":"funnel","set":"core","tags":["funnel"]},{"name":"sliders-horizontal","set":"core","tags":["sliders","horizontal"]},{"name":"trending-up","set":"core","tags":["trending","up"]},{"name":"code","set":"core","tags":["code","source","programming"]},{"name":"merge","set":"core","tags":["merge"]},{"name":"upload","set":"core","tags":["upload"]},{"name":"download","set":"core","tags":["download"]},{"name":"check","set":"core","tags":["check"]},{"name":"x","set":"core","tags":["x","cancel","close","delete","remove","times","clear"]},{"name":"plus","set":"core","tags":["plus","add","new"]},{"name":"minus","set":"core","tags":["minus","subtract"]},{"name":"ellipsis","set":"core","tags":["ellipsis"]},{"name":"play","set":"core","tags":["play","music","start"]},{"name":"send","set":"core","tags":["send","message","mail","email","paper airplane","paper aeroplane"]},{"name":"eye","set":"core","tags":["eye","view","watch"]},{"name":"copy","set":"core","tags":["copy","clone","duplicate"]},{"name":"trash-2","set":"core","tags":["trash","2","garbage","delete","remove","bin"]},{"name":"clock","set":"core","tags":["clock","time","watch","alarm"]},{"name":"calendar","set":"core","tags":["calendar","date"]},{"name":"undo-2","set":"core","tags":["undo","2"]},{"name":"redo-2","set":"core","tags":["redo","2"]},{"name":"history","set":"core","tags":["history"]},{"name":"bug","set":"core","tags":["bug"]},{"name":"info","set":"core","tags":["info"]},{"name":"triangle-alert","set":"core","tags":["triangle","alert"]},{"name":"event","set":"core","tags":["event","milestone","trigger","business event","diamond","stream"]},{"name":"consolidation","set":"core","tags":["consolidation","sum","aggregate","roll up","entities","group","total"]},{"name":"journal","set":"core","tags":["journal","ledger","entry","debit","credit","t-account","accounting","posting"]},{"name":"reconciliation","set":"core","tags":["reconciliation","match","check","tick","verify","compare","statement"]},{"name":"view-tree","set":"core","tags":["tree","tree view","hierarchy","outline","structure","parent","child"]},{"name":"drilldown","set":"core","tags":["drill down","drilldown","detail","expand","level","arrow down"]},{"name":"add-below","set":"core","tags":["add below","insert","new row","add","plus","after"]},{"name":"copy-data","set":"core","tags":["copy data","copy","import","load","into","transfer"]},{"name":"task-execution","set":"core","tags":["task","execute","execution","run","clipboard","start"]},{"name":"time-future","set":"core","tags":["future","forward","forecast","time","clock","period"]},{"name":"time-restatement","set":"core","tags":["restatement","restate","past","rewind","time","clock","period"]},{"name":"activity","set":"feather","tags":["activity","pulse","health","action","motion"]},{"name":"airplay","set":"feather","tags":["airplay","stream","cast","mirroring"]},{"name":"alert-circle","set":"feather","tags":["alert","circle","warning","danger"]},{"name":"alert-octagon","set":"feather","tags":["alert","octagon","warning","danger"]},{"name":"alert-triangle","set":"feather","tags":["alert","triangle","warning","danger"]},{"name":"align-center","set":"feather","tags":["align","center","text alignment"]},{"name":"align-justify","set":"feather","tags":["align","justify","text alignment","justified"]},{"name":"align-left","set":"feather","tags":["align","left","text alignment"]},{"name":"align-right","set":"feather","tags":["align","right","text alignment"]},{"name":"anchor","set":"feather","tags":["anchor"]},{"name":"aperture","set":"feather","tags":["aperture","camera","photo"]},{"name":"archive","set":"feather","tags":["archive","index","box"]},{"name":"arrow-down-circle","set":"feather","tags":["arrow","down","circle"]},{"name":"arrow-down-left","set":"feather","tags":["arrow","down","left"]},{"name":"arrow-down-right","set":"feather","tags":["arrow","down","right"]},{"name":"arrow-down","set":"feather","tags":["arrow","down"]},{"name":"arrow-left-circle","set":"feather","tags":["arrow","left","circle"]},{"name":"arrow-right-circle","set":"feather","tags":["arrow","right","circle"]},{"name":"arrow-up-circle","set":"feather","tags":["arrow","up","circle"]},{"name":"arrow-up-left","set":"feather","tags":["arrow","up","left"]},{"name":"arrow-up-right","set":"feather","tags":["arrow","up","right"]},{"name":"arrow-up","set":"feather","tags":["arrow","up"]},{"name":"at-sign","set":"feather","tags":["at","sign","mention","email","message"]},{"name":"award","set":"feather","tags":["award","achievement","badge"]},{"name":"bar-chart-2","set":"feather","tags":["bar","chart","2","statistics","diagram","graph"]},{"name":"bar-chart","set":"feather","tags":["bar","chart","statistics","diagram","graph"]},{"name":"battery-charging","set":"feather","tags":["battery","charging","power","electricity"]},{"name":"battery","set":"feather","tags":["battery","power","electricity"]},{"name":"bell-off","set":"feather","tags":["bell","off","alarm","notification","silent"]},{"name":"bluetooth","set":"feather","tags":["bluetooth","wireless"]},{"name":"bold","set":"feather","tags":["bold"]},{"name":"book","set":"feather","tags":["book","read","dictionary","booklet","magazine","library"]},{"name":"bookmark","set":"feather","tags":["bookmark","read","clip","marker","tag"]},{"name":"briefcase","set":"feather","tags":["briefcase","work","bag","baggage","folder"]},{"name":"camera-off","set":"feather","tags":["camera","off"]},{"name":"camera","set":"feather","tags":["camera","photo"]},{"name":"cast","set":"feather","tags":["cast","chromecast","airplay"]},{"name":"check-circle","set":"feather","tags":["check","circle"]},{"name":"check-square","set":"feather","tags":["check","square"]},{"name":"chevrons-down","set":"feather","tags":["chevrons","down"]},{"name":"chevrons-left","set":"feather","tags":["chevrons","left"]},{"name":"chevrons-right","set":"feather","tags":["chevrons","right"]},{"name":"chevrons-up","set":"feather","tags":["chevrons","up"]},{"name":"chrome","set":"feather","tags":["chrome"]},{"name":"circle","set":"feather","tags":["circle","off","zero","record"]},{"name":"clipboard","set":"feather","tags":["clipboard","copy"]},{"name":"cloud-drizzle","set":"feather","tags":["cloud","drizzle","weather","shower"]},{"name":"cloud-lightning","set":"feather","tags":["cloud","lightning","weather","bolt"]},{"name":"cloud-off","set":"feather","tags":["cloud","off"]},{"name":"cloud-rain","set":"feather","tags":["cloud","rain","weather"]},{"name":"cloud-snow","set":"feather","tags":["cloud","snow","weather","blizzard"]},{"name":"cloud","set":"feather","tags":["cloud","weather"]},{"name":"codepen","set":"feather","tags":["codepen","logo"]},{"name":"codesandbox","set":"feather","tags":["codesandbox","logo"]},{"name":"coffee","set":"feather","tags":["coffee","drink","cup","mug","tea","cafe","hot","beverage"]},{"name":"columns","set":"feather","tags":["columns","layout"]},{"name":"command","set":"feather","tags":["command","keyboard","cmd","terminal","prompt"]},{"name":"compass","set":"feather","tags":["compass","navigation","safari","travel","direction"]},{"name":"corner-down-left","set":"feather","tags":["corner","down","left","arrow","return"]},{"name":"corner-down-right","set":"feather","tags":["corner","down","right","arrow"]},{"name":"corner-left-down","set":"feather","tags":["corner","left","down","arrow"]},{"name":"corner-left-up","set":"feather","tags":["corner","left","up","arrow"]},{"name":"corner-right-down","set":"feather","tags":["corner","right","down","arrow"]},{"name":"corner-right-up","set":"feather","tags":["corner","right","up","arrow"]},{"name":"corner-up-left","set":"feather","tags":["corner","up","left","arrow"]},{"name":"corner-up-right","set":"feather","tags":["corner","up","right","arrow"]},{"name":"cpu","set":"feather","tags":["cpu","processor","technology"]},{"name":"credit-card","set":"feather","tags":["credit","card","purchase","payment","cc"]},{"name":"crop","set":"feather","tags":["crop","photo","image"]},{"name":"crosshair","set":"feather","tags":["crosshair","aim","target"]},{"name":"delete","set":"feather","tags":["delete","remove"]},{"name":"disc","set":"feather","tags":["disc","album","cd","dvd","music"]},{"name":"divide-circle","set":"feather","tags":["divide","circle"]},{"name":"divide-square","set":"feather","tags":["divide","square"]},{"name":"divide","set":"feather","tags":["divide"]},{"name":"dollar-sign","set":"feather","tags":["dollar","sign","currency","money","payment"]},{"name":"download-cloud","set":"feather","tags":["download","cloud"]},{"name":"dribbble","set":"feather","tags":["dribbble"]},{"name":"droplet","set":"feather","tags":["droplet","water"]},{"name":"edit-2","set":"feather","tags":["edit","2","pencil","change"]},{"name":"edit-3","set":"feather","tags":["edit","3","pencil","change"]},{"name":"edit","set":"feather","tags":["edit","pencil","change"]},{"name":"external-link","set":"feather","tags":["external","link","outbound"]},{"name":"eye-off","set":"feather","tags":["eye","off","view","watch","hide","hidden"]},{"name":"facebook","set":"feather","tags":["facebook","logo","social"]},{"name":"fast-forward","set":"feather","tags":["fast","forward","music"]},{"name":"feather","set":"feather","tags":["feather"]},{"name":"figma","set":"feather","tags":["figma","logo","design","tool"]},{"name":"file-minus","set":"feather","tags":["file","minus","delete","remove","erase"]},{"name":"file-plus","set":"feather","tags":["file","plus","add","create","new"]},{"name":"film","set":"feather","tags":["film","movie","video"]},{"name":"filter","set":"feather","tags":["filter","funnel","hopper"]},{"name":"flag","set":"feather","tags":["flag","report"]},{"name":"folder-minus","set":"feather","tags":["folder","minus","directory"]},{"name":"folder-plus","set":"feather","tags":["folder","plus","directory"]},{"name":"framer","set":"feather","tags":["framer","logo","design","tool"]},{"name":"frown","set":"feather","tags":["frown","emoji","face","bad","sad","emotion"]},{"name":"gift","set":"feather","tags":["gift","present","box","birthday","party"]},{"name":"git-branch","set":"feather","tags":["git","branch","code","version control"]},{"name":"git-commit","set":"feather","tags":["git","commit","code","version control"]},{"name":"git-merge","set":"feather","tags":["git","merge","code","version control"]},{"name":"git-pull-request","set":"feather","tags":["git","pull","request","code","version control"]},{"name":"github","set":"feather","tags":["github","logo","version control"]},{"name":"gitlab","set":"feather","tags":["gitlab","logo","version control"]},{"name":"globe","set":"feather","tags":["globe","world","browser","language","translate"]},{"name":"grid","set":"feather","tags":["grid"]},{"name":"hard-drive","set":"feather","tags":["hard","drive","computer","server","memory","data"]},{"name":"hash","set":"feather","tags":["hash","hashtag","number","pound"]},{"name":"headphones","set":"feather","tags":["headphones","music","audio","sound"]},{"name":"heart","set":"feather","tags":["heart","like","love","emotion"]},{"name":"help-circle","set":"feather","tags":["help","circle","question mark"]},{"name":"hexagon","set":"feather","tags":["hexagon","shape","node.js","logo"]},{"name":"home","set":"feather","tags":["home","house","living"]},{"name":"image","set":"feather","tags":["image","picture"]},{"name":"inbox","set":"feather","tags":["inbox","email"]},{"name":"instagram","set":"feather","tags":["instagram","logo","camera"]},{"name":"italic","set":"feather","tags":["italic"]},{"name":"key","set":"feather","tags":["key","password","login","authentication","secure"]},{"name":"layout","set":"feather","tags":["layout","window","webpage"]},{"name":"life-buoy","set":"feather","tags":["life","buoy","help","life ring","support"]},{"name":"link-2","set":"feather","tags":["link","2","chain","url"]},{"name":"link","set":"feather","tags":["link","chain","url"]},{"name":"linkedin","set":"feather","tags":["linkedin","logo","social media"]},{"name":"loader","set":"feather","tags":["loader"]},{"name":"lock","set":"feather","tags":["lock","security","password","secure"]},{"name":"log-in","set":"feather","tags":["log","in","sign in","arrow","enter"]},{"name":"log-out","set":"feather","tags":["log","out","sign out","arrow","exit"]},{"name":"mail","set":"feather","tags":["mail","email","message"]},{"name":"map-pin","set":"feather","tags":["map","pin","location","navigation","travel","marker"]},{"name":"map","set":"feather","tags":["map","location","navigation","travel"]},{"name":"maximize-2","set":"feather","tags":["maximize","2","fullscreen","arrows","expand"]},{"name":"maximize","set":"feather","tags":["maximize","fullscreen"]},{"name":"meh","set":"feather","tags":["meh","emoji","face","neutral","emotion"]},{"name":"message-circle","set":"feather","tags":["message","circle","comment","chat"]},{"name":"message-square","set":"feather","tags":["message","square","comment","chat"]},{"name":"mic-off","set":"feather","tags":["mic","off","record","sound","mute"]},{"name":"mic","set":"feather","tags":["mic","record","sound","listen"]},{"name":"minimize-2","set":"feather","tags":["minimize","2","exit fullscreen","arrows","close"]},{"name":"minimize","set":"feather","tags":["minimize","exit fullscreen","close"]},{"name":"minus-circle","set":"feather","tags":["minus","circle"]},{"name":"minus-square","set":"feather","tags":["minus","square"]},{"name":"monitor","set":"feather","tags":["monitor","tv","screen","display"]},{"name":"more-horizontal","set":"feather","tags":["more","horizontal","ellipsis"]},{"name":"more-vertical","set":"feather","tags":["more","vertical","ellipsis"]},{"name":"mouse-pointer","set":"feather","tags":["mouse","pointer","arrow","cursor"]},{"name":"move","set":"feather","tags":["move","arrows"]},{"name":"music","set":"feather","tags":["music","note"]},{"name":"navigation-2","set":"feather","tags":["navigation","2","location","travel"]},{"name":"navigation","set":"feather","tags":["navigation","location","travel"]},{"name":"octagon","set":"feather","tags":["octagon","stop"]},{"name":"package","set":"feather","tags":["package","box","container"]},{"name":"paperclip","set":"feather","tags":["paperclip","attachment"]},{"name":"pause-circle","set":"feather","tags":["pause","circle","music","audio","stop"]},{"name":"pause","set":"feather","tags":["pause","music","stop"]},{"name":"pen-tool","set":"feather","tags":["pen","tool","vector","drawing"]},{"name":"percent","set":"feather","tags":["percent","discount"]},{"name":"phone-call","set":"feather","tags":["phone","call","ring"]},{"name":"phone-forwarded","set":"feather","tags":["phone","forwarded","call"]},{"name":"phone-incoming","set":"feather","tags":["phone","incoming","call"]},{"name":"phone-missed","set":"feather","tags":["phone","missed","call"]},{"name":"phone-off","set":"feather","tags":["phone","off","call","mute"]},{"name":"phone-outgoing","set":"feather","tags":["phone","outgoing","call"]},{"name":"phone","set":"feather","tags":["phone","call"]},{"name":"pie-chart","set":"feather","tags":["pie","chart","statistics","diagram"]},{"name":"play-circle","set":"feather","tags":["play","circle","music","start"]},{"name":"plus-circle","set":"feather","tags":["plus","circle","add","new"]},{"name":"plus-square","set":"feather","tags":["plus","square","add","new"]},{"name":"pocket","set":"feather","tags":["pocket","logo","save"]},{"name":"power","set":"feather","tags":["power","on","off"]},{"name":"printer","set":"feather","tags":["printer","fax","office","device"]},{"name":"radio","set":"feather","tags":["radio","signal"]},{"name":"refresh-ccw","set":"feather","tags":["refresh","ccw","arrows"]},{"name":"repeat","set":"feather","tags":["repeat","loop","arrows","change"]},{"name":"rewind","set":"feather","tags":["rewind","music"]},{"name":"rotate-ccw","set":"feather","tags":["rotate","ccw","arrow"]},{"name":"rotate-cw","set":"feather","tags":["rotate","cw","arrow"]},{"name":"rss","set":"feather","tags":["rss","feed","subscribe"]},{"name":"save","set":"feather","tags":["save","floppy disk"]},{"name":"scissors","set":"feather","tags":["scissors","cut"]},{"name":"server","set":"feather","tags":["server"]},{"name":"share-2","set":"feather","tags":["share","2","network","connections"]},{"name":"share","set":"feather","tags":["share"]},{"name":"shield-off","set":"feather","tags":["shield","off","security","insecure"]},{"name":"shopping-bag","set":"feather","tags":["shopping","bag","ecommerce","cart","purchase","store"]},{"name":"shopping-cart","set":"feather","tags":["shopping","cart","ecommerce","purchase","store"]},{"name":"sidebar","set":"feather","tags":["sidebar"]},{"name":"skip-back","set":"feather","tags":["skip","back","music"]},{"name":"skip-forward","set":"feather","tags":["skip","forward","music"]},{"name":"slack","set":"feather","tags":["slack","logo"]},{"name":"slash","set":"feather","tags":["slash","ban","no"]},{"name":"sliders","set":"feather","tags":["sliders","settings","controls"]},{"name":"smartphone","set":"feather","tags":["smartphone","cellphone","device"]},{"name":"smile","set":"feather","tags":["smile","emoji","face","happy","good","emotion"]},{"name":"speaker","set":"feather","tags":["speaker","audio","music"]},{"name":"square","set":"feather","tags":["square"]},{"name":"star","set":"feather","tags":["star","bookmark","favorite","like"]},{"name":"stop-circle","set":"feather","tags":["stop","circle","media","music"]},{"name":"sun","set":"feather","tags":["sun","brightness","weather","light"]},{"name":"sunrise","set":"feather","tags":["sunrise","weather","time","morning","day"]},{"name":"sunset","set":"feather","tags":["sunset","weather","time","evening","night"]},{"name":"tablet","set":"feather","tags":["tablet","device"]},{"name":"tag","set":"feather","tags":["tag","label"]},{"name":"terminal","set":"feather","tags":["terminal","code","command line","prompt"]},{"name":"thermometer","set":"feather","tags":["thermometer","temperature","celsius","fahrenheit","weather"]},{"name":"thumbs-down","set":"feather","tags":["thumbs","down","dislike","bad","emotion"]},{"name":"thumbs-up","set":"feather","tags":["thumbs","up","like","good","emotion"]},{"name":"toggle-left","set":"feather","tags":["toggle","left","on","off","switch"]},{"name":"toggle-right","set":"feather","tags":["toggle","right","on","off","switch"]},{"name":"tool","set":"feather","tags":["tool","settings","spanner"]},{"name":"trash","set":"feather","tags":["trash","garbage","delete","remove","bin"]},{"name":"trello","set":"feather","tags":["trello"]},{"name":"trending-down","set":"feather","tags":["trending","down"]},{"name":"triangle","set":"feather","tags":["triangle","delta"]},{"name":"truck","set":"feather","tags":["truck","delivery","van","shipping","transport","lorry"]},{"name":"tv","set":"feather","tags":["tv","television","stream"]},{"name":"twitch","set":"feather","tags":["twitch","logo"]},{"name":"twitter","set":"feather","tags":["twitter","logo","social"]},{"name":"type","set":"feather","tags":["type","text"]},{"name":"umbrella","set":"feather","tags":["umbrella","rain","weather"]},{"name":"underline","set":"feather","tags":["underline"]},{"name":"unlock","set":"feather","tags":["unlock","security"]},{"name":"upload-cloud","set":"feather","tags":["upload","cloud"]},{"name":"user-check","set":"feather","tags":["user","check","followed","subscribed"]},{"name":"user-minus","set":"feather","tags":["user","minus","delete","remove","unfollow","unsubscribe"]},{"name":"user-plus","set":"feather","tags":["user","plus","new","add","create","follow","subscribe"]},{"name":"user-x","set":"feather","tags":["user","x","delete","remove","unfollow","unsubscribe","unavailable"]},{"name":"user","set":"feather","tags":["user","person","account"]},{"name":"users","set":"feather","tags":["users","group"]},{"name":"video-off","set":"feather","tags":["video","off","camera","movie","film"]},{"name":"video","set":"feather","tags":["video","camera","movie","film"]},{"name":"voicemail","set":"feather","tags":["voicemail","phone"]},{"name":"volume-1","set":"feather","tags":["volume","1","music","sound"]},{"name":"volume-2","set":"feather","tags":["volume","2","music","sound"]},{"name":"volume-x","set":"feather","tags":["volume","x","music","sound","mute"]},{"name":"volume","set":"feather","tags":["volume","music","sound","mute"]},{"name":"watch","set":"feather","tags":["watch","clock","time"]},{"name":"wifi-off","set":"feather","tags":["wifi","off","disabled"]},{"name":"wifi","set":"feather","tags":["wifi","connection","signal","wireless"]},{"name":"wind","set":"feather","tags":["wind","weather","air"]},{"name":"x-circle","set":"feather","tags":["x","circle","cancel","close","delete","remove","times","clear"]},{"name":"x-octagon","set":"feather","tags":["x","octagon","delete","stop","alert","warning","times","clear"]},{"name":"x-square","set":"feather","tags":["x","square","cancel","close","delete","remove","times","clear"]},{"name":"youtube","set":"feather","tags":["youtube","logo","video","play"]},{"name":"zap-off","set":"feather","tags":["zap","off","flash","camera","lightning"]},{"name":"zap","set":"feather","tags":["zap","flash","camera","lightning"]},{"name":"zoom-in","set":"feather","tags":["zoom","in","magnifying glass"]},{"name":"zoom-out","set":"feather","tags":["zoom","out","magnifying glass"]},{"name":"dimension-account","set":"core","tags":["account","chart of accounts","ledger","general ledger","book","coa","科目","会计科目","总账"]},{"name":"dimension-audit","set":"core","tags":["audit","audit trail","adjustment","search","inspect","file","审计","审计轨迹","调整"]},{"name":"dimension-currency","set":"core","tags":["currency","exchange","fx","conversion","dollar","yen","rmb","币种","货币","汇率","换算"]},{"name":"dimension-custom_default","set":"core","tags":["custom","dimension","model","cube","box","自定义","维度"]},{"name":"dimension-entity","set":"core","tags":["entity","legal entity","company","organization","buildings","subsidiary","实体","法人","公司","组织"]},{"name":"dimension-group","set":"core","tags":["group","consolidation group","hierarchy","network","tree","集团","组","层级"]},{"name":"dimension-intercompany","set":"core","tags":["intercompany","counterparty","exchange","transfer","往来","内部往来","关联方"]},{"name":"dimension-subtable","set":"core","tags":["subtable","table","detail","sub table","grid","子表","明细表"]},{"name":"dimension-time","set":"core","tags":["time","period","fiscal period","calendar","clock","month","year","期间","会计期间","时间","年月"]},{"name":"dimension-version","set":"core","tags":["version","actual","budget","forecast","branch","版本","实际","预算","预测"]},{"name":"version-exempt","set":"core","tags":["version exempt","exclude","excluded","branch","remove","版本豁免","排除"]},{"name":"model-exempt","set":"core","tags":["model exempt","exclude","excluded","cube","remove","模型豁免","排除"]},{"name":"modeling","set":"core","tags":["modeling","model design","designer","monitor","screen","cube","建模","模型设计"]},{"name":"restatement","set":"core","tags":["restatement","retrospective","history","adjust","restate","重述","追溯调整"]},{"name":"review","set":"core","tags":["review","check","view","inspect","eye","file","审阅","复核","查看"]},{"name":"admin","set":"core","tags":["admin","administration","settings","system","manage","管理","系统设置"]},{"name":"analytics","set":"core","tags":["analytics","analysis","insight","trend","dashboard","monitor","chart","分析","洞察","趋势"]},{"name":"automation","set":"core","tags":["automation","automate","run","schedule","gear","play","自动化","自动执行"]},{"name":"data_integration","set":"core","tags":["data integration","sync","connector","external data","数据集成","外部数据","接入","database","sources","merge","hub","数据源","汇集"]},{"name":"reconciliation-entity","set":"core","tags":["reconciliation","entity","company","buildings","实体对账","法人对账"]},{"name":"reconciliation-balance","set":"core","tags":["reconciliation","balance","coins","money","余额对账","科目余额"]},{"name":"scenario","set":"core","tags":["scenario","what if","compare","budget","forecast","情景","假设","对比"]},{"name":"report","set":"core","tags":["report","financial report","chart","line chart","报表","财务报告"]},{"name":"spreadsheet","set":"core","tags":["spreadsheet","sheet","table","grid","excel","电子表格","在线表格"]},{"name":"sheet","set":"core","tags":["sheet","worksheet","form","table","grid","表单","工作表"]},{"name":"script","set":"core","tags":["script","code","custom code","developer","脚本","代码","自定义代码"]},{"name":"workflow_group","set":"core","tags":["workflow group","workflow","folder","process","工作流组","流程集合"]},{"name":"model_interaction","set":"core","tags":["model interaction","model sync","link","exchange","cubes","模型交互","数据联动"]},{"name":"announcement","set":"core","tags":["announcement","notice","megaphone","broadcast","news","file","公告","通知","公示"]},{"name":"approval","set":"core","tags":["approval","approve","approved","badge","check","verified","审批","批准","通过"]},{"name":"data-import","set":"core","tags":["data import","import","load","database","in","down","数据导入","导入"]},{"name":"data-export","set":"core","tags":["data export","export","unload","database","out","up","数据导出","导出"]},{"name":"execution","set":"core","tags":["execution","execute","run","calculate","gear","check","done","执行","运行","计算"]},{"name":"submission","set":"core","tags":["submission","submit","send","report up","check","提交","上报"]},{"name":"expense_control","set":"core","tags":["expense control","expense","cost","budget control","gear","dollar","费用控制","费用","成本"]},{"name":"expense_control-aggregate","set":"core","tags":["expense control","aggregate","total","sum","merge","gear","汇总控制","汇总"]},{"name":"expense_control-limit","set":"core","tags":["expense control","limit","ceiling","cap","max","gear","上限控制","上限"]},{"name":"expense_control-enumerated","set":"core","tags":["expense control","enumerated","list","itemized","detail","gear","明细列举","明细"]},{"name":"expense_control-quota","set":"core","tags":["expense control","quota","percent","ratio","allowance","gear","额度控制","额度","比例"]},{"name":"profile-access","set":"core","tags":["profile access","access","permission","key","user","authorization","权限档案","访问授权","权限"]},{"name":"usergroup","set":"core","tags":["user group","users","team","members","people","用户组","团队"]},{"name":"user","set":"core","tags":["user","person","account","profile","member","用户","个人"]},{"name":"profile-system","set":"core","tags":["profile system","system","settings","user","gear","config","系统档案","系统配置"]},{"name":"rules-initialization","set":"core","tags":["rules","initialization","init","start","power","reset","规则","初始化"]},{"name":"rules-subsidy","set":"core","tags":["rules","subsidy","grant","allowance","dollar","plus","规则","补贴"]},{"name":"rules-transfer","set":"core","tags":["rules","transfer","move","exchange","arrows","规则","转移","调拨"]},{"name":"rules-import","set":"core","tags":["rules","import","load","in","down","规则","导入"]},{"name":"rules-reclassification","set":"core","tags":["rules","reclassification","reclass","split","move","规则","重分类"]},{"name":"rules-allocation","set":"core","tags":["rules","allocation","allocate","distribute","split","规则","分摊","分配"]},{"name":"rules-account","set":"core","tags":["rules","account","list","chart of accounts","规则","科目"]},{"name":"rules-carryforward","set":"core","tags":["rules","carry forward","carryforward","roll forward","forward","规则","结转"]},{"name":"rules-compound","set":"core","tags":["rules","compound","composite","hexagon","规则","复合"]},{"name":"rules-currency","set":"core","tags":["rules","currency","dollar","fx","规则","币种","货币"]},{"name":"rules-deduction","set":"core","tags":["rules","deduction","deduct","minus","subtract","dollar","规则","扣除","抵扣"]},{"name":"rules-depreciation","set":"core","tags":["rules","depreciation","decline","trend down","规则","折旧"]},{"name":"rules-eliminations_n_adjustments","set":"core","tags":["rules","elimination","eliminations","adjustment","adjustments","plus minus","规则","抵销","调整"]},{"name":"rules-general","set":"core","tags":["rules","general","text","lines","规则","通用"]},{"name":"rules-intercompany","set":"core","tags":["rules","intercompany","buildings","companies","规则","内部往来"]},{"name":"rules-journal_matching","set":"core","tags":["rules","journal matching","match","lines","check","规则","凭证匹配","匹配"]},{"name":"rules-method","set":"core","tags":["rules","method","path","route","flow","arrow","规则","方法"]},{"name":"rules-method_coefficient","set":"core","tags":["rules","method coefficient","coefficient","factor","multiply","bracket","规则","方法系数","系数"]},{"name":"rules-validation","set":"core","tags":["rules","validation","validate","check","verify","规则","校验","验证"]},{"name":"rules-shared","set":"core","tags":["rules","shared","share","network","规则","共享"]},{"name":"task-manual","set":"core","tags":["task","manual","edit","pencil","clipboard","任务","手工","手动"]},{"name":"task-reclassification","set":"core","tags":["task","reclassification","reclass","clipboard","任务","重分类"]},{"name":"task-account","set":"core","tags":["task","account","list","clipboard","任务","科目"]},{"name":"task-allocation","set":"core","tags":["task","allocation","allocate","distribute","clipboard","任务","分摊"]},{"name":"task-carryforward","set":"core","tags":["task","carry forward","carryforward","clipboard","任务","结转"]},{"name":"task-currency","set":"core","tags":["task","currency","dollar","fx","clipboard","任务","币种"]},{"name":"task-deduction","set":"core","tags":["task","deduction","deduct","minus","clipboard","任务","扣除"]},{"name":"task-depreciation","set":"core","tags":["task","depreciation","decline","clipboard","任务","折旧"]},{"name":"task-eliminations_n_adjustments","set":"core","tags":["task","elimination","adjustment","plus minus","clipboard","任务","抵销","调整"]},{"name":"task-etl","set":"core","tags":["task","etl","extract","transform","load","database","clipboard","任务","数据抽取"]},{"name":"task-shared","set":"core","tags":["task","shared","share","clipboard","任务","共享"]},{"name":"task-intercompany","set":"core","tags":["task","intercompany","buildings","clipboard","任务","内部往来"]},{"name":"ownership","set":"core","tags":["ownership","owner","own","belongs to","responsible","key","hand","handover","custody","所有权","所有者","负责人","归属","持有"]},{"name":"ai","set":"core","tags":["ai","artificial intelligence","assistant","copilot","agent","ai agent","generative","smart","intelligent","sparkles","badge","workspace","automation"]},{"name":"ai-sparkles","set":"core","tags":["ai","sparkles","magic","generate","generative","suggest","assistant","intelligent","smart","stars","auto"]}];