proje-react-panel 1.11.1 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/how_to.md CHANGED
@@ -137,6 +137,29 @@ export class ProductDetails {
137
137
  }
138
138
  ```
139
139
 
140
+ ### 4.4.1 Page size
141
+
142
+ `ListPage` asks for as many rows as fit the datagrid, instead of a fixed page size. It measures the
143
+ grid element (so your own shell CSS is accounted for) and divides by the row height — which is
144
+ declared, not measured, and then applied to the row, so the number cannot drift from what is on
145
+ screen:
146
+
147
+ ```ts
148
+ // Default: auto page size, library row height, nothing to declare.
149
+ @List({ getData: dataFetchers.products.getAll, primaryId: 'id' })
150
+
151
+ // A list with taller rows (an image cell, or your own row CSS) declares its height.
152
+ @List({ getData: dataFetchers.products.getAll, primaryId: 'id', rowHeight: 124 })
153
+
154
+ // Opt out: `getData` (or the server) decides the page size, as before.
155
+ @List({ getData: dataFetchers.products.getAll, primaryId: 'id', autoCalculate: false })
156
+ ```
157
+
158
+ The computed size is sent as `limit`, so the backend contract in §2 is unchanged. It is clamped to
159
+ 5–100 rows, and recomputed on window resize.
160
+
161
+ ---
162
+
140
163
  ### 4.5 Route the pages
141
164
 
142
165
  ```tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proje-react-panel",
3
- "version": "1.11.1",
3
+ "version": "1.12.0",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "author": "SEFA DEMİR",
@@ -9,6 +9,7 @@
9
9
  "module": "dist/index.esm.js",
10
10
  "source": "src/index.ts",
11
11
  "types": "dist/index.d.ts",
12
+ "packageManager": "yarn@4.17.0",
12
13
  "scripts": {
13
14
  "test": "jest",
14
15
  "build": "rollup -c",
@@ -23,12 +24,12 @@
23
24
  "url": "https://github.com/demirsefa/proje-react-panel/issues"
24
25
  },
25
26
  "homepage": "https://github.com/demirsefa/proje-react-panel#readme",
