-
-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathnotes.txt
342 lines (287 loc) · 24.7 KB
/
notes.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
STRATEGY:
REF-1
cellStyle will only override the borders of inner table structure and not the outside of the table
These have been overriden in the css using !import
The reason for this is because the use of custom border left or border right styles would leave one of the side borders uncovered
and the use of both would result in thick middle borders
For the table borders to be set, use tableStyle
REF-2
contentEditable does not display the caret correctly in shadow dom in FireFox all the time and on initial mouse down on text
element in the Safari browser
the workaround is to set contentEditable every time the data cell is focused
alternatively can refactor cells to be inputs
https://pablo.berganza.dev/blog/shadow-dom-firefox-contenteditable/
REF-3
When customTextProcessing and textValidation both exist, textValidation will validate text after it has been processed and will
determine if it needs to be set back to default and additionally overwrite the cell style with failedStyle if required.
REF-4
Identified an issue where the scrollIntoView does not scroll down far enough to an element for it to be fully visible when
there is a horizontal scroll bar as it is partially covered by it. This appears to only happen when the parent element
(cell dropdown) is displayed and we immediately proceed to scroll to an element.
The workaround for this is to identify that there is a horizontal scroll bar and programmatically scroll further down
REF-5
Trying to limit column types that use cells with text because they carry padding and if one cell with text contains a lot of text
and is resultantly very high, the text elements in the same row which do not contain that much text and are inherently shorter,
would have white space fill up the rest of their cell's height.
If the user would click on that whitespace, the text editor caret would focus on the left of the text which looks bad.
Fortunately this can be fixed by shadowRoot.getSelection, however this is not available to all browsers. Hence, because
the UX cannot be good for all browsers, the table will continue to use plane text within its data cells to have as much
of a better user experience as possible. It is true that in those instances the limitation will still be there when
select/date cells are used, but at least it will only be problematic in those specific cells.
REF-6
Knowing that data cell elements contain a text node and select/label cells contain a text element with a text node inside,
the .innerText grabs the text when used on the cell or the text element for all cases (CAUTION-1 when not to trim it)
REF-7
Because there is no way in firefox to distinguish if element focus was fired by tab key, the cellKeyEventState is used
to hold temporary state to indicate this. Primarily used to decide whether move caret via the setToEndOfText method
REF-8
The reason why there is one cell dropdown per column is because each one contains unique items for it. Hence it
is inefficient to repopulate the dropdown every time it is clicked on a different column cell.
REF-9
Relative positioning does not work on tbody in safari which causes absolute elements to use the screen
REF-10
SVG elements are constructed by parsing a string instead of using an actual file. This is because I did not want to introduce extra
plugins and bundling complexity for adding SVGs into the resultant bundle
Additionally not using the lit element way for inserting svg templates as it has been considered to potentially stop using lit
element and instead go native, hence the current approach of parsing a string will make it easier if moving to that direction
However if opted to stay with lit element - use the following approach: https://lit.dev/docs/api/templates/#svg
REF-11
Because the table is re-rendered when the user changes the parent width - the column width setting process has been optimised.
This has lead to a relatively complex code flow to only trigger the changeWidthsBasedOnColumnInsertRemove method once:
When maxWidth is set and narrow columns are not preserved - we must set the display style to 'block' in order for the table offset
to have the exact width as the temporarily set css width - otherwise the addition of initial columns will increase the table width
by 100 (default width) for each column and stop the canAddMore check from returning false as column widths will be above minimum.
(Executing changeWidthsBasedOnColumnInsertRemove after each column addition would prevent this but we do not want to execute
it every time for optimization)
(This is also why the display is set to 'block' when the client has set a specific width).
Finally, this is why we have the isNewText check in the InsertNewCell class and instead calling changeWidthsBasedOnColumnInsertRemove
once inside the TableElement class.
REF-12
The original architecture of a column sizer (known as the resizer to the user) allowed it to change the column width as it was moving,
however this was deemed too computationaly expensive when the table had many rows as the render speed was very slow, hence it was
rebuilt to only change column width once the user triggers a mouse up event.
Anatomy of a column sizer :
| | | || | | | ||
| | | || | | | ||
| cell divider | sizer | filler || | overlay | | movable ||
| | | || | | | ||
| | | || | | | ||
| | | || | | | ||
A cell divider contains a sizer and 2 other sizer related components that help it perform the functionality that it requires
The sizer element itself contains a filler element which is used to prevent a bug where during the entrance animation - the cell
border tends to bleed out onto the middle of the sizer causing its color to be visible
The sizer element itself does not have any events and all of them are actually performed on the sizer overlay. The reason for this
is because by default the sizer width is short, hence when the user tries to hover over it to have it expand - the area of hover
is actually really small and it may sometimes be hard to hover over it - this is where the overlay comes in and triggers the hover
event before the sizer element is actually reached - thus allowing better experience for the user.
The movable sizer element is the element that follows the cursor whilst the actual sizer element remains static in its position
REF-13
The process of adding adding a new column details object (ColumnDetailsT):
First creating the initial object with just the properties that are required immediately when the header cell is created. Then in
a timeout - asynchronously adding all the other necessary details except the sizer as its creation requires column details. Later
proceeding to create the sizer and adding it to the object, thus completing the creation of the column details object.
REF-14
In Firefox - the delayed insertion of arguments/properties was causing the render lifecycle method to be triggered twice where
the first render method did not have the passed in client properties. This behaviour was fixed by calling
super.connectedCallback() inside a timeout.
REF-15
It has been identified that when contentEditable is changed, chrome can fire a blur event on text. This was problematic for
a situation when column settings were changed after a header text change during multi row data paste, so when prepContentEditable
was being called and blur was fired, the focusedElements object would lose the cell reference. I have edited the code to
work around this use case, however if there are problems in the future, will need to set the chrome contentEditable change
in the prepContentEditable method into a timeout. Additionally all other contentEditable changes will also need to be
placed inside timeouts as their changes need to take affect after that method's timeout execution because when a new
cell is created, all cell elements are immediately assumed to be text type (pre-conversion) and are set with contentEditable
as true via prepContentEditable.
REF-16
In Chrome/Firefox: it has been identified that when there is no width set on the table - the default html does not allow the table
to expand beyond the parent limits (increase the parent width) and instead automatically reduces the column widths. This is
problematic as the widths are no longer what they are set to by the component and the resize functionality no longer works as
intended, hence the table-controlled-width class allows the table to expand the parent element and keep the intended column widths
This behaviour is still permitted when preserveNarrowColumns is set to true.
In Safari: This also serves as a fix for Safari table width bug and the table width no longer needs to be set for this browser.
Reference to this commit to undo anything that has been changed: 2fd86cd84f88d19e8a4a8a95c6b73e4fe181ad13
Snippet from a previous Ref:
(Safari has a bug where column widths for cell elements that contain wrapped text (text that oveflows the width) is increased to what
the width would be without the wrapping. This can be fixed by programmatically setting the table width based on the column widths.
I have raised a stack overflow question to see if this can be fixed automatically via plain css without a manual workaround:
https://stackoverflow.com/questions/73920147/table-element-width-using-column-widths-safari)
REF-17
Conditional dynamic modules that are imported from the parent project had to be abandoned for the following reasons:
await import('module'); would force the module to be present when the component is built. This was ok for this project as we can
use the 'external: ['module']' property in rollup config, however if the component is part of another project with their own
configuration - the module would need to be present even if it was not being used by the parent.
A workaround to use padding in the imported module name (import('module' + paddingStr)) does not work in docusaurus (SSR)
or solidjs.
Using a url to import modules was a potential solution, however this is still new and is not supported by older bundlers
such as webpack older versions - hence if parent project is using them - they would not build.
Therefore the final solution is to import them as external scripts.
REF-18
The add row button is always in the table body and is converted into a root cell and reconverted after it is clicked on.
The reasonings for this are explained here:
When there are no cells in the table, we display a root cell that the user can use to add the first element of the table.
The original idea was to have the column add cell act as the root cell when it is displayed or add new row cell to act when
the column is not displayed etc. However this idea was dismissed due to the code complexity this required as we needed code to
set and unset both of the cells' styles and have them work with the toggle logic for the max numbers. The second idea was to
use a standalone cell button that would independently appear when the table had no data and would hide when data was
added again. This originally worked, but the logic behind displaying it and keeping the other cells hidden grew in complexity.
I then realised that this cell had an onclick event that would add a new row (a new column after no cells cannot be added)
which was pretty much just like the add new row button, so why really have another standalone cell if we can just reuse
the add new row button exclusively. Hence this is why the add row button is always present within the table body and all we do
is convert its style to a root cell when there is no data and set it back to normal or hide it when the data is readded.
REF-19
When the index column is too wide (and narrow columns are not preserved) - the table body will actually be increased over the size
of the table as the initial columns that have been added when the index column had a default width can no longer be removed. To
prevent this we reduce the index column back to its default width by allowing its numbers to wrap. Please note that unwrapping
detection is not supported and once the index column is wrapped it will stay in this form for the rest of the session.
If the table needs to be re-rendered due to resizing, we use the isColumnIndexCellTextWrapped state to check was it wrapped before
and if it was we wrap the index column from the getgo.
When preserveNarrowColumns is set to true and the table exceeds the set width, the column is set to wrap.
The client also has the ability to set the column to wrap all the time by using the wrapIndexCellText flag.
REF-20
onCellUpdate is specifically designed to help maintain a data object in the parent application and IS NOT meant to be used like
an alert notification system to report what has been specifically removed or added. The reason for this is because the update
messages that are sent don't actually describe the particular cells that have been removed/added are and instead describe what
changes need to be made to the outside data object in order for it to be the same as the data object in this component.
Example:
When there are 5 rows and 5 columns, and row number 3 gets removed - the messages that are sent out do not say that row 3 cells
were removed and instead they will say that row 3 cells were updated, row 4 was updated ad row 5 was the one that was removed.
Reasons for implementing onCellUpdate this way:
1. This allows the shadow component to be 100% clear on what happens inside it when a certain part of the table object is
manipulated and how the changes should be made on the parent object to keep it the same as API consumers can take the wrong path
of implementing their array splice functionality incorrectly which would leave their objects out of sync.
2. In state management solutions such as Redux - reducers should be pure functions and not something that would maintain existing
state. Hence incentivising users to implement array splicing and addition logic for state manipulation is something that we want
to avoid.
3. The simplicity, messages that are sent out denote exactly what cells need to be changed and not making the user solve the problem
of what else needs to be changed when one has been deleted/inserted at a particular table index. Hence, they should do exactly what
the messages tell them to and absolutely nothing else.
REF-21
The _defaultColumnsSettings property serves as a one for all default columns settings object that aggregates all the column related top
level properties and processed them for use internally. Previously, this was a property that was defined by the client, however
the fact that the use of the columnsSettings name served as an abstraction for column related properties - it made it more difficult
for the client to intuitively access them without adding much thought, hence they were spread out as top level properties.
The customColumnsSettings property has remained the same.
REF-22
Frame component toggles are exposed as top level boolean properties whereas their state is stored internally within the same object
because they need to be easily accessible to the client without any nesting due to their importance and at the same time it is
much simpler to access them from the same object with required properties internally to reduce code complexity.
REF-23
If a cell with a border style that was set in the cellStyle property is beside a column that has a border set by customColumnsSettings
property (cellStyle), their borders will be displayed side by side which does not look appealing. Hence, we unset (0px) the border of
the cell without the customColumnsSettings border style to make the one with the custom settings border dominant.
If there are two columns with customColumnsSettings border styles, the right column will be dominant.
We also do this for frame element borders.
REF-24
When the table width/maxWidth is set and there are columns with set widths that exceed the table width, those widths can cause the table
to exceed the set width. The reason for this is because it is relatively complex to identify when the table width will be exceeded and
what to accordingly resize, prevent the addition of or change of to keep the width within the set bound. This can be furtehr worked on
in the future, but for now API users that do have this configuration should be careful what column widths are set and to preferably
disable the use of duplicate headers.
Currently the add column button disappears when the table size is exceeded, but this comes at a cost of not allowing any further columns
to be added (incl. render) even when preserveNarrowColumns is true.
REF-25
Given that the add new row button is not always visible and there is no css selector that can identify the last visible row, we manually
mark the last visible row with an id. This is done to not display bottom border if last row cells contain one in their style settings
and to inherit bottom table border radius.
REF-26
To insert a header row when there is an existing one - we create a new row below it and move the data of the header row to it.
The reason for this approach is not to have to create a new row with <td> elements and sizers.
REF-27
To remove a header row when there is data below the header, we swap the header row with the below data row and then remove the data row.
The reason for this approach is not to have to create a new row with <td> elements and sizers.
REF-28
Default column type dropdown items are created after the DEFAULT_TYPES config has been initialised because the creation logic is
not immediately available on startup (cannot use it in the DEFAULT_TYPES variable) and additionally the items are not needed on
the initial table render.
REF-29
Safari does not support the focus of a checkbox, therefore we do not focus it's cell for that browser when tabbing
REF-30
When new buttons are being created in pagination during a shift and the mouse is over a new button - the mouse enter event
does not get triggered, hence we need to apply it programmatically. Because it is set in PaginationButtonElement.setActive
method, the shift can be triggered by side buttons and row events, hence we need a way to identify if it was activated
via number click events. Passing down an indicator via parameters would have complicated the code too much, hence I opted
for an easier solution to hold temporary state of when a number button has been clicked.
REF-31
When a pagination button style is programmatically set to hover, sometimes the pagination button container shifts upwards/downwards
when a new row is inserted or older is removed, hence the mouse is no longer on a button and the style should not be applied.
REF-32
When pagination is enabled along with striped rows and add new row cell displayed - the add new row element can sometimes have
the same style as the row above due to the fact that its style was set based on the position of all rows and not where it is
in the current page (due to rows being hidden). To fix this we set the add new row element to have a consistent style. Because
this issue also affects the header element when there is an odd number of visible rows allowed, we change the allowed row number
to be even to make sure that no sibling rows are same.
REF-33
Cell event setters have been optimized to only be called when a column type is set or when a new cell is being inserted - instead of
setting events on the conversion of each cell. The reason for that is after a cell is created, the events are set again in
CellEventsReset method, hence removing a redundant call.
REF-34
To make sure that label colors vary, _globalItemColors property contains a reference to a default array of predefined colors which will
be used before random ones are going to be generated.
REF-35
The reason why we do not store column widths in the column details state is because there are multiple areas that affect them and
they overwrite the elements by traversing the table or having a direct reference to the cell element instead, hence the logic
would need a hard rewrite to track each and every column that has its width changed.
REF-36
Columns have two types of widths:
static which cannot be resized and will cause subject column widths to always be the same
initial is a starter width which can be resized and is overwritten if table has a set width
REF-37
When the header is sticky and the overflow is not coming from the table, but the parent element - upon scrolling the table - header row
looks weird when the table has a border-top and it does not. To fix this we have moved border-top from the table to the table body
to not have it displayed and use inheritance via selectors to set it on the header cells instead.
REF-38
Outer container column elements use 'width: 0px' property to not force component elements of the other columns to move out of the table
bounds. This has resulted in a new problem where the 'width' property of the components would no longer work and 'min-width' would be
the only one that can set the widths. This is not intuitive for the client when they want to change the width of the elements, hence
we added nested elements inside those columns to allow the component widths to be set via the 'width' property. The inner column
element has been nested in another nested element for this to work in Safari.
If the above will no longer meet the requirements - undo and force user to use min-width to set the widths.
REF-39
The website homepage initial contents are faded in because the table component is imported dynamically and then rendered, which
results in an empty space for the first couple of milliseconds that don't look too great. Hence the fade-in animation is used
to overcome this issue.
REF-40
Pagination action buttons icon color is set via stroke, this is because filter would simply fill the whole button color. Using
icons instead of text in order to keep them consistent for all operating systems.
REF-41
Table font family is set programmatically to allow the outer containers to inherit it so when the user set a custom style, they would
automatically pick it up.
REF-42
Row filtering works by iterating all filter inputs sequentially and eleminitating (hiding) any rows that don't match the filter text.
REF-43
_isPopulatingTable is primarily used to trigger ADD cell events when table is prepopulated as otherwise it would use UPDATE events.
REF-44
Fix for a bug where upon clicking on option button inside a cell dropdown - the cell was no longer focused and clicking on another
cell's empty area whilst the dropdown is still open would not not close it. Usually this was fine as the TableEvents.closeCellDropdown
is called before the focus is switched to another cell, but it was found that when the aforementioned steps are expedited and the user
clicks on another cell type that has empty cell space, the focus would be switched before TableEvents.closeCellDropdown is called.
This is now fixed by checking if at._focusedElements.cellDropdown is open on a cell with text mouseDownCell method.
Test this: click on an option button inside label dropdown and then click on the empty space between text and calendar in date cell.
CAUTION-1
The returned string from textElement.innerText.trim() should not be used to set text on another cell as innerText/textContent property
does not just return the cell text but additionally the new line chars (\n) which represent <br> elements within the cell and .trim()
removes them, hence using this to set text on another cell will set it without the important <br> elements which will make it difficult
to programmatically set the pointer position on that cell
The reason why trim() is used is because the new line characters make it difficult to compare cell text to an actual string or use it
for other programmatic needs.
CAUTION-2
When resizing the table, take note of some async code that was waiting to be executed before the resize was triggered.
CAUTION-3
Table dimensions have exclusive optional interfaces that are exposed to the client, however they may not be respected, hence the logic
needs to be cautious when handling the passed in object
CAUTION-4
Do not set state/property variables after render has occurred as it will trigger full component re-render.
Be careful when assigning element references and instead do them in the connectedCallback method.
Exception for at.isRendering.
Accepted behaviour
When a table column is squished - the increased height can create a scrollbar in the parent - this will trigger a re-render in the table
and cause the column widths to be reset (or be even smaller if using maxWidth as the new re-render is using the new table width that may
not breach the maxWdith threshold). Because we do not record column widths in-state, there is currently no way to track back what the
original widths were and they will be forced to reset.
TO-DO can potentially keep track of the column widths and reset them correctly in the future
Updating package version Checklist:
Run npm run build:bundle
Update Installation
Update CDN references - in doc Install and VanillaJS/Files VanillaJS Live Example sections.
Update React wrapper