@urbanos/react-discovery-ui 2.1.47 → 2.1.49

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.
@@ -34,7 +34,8 @@ var DataTable = function DataTable(_ref) {
34
34
  var data = _ref.data,
35
35
  columns = _ref.columns,
36
36
  page = _ref.page,
37
- onNextPageClicked = _ref.onNextPageClicked;
37
+ onNextPageClicked = _ref.onNextPageClicked,
38
+ datasetName = _ref.datasetName;
38
39
  var _useState = (0, _react.useState)({
39
40
  pageIndex: page || 0,
40
41
  pageSize: 50
@@ -42,6 +43,20 @@ var DataTable = function DataTable(_ref) {
42
43
  _useState2 = _slicedToArray(_useState, 2),
43
44
  pagination = _useState2[0],
44
45
  setPagination = _useState2[1];
46
+ var _useState3 = (0, _react.useState)({
47
+ row: 0,
48
+ col: 0
49
+ }),
50
+ _useState4 = _slicedToArray(_useState3, 2),
51
+ focusedCell = _useState4[0],
52
+ setFocusedCell = _useState4[1];
53
+ var focusedCellRef = (0, _react.useRef)({
54
+ row: 0,
55
+ col: 0
56
+ });
57
+ var totalRowsRef = (0, _react.useRef)(1);
58
+ var totalColsRef = (0, _react.useRef)(columns.length);
59
+ var tableRef = (0, _react.useRef)(null);
45
60
  (0, _react.useEffect)(function () {
46
61
  setPagination(function (prev) {
47
62
  return _objectSpread(_objectSpread({}, prev), {}, {
@@ -65,37 +80,124 @@ var DataTable = function DataTable(_ref) {
65
80
  getCoreRowModel: (0, _reactTable.getCoreRowModel)(),
66
81
  getPaginationRowModel: (0, _reactTable.getPaginationRowModel)()
67
82
  });
83
+
84
+ // Keep refs current on every render so the native keydown handler always sees fresh values
85
+ totalRowsRef.current = table.getRowModel().rows.length + 1; // row 0 = header
86
+ totalColsRef.current = columns.length;
87
+
88
+ // Native keydown listener — fires before React's document-level delegation,
89
+ // so e.preventDefault() reliably cancels browser arrow-key scroll.
90
+ (0, _react.useEffect)(function () {
91
+ var tableEl = tableRef.current;
92
+ if (!tableEl) return;
93
+ var handleKeyDown = function handleKeyDown(e) {
94
+ var _focusedCellRef$curre = focusedCellRef.current,
95
+ row = _focusedCellRef$curre.row,
96
+ col = _focusedCellRef$curre.col;
97
+ var moves = {
98
+ ArrowUp: [row - 1, col],
99
+ ArrowDown: [row + 1, col],
100
+ ArrowLeft: [row, col - 1],
101
+ ArrowRight: [row, col + 1]
102
+ };
103
+ if (!moves[e.key]) return;
104
+ e.preventDefault();
105
+ var r = Math.max(0, Math.min(totalRowsRef.current - 1, moves[e.key][0]));
106
+ var c = Math.max(0, Math.min(totalColsRef.current - 1, moves[e.key][1]));
107
+ focusedCellRef.current = {
108
+ row: r,
109
+ col: c
110
+ };
111
+ setFocusedCell({
112
+ row: r,
113
+ col: c
114
+ });
115
+ var cell = tableEl.querySelector("[data-row=\"".concat(r, "\"][data-col=\"").concat(c, "\"]"));
116
+ if (cell) {
117
+ cell.focus({
118
+ preventScroll: true
119
+ });
120
+ cell.scrollIntoView({
121
+ block: 'nearest',
122
+ inline: 'nearest'
123
+ });
124
+ }
125
+ };
126
+ tableEl.addEventListener('keydown', handleKeyDown);
127
+ return function () {
128
+ return tableEl.removeEventListener('keydown', handleKeyDown);
129
+ };
130
+ }, []); // empty deps — all values accessed via refs
131
+
132
+ var tabIndex = function tabIndex(row, col) {
133
+ return focusedCell.row === row && focusedCell.col === col ? 0 : -1;
134
+ };
135
+ var onCellFocus = function onCellFocus(row, col) {
136
+ focusedCellRef.current = {
137
+ row: row,
138
+ col: col
139
+ };
140
+ setFocusedCell({
141
+ row: row,
142
+ col: col
143
+ });
144
+ };
68
145
  return /*#__PURE__*/_react.default.createElement("div", {
69
146
  style: {
70
147
  height: '400px',
71
148
  overflow: 'auto'
72
149
  }
73
150
  }, /*#__PURE__*/_react.default.createElement("table", {
151
+ ref: tableRef,
74
152
  style: {
75
153
  minWidth: "".concat(columns.length * 120, "px")
76
154
  }
77
- }, /*#__PURE__*/_react.default.createElement("thead", null, table.getHeaderGroups().map(function (headerGroup) {
155
+ }, /*#__PURE__*/_react.default.createElement("caption", null, datasetName ? "".concat(datasetName, " Dataset Preview") : 'Dataset Preview'), /*#__PURE__*/_react.default.createElement("thead", null, table.getHeaderGroups().map(function (headerGroup) {
78
156
  return /*#__PURE__*/_react.default.createElement("tr", {
79
157
  key: headerGroup.id
80
- }, headerGroup.headers.map(function (header) {
158
+ }, headerGroup.headers.map(function (header, colIndex) {
81
159
  var _header$column$column;
82
160
  return /*#__PURE__*/_react.default.createElement("th", {
83
161
  key: header.id,
84
162
  scope: "col",
163
+ tabIndex: tabIndex(0, colIndex),
164
+ "data-row": 0,
165
+ "data-col": colIndex,
166
+ onFocus: function onFocus() {
167
+ return onCellFocus(0, colIndex);
168
+ },
85
169
  className: ((_header$column$column = header.column.columnDef.meta) === null || _header$column$column === void 0 ? void 0 : _header$column$column.headerClassName) || 'table-header'
86
170
  }, header.isPlaceholder ? null : (0, _reactTable.flexRender)(header.column.columnDef.header, header.getContext()));
87
171
  }));
88
172
  })), /*#__PURE__*/_react.default.createElement("tbody", null, table.getRowModel().rows.length === 0 ? /*#__PURE__*/_react.default.createElement("tr", null, /*#__PURE__*/_react.default.createElement("td", {
89
173
  colSpan: columns.length,
90
174
  className: "no-data-message"
91
- }, "No rows found")) : table.getRowModel().rows.map(function (row, i) {
175
+ }, "No rows found")) : table.getRowModel().rows.map(function (row, rowIndex) {
176
+ var tableRow = rowIndex + 1;
92
177
  return /*#__PURE__*/_react.default.createElement("tr", {
93
178
  key: row.id,
94
- className: i % 2 === 0 ? 'striped-row' : ''
95
- }, row.getVisibleCells().map(function (cell) {
96
- return /*#__PURE__*/_react.default.createElement("td", {
97
- key: cell.id
98
- }, (0, _reactTable.flexRender)(cell.column.columnDef.cell, cell.getContext()));
179
+ className: rowIndex % 2 === 0 ? 'striped-row' : ''
180
+ }, row.getVisibleCells().map(function (cell, colIndex) {
181
+ var content = (0, _reactTable.flexRender)(cell.column.columnDef.cell, cell.getContext());
182
+ return colIndex === 0 ? /*#__PURE__*/_react.default.createElement("th", {
183
+ key: cell.id,
184
+ scope: "row",
185
+ tabIndex: tabIndex(tableRow, colIndex),
186
+ "data-row": tableRow,
187
+ "data-col": colIndex,
188
+ onFocus: function onFocus() {
189
+ return onCellFocus(tableRow, colIndex);
190
+ },
191
+ className: "row-header"
192
+ }, content) : /*#__PURE__*/_react.default.createElement("td", {
193
+ key: cell.id,
194
+ tabIndex: tabIndex(tableRow, colIndex),
195
+ "data-row": tableRow,
196
+ "data-col": colIndex,
197
+ onFocus: function onFocus() {
198
+ return onCellFocus(tableRow, colIndex);
199
+ }
200
+ }, content);
99
201
  }));
100
202
  }))), /*#__PURE__*/_react.default.createElement("div", {
101
203
  className: "pagination"
@@ -190,10 +292,10 @@ var cleanseData = function cleanseData(data) {
190
292
  });
191
293
  };
192
294
  var _default = exports.default = function _default(props) {
193
- var _useState3 = (0, _react.useState)(0),
194
- _useState4 = _slicedToArray(_useState3, 2),
195
- index = _useState4[0],
196
- setIndex = _useState4[1];
295
+ var _useState5 = (0, _react.useState)(0),
296
+ _useState6 = _slicedToArray(_useState5, 2),
297
+ index = _useState6[0],
298
+ setIndex = _useState6[1];
197
299
  var isGeojson = props.format === 'geojson';
198
300
  var cleanData = isGeojson ? undefined : props.data ? cleanseData(props.data) : props.data;
199
301
  var columns = (0, _react.useMemo)(function () {
@@ -231,7 +333,8 @@ var _default = exports.default = function _default(props) {
231
333
  data: cleanData,
232
334
  columns: columns,
233
335
  page: props.page,
234
- onNextPageClicked: props.onNextPageClicked
336
+ onNextPageClicked: props.onNextPageClicked,
337
+ datasetName: props.datasetName
235
338
  }))), /*#__PURE__*/_react.default.createElement(_reactTabs.TabPanel, null, /*#__PURE__*/_react.default.createElement("div", {
236
339
  id: "data-view-raw"
237
340
  }, props.loading ? /*#__PURE__*/_react.default.createElement(_loadingElement.default, {
@@ -48,6 +48,11 @@
48
48
  text-align: left;
49
49
  font-weight: 800;
50
50
  }
51
+
52
+ tbody th.row-header {
53
+ font-weight: normal;
54
+ text-align: left;
55
+ }
51
56
  }
52
57
 
53
58
  #data-view-raw {
@@ -33,14 +33,16 @@ describe('data view', () => {
33
33
  })
34
34
 
35
35
  test('should render table rows including data for column names with dots', () => {
36
- const table_elements = subject.find('#data-view-table tbody td')
37
- const expected = [
38
- 'Joe', 'Smith', 'true', 'Joe Smith',
39
- 'Jane', 'Doe', 'false', 'Jane Doe'
40
- ]
41
- const actual = table_elements.map((x) => x.text())
36
+ const rowHeaders = subject.find('#data-view-table tbody .row-header').map(x => x.text())
37
+ const dataCells = subject.find('#data-view-table tbody td').map(x => x.text())
38
+ const actual = [...rowHeaders, ...dataCells]
42
39
 
43
- expect(actual).toContain(...expected)
40
+ expect(actual).toContain('Joe')
41
+ expect(actual).toContain('Smith')
42
+ expect(actual).toContain('true')
43
+ expect(actual).toContain('Jane')
44
+ expect(actual).toContain('Doe')
45
+ expect(actual).toContain('false')
44
46
  })
45
47
 
46
48
  test('should render two tabs', () => {
@@ -114,12 +116,10 @@ describe('data view', () => {
114
116
 
115
117
  const subject = mount(<DataView data={queryData} columns={columns} />)
116
118
 
117
- const cells = subject.find('#data-view-table tbody td')
118
- const actualValues = cells.map(c => c.text())
119
- expect(actualValues).toEqual([
120
- '{"value":1}', 'true', '[1]', '', '',
121
- '{"value":2}', 'false', '[2,3]', '', ''
122
- ])
119
+ const rowHeaders = subject.find('#data-view-table tbody .row-header').map(c => c.text())
120
+ const dataCells = subject.find('#data-view-table tbody td').map(c => c.text())
121
+ expect(rowHeaders).toEqual(['{"value":1}', '{"value":2}'])
122
+ expect(dataCells).toEqual(['true', '[1]', '', '', 'false', '[2,3]', '', ''])
123
123
  expect(subject.prop('data')).toEqual(queryData)
124
124
  })
125
125
 
@@ -139,11 +139,13 @@ describe('data view', () => {
139
139
  expect(headers).toContain('first.name')
140
140
  expect(headers).toContain('last.name')
141
141
 
142
- const cells = subject.find('#data-view-table tbody td').map(c => c.text())
143
- expect(cells).toContain('Mark')
144
- expect(cells).toContain('Johnson')
145
- expect(cells).toContain('George')
146
- expect(cells).toContain('Lakoff')
142
+ const rowHeaders = subject.find('#data-view-table tbody .row-header').map(c => c.text())
143
+ const dataCells = subject.find('#data-view-table tbody td').map(c => c.text())
144
+ const allCells = [...rowHeaders, ...dataCells]
145
+ expect(allCells).toContain('Mark')
146
+ expect(allCells).toContain('Johnson')
147
+ expect(allCells).toContain('George')
148
+ expect(allCells).toContain('Lakoff')
147
149
  })
148
150
  })
149
151
  })
@@ -11,9 +11,11 @@ var _selectors = require("../../store/selectors");
11
11
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
12
  var mapStateToProps = function mapStateToProps(state, _ref) {
13
13
  var format = _ref.format;
14
+ var dataset = (0, _selectors.getDataSet)(state);
14
15
  return {
15
16
  datasetPreview: (0, _selectors.getDataSetPreview)(state, format),
16
- previewLoading: state.presentation.previewLoading
17
+ previewLoading: state.presentation.previewLoading,
18
+ datasetName: dataset && dataset.title
17
19
  };
18
20
  };
19
21
  var mapDispatchToProps = function mapDispatchToProps(dispatch) {
@@ -50,7 +50,8 @@ var _default = exports.default = /*#__PURE__*/function (_Component) {
50
50
  data: data,
51
51
  columns: columns,
52
52
  loading: this.props.previewLoading,
53
- format: this.props.format
53
+ format: this.props.format,
54
+ datasetName: this.props.datasetName
54
55
  })));
55
56
  }
56
57
  }]);
@@ -12,8 +12,10 @@ var _visualizationSelectors = require("../../store/visualization-selectors");
12
12
  var _selectors = require("../../store/selectors");
13
13
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
14
  var mapStateToProps = function mapStateToProps(state) {
15
+ var dataset = (0, _selectors.getDataSet)(state);
15
16
  return {
16
17
  dataSources: (0, _querySelectors.getVisualizationDataSources)(state),
18
+ datasetName: dataset && dataset.title,
17
19
  recommendations: (0, _selectors.getDatasetRecommendations)(state),
18
20
  usedDatasets: (0, _visualizationSelectors.visualizationUsedDatasets)(state),
19
21
  datasetReferences: (0, _selectors.getDataSetReferences)(state),
@@ -21,6 +21,7 @@ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" !=
21
21
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
22
22
  var QueryView = function QueryView(props) {
23
23
  var dataSources = props.dataSources,
24
+ datasetName = props.datasetName,
24
25
  recommendations = props.recommendations,
25
26
  usedDatasets = props.usedDatasets,
26
27
  datasetReferences = props.datasetReferences,
@@ -74,7 +75,8 @@ var QueryView = function QueryView(props) {
74
75
  data: queryData,
75
76
  page: page,
76
77
  columns: Object.keys(dataSources),
77
- onNextPageClicked: onNextPageClicked
78
+ onNextPageClicked: onNextPageClicked,
79
+ datasetName: datasetName
78
80
  })));
79
81
  };
80
82
  var _default = exports.default = QueryView;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@urbanos/react-discovery-ui",
3
- "version": "2.1.47",
3
+ "version": "2.1.49",
4
4
  "description": "React component for dataset discovery UI",
5
5
  "main": "./lib/ReactDiscoveryUI.js",
6
6
  "repository": {