26
- "dependencies": {},
27
27
  "devDependencies": {
28
28
  "@eslint/compat": "^1.2.9",
29
29
  "@hookform/resolvers": "^4.1.3",
30
30
  "@rollup/plugin-commonjs": "^28.0.3",
31
31
  "@rollup/plugin-node-resolve": "^16.0.1",
32
+ "@svgr/core": "^8.1.0",
32
33
  "@svgr/rollup": "^8.1.0",
33
34
  "@testing-library/dom": "^10.4.1",
34
35
  "@testing-library/react": "^16.3.2",
@@ -0,0 +1,48 @@
1
+ import { readdirSync, readFileSync } from 'fs';
2
+ import path from 'path';
3
+ import { describe, expect, it } from '@jest/globals';
4
+ import { transform } from '@svgr/core';
5
+
6
+ /**
7
+ * Ikonlar bundle'a SVGR + SVGO'dan gecerek gomuluyor ve SVGO'nun varsayilan
8
+ * `removeViewBox` plugin'i, viewBox width/height ile birebir ayniysa onu
9
+ * "gereksiz" sayip siliyor. viewBox'siz bir SVG olceklenemez: tuketici CSS'te
10
+ * `.icon { width: 16px }` verdiginde ikon kucultulmez, KIRPILIR — check/cross
11
+ * bir kez tam bu yuzden gorunmez oldu, dist'e yama atilarak kapatildi, yama
12
+ * baska bir fix'le dusunce geri geldi.
13
+ *
14
+ * Bu yuzden test tek bir ikonu degil ayari kilitliyor: rollup'in okudugu
15
+ * `svgo.icons.json` ile HER ikon gercekten donusturuluyor. Ayar bozulursa —
16
+ * ya da viewBox'i width/height'iyla ayni yeni bir ikon eklenirse — kirmizi yanar.
17
+ */
18
+ const repoRoot = path.resolve(__dirname, '../../..');
19
+ const iconDir = path.join(repoRoot, 'src/assets/icons/svg');
20
+ const svgoConfig = JSON.parse(
21
+ readFileSync(path.join(repoRoot, 'svgo.icons.json'), 'utf8')
22
+ );
23
+
24
+ const icons = readdirSync(iconDir).filter((f) => f.endsWith('.svg'));
25
+
26
+ const compile = (file: string): Promise<string> =>
27
+ transform(
28
+ readFileSync(path.join(iconDir, file), 'utf8'),
29
+ { plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx'], svgoConfig },
30
+ { componentName: 'Icon', filePath: path.join(iconDir, file) }
31
+ );
32
+
33
+ describe('icon build config', () => {
34
+ it('has icons to check', () => {
35
+ expect(icons.length).toBeGreaterThan(0);
36
+ });
37
+
38
+ it.each(icons)('%s keeps its viewBox', async (file) => {
39
+ expect(await compile(file)).toContain('viewBox=');
40
+ });
41
+
42
+ // Kirpilma yalnizca viewBox'in varligina degil dogruluguna bagli: yanlis bir
43
+ // viewBox da ayni sonucu verir, o yuzden regresyonun kaynagi olan ikon acikca
44
+ // kendi kutusuyla kilitli.
45
+ it('check.svg keeps the exact box its paths are drawn in', async () => {
46
+ expect(await compile('check.svg')).toContain('viewBox="0 0 24 24"');
47
+ });
48
+ });
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ import { TextDecoder, TextEncoder } from 'util';
5
+ // jsdom'da TextEncoder yok; testing-library'nin cektigi bagimliliklar import
6
+ // aninda okuyor.
7
+ Object.assign(globalThis, { TextEncoder, TextDecoder });
8
+
9
+ import React from 'react';
10
+ import { describe, expect, it, jest } from '@jest/globals';
11
+ import { render, screen } from '@testing-library/react';
12
+ import { Pagination } from '../../../components/list/Pagination';
13
+
14
+ const lastPageButton = () => {
15
+ const numbered = screen
16
+ .getAllByRole('button')
17
+ .map(button => Number(button.textContent))
18
+ .filter(value => Number.isFinite(value) && value > 0);
19
+ return Math.max(...numbered);
20
+ };
21
+
22
+ describe('Pagination', () => {
23
+ it('eksik dolu son sayfayi da cizer', () => {
24
+ // 2151 kayit / 10 limit = 216 sayfa. `floor` ile 215 ciziliyordu ve son
25
+ // kayda hicbir sayfadan ulasilamiyordu.
26
+ render(
27
+ <Pagination pagination={{ total: 2151, page: 1, limit: 10 }} onPageChange={jest.fn()} />
28
+ );
29
+ expect(lastPageButton()).toBe(216);
30
+ });
31
+
32
+ it('ikinci sayfasi olan listede gizlenmez', () => {
33
+ // total < 2*limit: `floor` 1 sayfa sanip pagination'i tamamen gizliyordu.
34
+ render(<Pagination pagination={{ total: 15, page: 1, limit: 10 }} onPageChange={jest.fn()} />);
35
+ expect(lastPageButton()).toBe(2);
36
+ });
37
+
38
+ it('tek sayfaya sigan listede cizilmez', () => {
39
+ const { container } = render(
40
+ <Pagination pagination={{ total: 8, page: 1, limit: 10 }} onPageChange={jest.fn()} />
41
+ );
42
+ expect(container.firstChild).toBeNull();
43
+ });
44
+
45
+ it('limit gelmeden cizilmez', () => {
46
+ // Liste ilk fetch'ini beklerken pagination state'i {0,0,0}; bolme NaN
47
+ // uretip "NaN <= 1" false donuyordu.
48
+ const { container } = render(
49
+ <Pagination pagination={{ total: 0, page: 0, limit: 0 }} onPageChange={jest.fn()} />
50
+ );
51
+ expect(container.firstChild).toBeNull();
52
+ });
53
+ });
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from '@jest/globals';
2
+ import {
3
+ LIST_FALLBACK_LIMIT,
4
+ LIST_MAX_LIMIT,
5
+ LIST_MIN_LIMIT,
6
+ LIST_ROW_HEIGHT,
7
+ calculateListLimit,
8
+ } from '../../../components/list/listMetrics';
9
+
10
+ describe('calculateListLimit', () => {
11
+ it('istenen satirlar konteynere sigar, bir fazlasi sigmaz', () => {
12
+ const containerHeight = 780;
13
+ const limit = calculateListLimit(containerHeight);
14
+
15
+ // Tablo basligi da konteynerin icinde; onun icin bir satirlik yer ayriliyor.
16
+ // Toplam konteyneri asiyorsa otomatik boyut kendi amacinin tersine scroll
17
+ // uretiyor demektir.
18
+ expect((limit + 1) * LIST_ROW_HEIGHT).toBeLessThanOrEqual(containerHeight);
19
+ expect((limit + 2) * LIST_ROW_HEIGHT).toBeGreaterThan(containerHeight);
20
+ });
21
+
22
+ it('yuksek satirda daha az kayit ister', () => {
23
+ expect(calculateListLimit(780, 124)).toBeLessThan(calculateListLimit(780));
24
+ });
25
+
26
+ it('cok kisa konteynerde tabana, cok uzununda tavana yapisir', () => {
27
+ expect(calculateListLimit(120)).toBe(LIST_MIN_LIMIT);
28
+ expect(calculateListLimit(100000)).toBe(LIST_MAX_LIMIT);
29
+ });
30
+
31
+ it('olculemeyen degerlerde fallback limit doner', () => {
32
+ expect(calculateListLimit(Number.NaN)).toBe(LIST_FALLBACK_LIMIT);
33
+ expect(calculateListLimit(780, 0)).toBe(LIST_FALLBACK_LIMIT);
34
+ expect(calculateListLimit(0)).toBe(LIST_FALLBACK_LIMIT);
35
+ });
36
+ });
@@ -1,3 +1,3 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
3
3
  <svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M903.232 256l56.768 50.432L512 768 64 306.432 120.768 256 512 659.072z" fill="#000000" /></svg>
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { Link } from 'react-router';
3
3
  import { EmptyList } from './EmptyList';
4
+ import { LoadingScreen } from '../LoadingScreen';
4
5
  import SearchIcon from '../../assets/icons/svg/search.svg';
5
6
  import PencilIcon from '../../assets/icons/svg/pencil.svg';
6
7
  import DownArrowIcon from '../../assets/icons/svg/down-arrow-backup-2.svg';
@@ -21,12 +22,24 @@ const ACTIONS_COLUMN_WIDTH = '120px';
21
22
  interface DatagridProps<T extends AnyClass> {
22
23
  data: T[];
23
24
  listPageMeta: ListPageMeta<T>;
25
+ loading?: boolean;
26
+ /**
27
+ * Satir yuksekligi satira UYGULANIR, sadece bildirilmez: otomatik sayfa
28
+ * boyutu bu sayiya gore hesaplandigi icin gercekle ayrismasina izin
29
+ * verilemez. Inline veriliyor ki tuketicinin kendi tablo CSS'i olsa da
30
+ * gecerli olsun.
31
+ */
32
+ rowHeight?: number;
33
+ containerRef?: React.RefObject<HTMLDivElement | null>;
24
34
  onRemoveItem?: (item: T) => Promise<void>;
25
35
  }
26
36
 
27
37
  export function Datagrid<T extends AnyClass>({
28
38
  data,
29
39
  listPageMeta,
40
+ loading,
41
+ rowHeight,
42
+ containerRef,
30
43
  onRemoveItem,
31
44
  }: DatagridProps<T>) {
32
45
  const cells = listPageMeta.cells;
@@ -38,8 +51,10 @@ export function Datagrid<T extends AnyClass>({
38
51
  : null;
39
52
 
40
53
  return (
41
- <div className="datagrid">
42
- {!data || data.length === 0 ? (
54
+ <div className="datagrid" ref={containerRef}>
55
+ {loading ? (
56
+ <LoadingScreen id={listPageMeta.class.key} />
57
+ ) : !data || data.length === 0 ? (
43
58
  <EmptyList />
44
59
  ) : (
45
60
  <table className="datagrid-table">
@@ -75,7 +90,7 @@ export function Datagrid<T extends AnyClass>({
75
90
  | undefined)
76
91
  : null;
77
92
  return (
78
- <tr key={index}>
93
+ <tr key={index} style={{ height: rowHeight }}>
79
94
  {cells.map((configuration: CellConfiguration) => {
80
95
  return (
81
96
  <CellField
@@ -1,13 +1,23 @@
1
- import React, { useMemo, useCallback, useEffect, useState, useId } from 'react';
1
+ import React, {
2
+ useMemo,
3
+ useCallback,
4
+ useEffect,
5
+ useLayoutEffect,
6
+ useRef,
7
+ useState,
8
+ useId,
9
+ } from 'react';
2
10
  import { useParams, useNavigate } from 'react-router';
3
11
  import { Datagrid } from './Datagrid';
4
12
  import { ErrorComponent } from '../ErrorComponent';
5
- import { LoadingScreen } from '../LoadingScreen';
6
13
  import { AnyClass, AnyClassConstructor } from '../../types/AnyClass';
7
14
  import { Pagination } from './Pagination';
8
15
  import { ListHeader } from './ListHeader';
9
16
  import { FilterPopup } from './FilterPopup';
10
17
  import { getListPageMeta } from '../../decorators/list/getListPageMeta';
18
+ import { LIST_ROW_HEIGHT, calculateListLimit } from './listMetrics';
19
+
20
+ const RESIZE_DEBOUNCE_MS = 200;
11
21
 
12
22
  export function ListPage<T extends AnyClass>({
13
23
  model,
@@ -24,17 +34,51 @@ export function ListPage<T extends AnyClass>({
24
34
  const [error, setError] = useState<unknown>(null);
25
35
 
26
36
  const [pagination, setPagination] = useState({ total: 0, page: 0, limit: 0 });
37
+
38
+ const rowHeight = listPageMeta.class.rowHeight ?? LIST_ROW_HEIGHT;
39
+ const autoCalculate = listPageMeta.class.autoCalculate ?? true;
40
+
41
+ const datagridRef = useRef<HTMLDivElement>(null);
42
+ const [containerHeight, setContainerHeight] = useState<number | null>(null);
43
+
44
+ // undefined: limit gonderme (autoCalculate kapali). null: henuz olculmedi,
45
+ // yanlis boyutla bir istek atmaktansa bekle.
46
+ const limit = !autoCalculate
47
+ ? undefined
48
+ : containerHeight === null
49
+ ? null
50
+ : calculateListLimit(containerHeight, rowHeight);
51
+
27
52
  const [isFilterOpen, setIsFilterOpen] = useState(false);
28
53
  const [activeFilters, setActiveFilters] = useState<Record<string, string>>();
29
54
  const params = useParams();
30
55
  const navigate = useNavigate();
31
56
 
57
+ // Gorsel hucresi satiri kutuphanenin olcusunun cok uzerine cikariyor; tablo
58
+ // artik satiri kirptigi icin bunu soylemeden birakmak gorseli sessizce
59
+ // kucultmek olurdu. Uretim derlemesinde sussun, tuketicinin konsolunu
60
+ // kirletmeyelim.
61
+ useEffect(() => {
62
+ if (process.env.NODE_ENV === 'production') return;
63
+ if (listPageMeta.class.rowHeight !== undefined) return;
64
+ if (!listPageMeta.cells.some(cell => cell.type === 'image')) return;
65
+
66
+ console.warn(
67
+ `[proje-react-panel] "${listPageMeta.class.key}" listesinde gorsel hucresi var ` +
68
+ `ama rowHeight verilmemis; satir ${LIST_ROW_HEIGHT}px'e kirpilacak. ` +
69
+ `@List({ rowHeight: ... }) ile gorselin sigacagi yuksekligi bildir.`
70
+ );
71
+ }, [listPageMeta]);
72
+
32
73
  const fetchData = useCallback(
33
74
  async (page: number, filters?: Record<string, string>) => {
34
75
  setLoading(true);
35
76
  try {
36
77
  const result = await listPageMeta.class.getData({
37
78
  page,
79
+ // autoCalculate kapaliysa limit hic gonderilmez: sayfa boyutunu
80
+ // eskisi gibi `getData`/sunucu belirlesin.
81
+ ...(limit ? { limit } : {}),
38
82
  filters: filters ?? activeFilters ?? {},
39
83
  });
40
84
  //TODO: any is not a good solution, we need to find a better way to do this
@@ -52,7 +96,7 @@ export function ListPage<T extends AnyClass>({
52
96
  setLoading(false);
53
97
  }
54
98
  },
55
- [activeFilters, listPageMeta.class]
99
+ [activeFilters, limit, listPageMeta.class]
56
100
  );
57
101
 
58
102
  useEffect(() => {
@@ -60,15 +104,47 @@ export function ListPage<T extends AnyClass>({
60
104
  const filtersFromUrl: Record<string, string> = {};
61
105
  searchParams.forEach((value, key) => {
62
106
  filtersFromUrl[key] = value;
63
- });
107
+ });
64
108
  setActiveFilters(filtersFromUrl);
65
109
  }, []);
66
110
 
111
+ // Datagrid ilk boyamadan once olculuyor: veri beklerken de render edildigi
112
+ // icin yuksekligi hazir, yani dogru limit ILK istekte kullanilabiliyor.
113
+ // Olcum penceye degil elemana bakiyor; tuketicinin kabugu (sidebar, baslik,
114
+ // dolgu) ne kadar yer kaplarsa kaplasin hesap dogru kaliyor.
115
+ //
116
+ // ResizeObserver yerine window resize: icerik buyudukce buyuyen bir
117
+ // konteynerde observer "daha cok satir iste -> daha uzun konteyner" dongusune
118
+ // girebilirdi. Yuksekligi degistiren gercek olay zaten pencere boyutu.
119
+ useLayoutEffect(() => {
120
+ if (!autoCalculate) return;
121
+
122
+ const measure = () => {
123
+ const element = datagridRef.current;
124
+ if (!element) return;
125
+ const next = element.clientHeight;
126
+ setContainerHeight(previous => (previous === next ? previous : next));
127
+ };
128
+ measure();
129
+
130
+ let timer: ReturnType<typeof setTimeout>;
131
+ const onResize = () => {
132
+ clearTimeout(timer);
133
+ timer = setTimeout(measure, RESIZE_DEBOUNCE_MS);
134
+ };
135
+
136
+ window.addEventListener('resize', onResize);
137
+ return () => {
138
+ clearTimeout(timer);
139
+ window.removeEventListener('resize', onResize);
140
+ };
141
+ }, [autoCalculate]);
142
+
67
143
  useEffect(() => {
68
- if (activeFilters) {
144
+ if (activeFilters && limit !== null) {
69
145
  fetchData(parseInt(params.page as string) || 1, activeFilters);
70
146
  }
71
- }, [fetchData, params.page, activeFilters, listPageMeta.class.getData]);
147
+ }, [fetchData, limit, params.page, activeFilters, listPageMeta.class.getData]);
72
148
 
73
149
  const handleFilterApply = (filters: Record<string, string>) => {
74
150
  setActiveFilters(filters);
@@ -86,11 +162,18 @@ export function ListPage<T extends AnyClass>({
86
162
  fetchData(1, filters); // Reset to first page when filters change
87
163
  };
88
164
 
89
- if (loading) return <LoadingScreen id={id} />;
165
+ // Yukleme ekrani artik sayfanin tamamini degil datagrid'in icini kapliyor:
166
+ // konteyner ilk istekten once render olmazsa olculemez, olculemezse ilk
167
+ // istek yanlis sayfa boyutuyla gider ve duzeltmek ikinci bir istek olurdu.
90
168
  if (error) return <ErrorComponent id={id} error={error} />;
91
169
 
92
170
  return (
93
- <div className="list">
171
+ <div
172
+ className="list"
173
+ // Satir yuksekligi hucrelere de lazim (ornegin gorsel bu yukseklige
174
+ // sigdiriliyor); tek kaynak yine `@List({ rowHeight })`.
175
+ style={{ '--prp-list-row-height': `${rowHeight}px` } as React.CSSProperties}
176
+ >
94
177
  <ListHeader
95
178
  listPageMeta={listPageMeta}
96
179
  filtered={!!(activeFilters && !!Object.keys(activeFilters).length)}
@@ -100,6 +183,9 @@ export function ListPage<T extends AnyClass>({
100
183
  <Datagrid
101
184
  listPageMeta={listPageMeta}
102
185
  data={data}
186
+ loading={loading}
187
+ rowHeight={rowHeight}
188
+ containerRef={datagridRef}
103
189
  onRemoveItem={async () => {
104
190
  await fetchData(pagination.page);
105
191
  }}
@@ -11,7 +11,11 @@ interface PaginationProps {
11
11
 
12
12
  export function Pagination({ pagination, onPageChange }: PaginationProps) {
13
13
  const { total, page, limit } = pagination;
14
- const totalPages = Math.floor(total / limit);
14
+ // `floor` son, eksik dolu sayfayi hic saymiyordu: 2151 kayit / 10 limit ->
15
+ // 215 sayfa cizilir, son kayda hicbir sayfadan ulasilamazdi. Daha kotusu
16
+ // total < 2*limit olan her listede sonuc 1 cikip pagination tamamen
17
+ // gizleniyor, ikinci sayfadaki kayitlar erisilemez oluyordu.
18
+ const totalPages = limit > 0 ? Math.ceil(total / limit) : 0;
15
19
 
16
20
  if (totalPages <= 1) return null;
17
21
 
@@ -17,11 +17,16 @@ export function ImageCell({ item, configuration }: ImageCellProps) {
17
17
  // maxWidth sart: sabit tablo duzeninde kolon 100px'ten dar kalabiliyor ve
18
18
  // hucre tasmayi kirptigi icin gorsel sessizce yarim gorunurdu. Kucultmek
19
19
  // kirpmaktan iyi; objectFit: contain oranı koruyor.
20
+ //
21
+ // maxHeight ayni isi dikeyde yapiyor: gorsel satir yuksekliginden uzun
22
+ // olursa satiri tek basina buyutur ve otomatik sayfa boyutunun dayandigi
23
+ // rowHeight yalan olurdu. Gorseli buyuk gormek isteyen liste
24
+ // `@List({ rowHeight })` ile satiri buyutur.
20
25
  <img
21
26
  width={100}
22
27
  height={100}
23
28
  src={imageConfiguration.baseUrl + value}
24
- style={{ objectFit: 'contain', maxWidth: '100%' }}
29
+ style={{ objectFit: 'contain', maxWidth: '100%', maxHeight: 'var(--prp-list-row-height)' }}
25
30
  alt=""
26
31
  />
27
32
  );
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Otomatik sayfa boyutunun iki girdisi var ve ikisi ayri yollardan geliyor:
3
+ *
4
+ * - Datagrid'in yuksekligi OLCULUR. Tuketici kutuphanenin liste CSS'ini
5
+ * kullanmak zorunda degil (kendi kabugunu yazan bir panelde datagrid ne
6
+ * `100vh - 120px`'tir ne de baska bir sabit), dolayisiyla buraya px yazmak
7
+ * sessizce yanlislanacak bir varsayim olurdu.
8
+ * - Satir yuksekligi BEYAN EDILIR ve tabloya dayatilir (`@List({ rowHeight })`).
9
+ * Olculmuyor: olcmek icin once satirin render olmasi, yani bir istek atilmis
10
+ * olmasi gerekirdi; dogru limit ikinci bir istek demek olurdu.
11
+ */
12
+
13
+ /** Olcum yapilamadiginda (SSR, jsdom, yukseklik 0) kullanilan sayfa boyutu. */
14
+ export const LIST_FALLBACK_LIMIT = 10;
15
+
16
+ /**
17
+ * Satirin varsayilan yuksekligi (px): hucre dolgusu + satir kutusu + alt cizgi.
18
+ * Satirini kendi CSS'iyle degistiren ya da `image` gibi yuksek hucre tasiyan
19
+ * tuketici `@List({ rowHeight })` ile kendi olcusunu verir.
20
+ */
21
+ export const LIST_ROW_HEIGHT = 44;
22
+
23
+ /**
24
+ * Otomatik hesabin tabani ve tavani: cok kisa ekranda kullanilamaz bir liste,
25
+ * cok uzun ekranda sunucuyu doven bir sorgu cikmasin.
26
+ */
27
+ export const LIST_MIN_LIMIT = 5;
28
+ export const LIST_MAX_LIMIT = 100;
29
+
30
+ /**
31
+ * Datagrid'e kac satir sigiyorsa o kadar kayit iste. Tablo basligi da o alanin
32
+ * icinde duruyor, o yuzden bir satir dusuluyor; dusulmezse hesap tam bir satir
33
+ * tasar ve otomatik boyut kendi amacinin tersine scroll uretir.
34
+ */
35
+ export function calculateListLimit(containerHeight: number, rowHeight = LIST_ROW_HEIGHT): number {
36
+ if (!Number.isFinite(containerHeight) || containerHeight <= 0 || rowHeight <= 0) {
37
+ return LIST_FALLBACK_LIMIT;
38
+ }
39
+
40
+ const rows = Math.floor(containerHeight / rowHeight) - 1;
41
+ return Math.min(LIST_MAX_LIMIT, Math.max(LIST_MIN_LIMIT, rows));
42
+ }
@@ -38,6 +38,21 @@ export interface ListOptions<T> {
38
38
  actions?: ((item: T) => ListActionOptions<T>) | ListActionOptions<T>;
39
39
  primaryId?: string;
40
40
  key?: string;
41
+ /**
42
+ * Sayfa boyutunu ekrana sigan satir sayisindan hesapla. Varsayilan acik:
43
+ * sabit bir sayfa boyutu (ornegin 10) buyuk ekranda tablonun altinda kocaman
44
+ * bir bosluk birakiyordu — datagrid yuksekligi viewport'a civili.
45
+ * Kapatirsan `getData` limit'i eskisi gibi kendi belirler.
46
+ */
47
+ autoCalculate?: boolean;
48
+ /**
49
+ * Satir yuksekligi (px). Sadece bir bilgi degil, tabloya UYGULANIR: satira
50
+ * bu yukseklik verilir ve hucre icerigi (ornegin gorsel) buna kirpilir.
51
+ * Boylece beyan ile gercek asla ayrisamaz. Yalnizca satirini kendi CSS'iyle
52
+ * degistiren ya da `image` gibi yuksek hucre tasiyan listelerde gerekir;
53
+ * verilmezse kutuphanenin kendi olcusu (`LIST_ROW_HEIGHT`) kullanilir.
54
+ */
55
+ rowHeight?: number;
41
56
  }
42
57
 
43
58
  export type ListConfiguration<T> = ListOptions<T> & {
@@ -0,0 +1,6 @@
1
+ {
2
+ "plugins": [
3
+ { "name": "preset-default", "params": { "overrides": { "removeViewBox": false } } },
4
+ "prefixIds"
5
+ ]
6
+ }
@@ -1,54 +0,0 @@
1
- <component name="ProjectCodeStyleConfiguration">
2
- <code_scheme name="Project" version="173">
3
- <HTMLCodeStyleSettings>
4
- <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
5
- </HTMLCodeStyleSettings>
6
- <JSCodeStyleSettings version="0">
7
- <option name="FORCE_SEMICOLON_STYLE" value="true" />
8
- <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
9
- <option name="FORCE_QUOTE_STYlE" value="true" />
10
- <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
11
- <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
12
- <option name="SPACES_WITHIN_IMPORTS" value="true" />
13
- </JSCodeStyleSettings>
14
- <TypeScriptCodeStyleSettings version="0">
15
- <option name="FORCE_SEMICOLON_STYLE" value="true" />
16
- <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
17
- <option name="FORCE_QUOTE_STYlE" value="true" />
18
- <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
19
- <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
20
- <option name="SPACES_WITHIN_IMPORTS" value="true" />
21
- </TypeScriptCodeStyleSettings>
22
- <VueCodeStyleSettings>
23
- <option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
24
- <option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
25
- </VueCodeStyleSettings>
26
- <codeStyleSettings language="HTML">
27
- <option name="SOFT_MARGINS" value="120" />
28
- <indentOptions>
29
- <option name="CONTINUATION_INDENT_SIZE" value="4" />
30
- <option name="USE_TAB_CHARACTER" value="true" />
31
- </indentOptions>
32
- </codeStyleSettings>
33
- <codeStyleSettings language="JavaScript">
34
- <option name="SOFT_MARGINS" value="120" />
35
- <indentOptions>
36
- <option name="USE_TAB_CHARACTER" value="true" />
37
- </indentOptions>
38
- </codeStyleSettings>
39
- <codeStyleSettings language="TypeScript">
40
- <option name="SOFT_MARGINS" value="120" />
41
- <indentOptions>
42
- <option name="USE_TAB_CHARACTER" value="true" />
43
- </indentOptions>
44
- </codeStyleSettings>
45
- <codeStyleSettings language="Vue">
46
- <option name="SOFT_MARGINS" value="120" />
47
- <indentOptions>
48
- <option name="INDENT_SIZE" value="4" />
49
- <option name="TAB_SIZE" value="4" />
50
- <option name="USE_TAB_CHARACTER" value="true" />
51
- </indentOptions>
52
- </codeStyleSettings>
53
- </code_scheme>
54
- </component>
@@ -1,5 +0,0 @@
1
- <component name="ProjectCodeStyleConfiguration">
2
- <state>
3
- <option name="USE_PER_PROJECT_SETTINGS" value="true" />
4
- </state>
5
- </component>
@@ -1,6 +0,0 @@
1
- <component name="InspectionProjectProfileManager">
2
- <profile version="1.0">
3
- <option name="myName" value="Project Default" />
4
- <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
5
- </profile>
6
- </component>
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="EslintConfiguration">
4
- <option name="fix-on-save" value="true" />
5
- </component>
6
- </project>
package/.idea/misc.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="GithubDefaultAccount">
4
- <option name="defaultAccountId" value="1f47474a-5c46-4e64-871f-41c4af1c466e" />
5
- </component>
6
- </project>
package/.idea/modules.xml DELETED
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/proje-react-panel.iml" filepath="$PROJECT_DIR$/.idea/proje-react-panel.iml" />
6
- </modules>
7
- </component>
8
- </project>
@@ -1,7 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="PrettierConfiguration">
4
- <option name="myConfigurationMode" value="AUTOMATIC" />
5
- <option name="myRunOnSave" value="true" />
6
- </component>
7
- </project>
@@ -1,12 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <module type="WEB_MODULE" version="4">
3
- <component name="NewModuleRootManager">
4
- <content url="file://$MODULE_DIR$">
5
- <excludeFolder url="file://$MODULE_DIR$/.tmp" />
6
- <excludeFolder url="file://$MODULE_DIR$/temp" />
7
- <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
- </content>
9
- <orderEntry type="inheritedJdk" />
10
- <orderEntry type="sourceFolder" forTests="false" />
11
- </component>
12
- </module>
package/.idea/vcs.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="VcsDirectoryMappings">
4
- <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
- </component>
6
- </project>