diff --git a/data/csv.js b/data/csv.json
similarity index 99%
rename from data/csv.js
rename to data/csv.json
index f24ed1a..4a4fd27 100644
--- a/data/csv.js
+++ b/data/csv.json
@@ -1,4 +1,4 @@
-var CSV = [
+[
["fspr","plnt","promo","terms","bill_cust_descr","ship_cust_descr","dsm","quota_rep_descr","director","billto_group","shipto_group","chan","chansub","chan_retail","part","part_descr","part_group","branding","majg_descr","ming_descr","majs_descr","mins_descr","segm","substance","fs_line","r_currency","r_rate","c_currency","c_rate","units","value_loc","value_usd","cost_loc","cost_usd","calc_status","flag","order_date","order_month","order_season","request_date","request_month","request_season","ship_date","ship_month","ship_season","version","iter","logid","tag","comment","module"],
["2102","152","NONE","1A","BWIC0001 - BWI COMPANIES INC","BLOO0017 - BLOOMING COLOR","10032","BRYAN HILL","Baggetta","BWI","BLOOMING COLOR","DRP","DRP","DRP","HZP3E100G18D050","HZP3E100G18D050 - E-10 3 STRAND HGR BLACK","HZP3E100","","110 - INJECTION","000 - NON BRANDED","108 - ACCESSORIES","A06 - HANGERS","Greenhouse","Plastic","41010","US","1.0000000000","US","1.0000000000","25600.0000000000","2099.2000000000","2099.20","1697.2800000000","1697.28","CLOSED","SHIPMENT","2020-06-08","01 - Jun","2021","2020-06-15","01 - Jun","2021","2020-07-09","02 - Jul","2021","15mo","actuals","1","Initial Build","don't undo","build_pool"],
["2211","152","NONE","1A","PAST0002 - PASTANCH LLC","PAST0002 - PASTANCH LLC","13028","RICHARD MEULE","Soltis","PASTANCH LLC","PASTANCH LLC","WHS","WHS","WHS","AMK12000G18D050","AMK12000G18D050 - 3G 1200 REG BM POT BLACK","AMK12000","","210 - BLOW MOLD","000 - NON BRANDED","104 - ROUND POTS AND TRAYS","A19 - NURSERY POTS >= 1 GAL","Nursery","Plastic","41010","US","1.0000000000","US","1.0000000000","24000.0000000000","9144.0000000000","9144.00","4936.0800000000","4936.08","OPEN","REMAINDER","2022-03-26","10 - Mar","2022","2022-04-28","11 - Apr","2022","2022-04-28","11 - Apr","2022","b22","copy","1","Initial Build","don't undo","build_pool"],
diff --git a/index.html b/index.html
index 5ca13ce..e49dfdb 100755
--- a/index.html
+++ b/index.html
@@ -2,490 +2,21 @@
-
-
-
-
+
-
-
-
-
+
diff --git a/libraries/n.js b/libraries/n.js
deleted file mode 100644
index 7813650..0000000
--- a/libraries/n.js
+++ /dev/null
@@ -1,172 +0,0 @@
-var N =
-{
-ID:{
- Walk:0,
- Instance:0
-},
-Create(inMeta)
-{
- return {
- ID:{
- Walk:0,
- Instance:N.ID.Instance++
- },
- Meta:inMeta||{},
- Link:{}
- };
-},
-Connect(inNodeMajor, inNodeMinor, inKey, inUnique)
-{
- if(inUnique) // bail if the nodes are already connected
- {
- let check = N.Step(inNodeMajor, inKey, true);
- if(check)
- {
- if(check.indexOf(inNodeMinor) !== -1)
- {
- return;
- }
- }
- }
- N.Step(inNodeMajor, inKey, true, true).push(inNodeMinor);
- N.Step(inNodeMinor, inKey, false, true).push(inNodeMajor);
-},
-Disconnect(inNodeMajor, inNodeMinor, inKey)
-{
- let remove = (inArray, inMatch) => inArray.findIndex( (inMember, inIndex, inArray) => (inMember === inMatch) ? inArray.splice(inIndex, 1) : false );
-
- // if no specific child was passed
- if(inNodeMinor === null)
- {
- // get all the children
- let check = N.Step(inNodeMajor, inKey);
- if(!check){ return; }
-
- // go down to each child ...
- check.forEach( inNodeMinor =>
- {
- let connections = inNodeMinor.Link[inKey];
- remove( connections.Get, inNodeMajor); // ... and remove any reference to the parent
-
- // if after the remove operation, this child has no connections on inKey, scrub the key
- if(!connections.Set.length && !connections.Get.length)
- {
- delete inNodeMinor.Link[inKey];
- }
- });
-
- // we just wiped out all outgoing connections to the parent, if incoming connections are empty too we can purge the key there as well
- if(inNodeMajor.Link[inKey].Get.length == 0)
- {
- delete inNodeMajor.Link[inKey];
- }
- return;
- }
-
- // if no specific parent was passed
- if(inNodeMajor === null)
- {
- // get all the parents
- let check = N.Step(inNodeMinor, inKey, false);
- if(!check){ return; }
-
- // go up to each parent ...
- check.forEach( inNodeMajor =>
- {
- let connections = inNodeMajor.Link[inKey];
- remove( connections.Set, inNodeMinor); // ... and remove any reference to the child
-
- // if after the remove operation, this parent has no connections on inKey, scrub the key
- if( !connections.Set.length && !connections.Get.length )
- {
- delete inNodeMajor.Link[inKey];
- }
- });
-
- // we just wiped out all incoming connections to the child, if outgoing connections are empty too we can purge the key there as well
- if(inNodeMinor.Link[inKey].Set.length == 0)
- {
- delete inNodeMinor.Link[inKey];
- }
- return;
- }
-
- // if a specific parent and child were passed
- if(inNodeMajor.Link[inKey].Set.length == 1)
- {
- delete inNodeMajor.Link[inKey];
- }
- else
- {
- remove(inNodeMajor.Link[inKey].Set, inNodeMinor);
- }
- if(inNodeMinor.Link[inKey].Get.length == 1)
- {
- delete inNodeMinor.Link[inKey];
- }
- else
- {
- remove(inNodeMinor.Link[inKey].Get, inNodeMajor);
- }
-
-},
-Step(inNode, inKey, inForward, inForceCreate)
-{
- let connectionGroup = inNode.Link[inKey];
- if(!connectionGroup)
- {
- if(inForceCreate === true)
- {
- inNode.Link[inKey] = connectionGroup = {Get:[], Set:[]};
- }
- else
- {
- return false;
- }
- }
- return (inForward === undefined || inForward === true) ? connectionGroup.Set : connectionGroup.Get;
-
-},
-Walk(inIterator, inNode, inKey, inForward, inTerminal)
-{
- let array = N.Step(inNode, inKey, inForward);
-
- if(!array.length && inTerminal)
- {
- return inTerminal(inNode);
- }
-
- for(let i=0; i=this._config.preview;if(o)f.postMessage({results:n,workerId:b.WORKER_ID,finished:a});else if(q(this._config.chunk)&&!t){if(this._config.chunk(n,this._handle),this._handle.paused()||this._handle.aborted())return void(this._halted=!0);n=void 0,this._completeResults=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(n.data),this._completeResults.errors=this._completeResults.errors.concat(n.errors),this._completeResults.meta=n.meta),this._completed||!a||!q(this._config.complete)||n&&n.meta.aborted||(this._config.complete(this._completeResults,this._input),this._completed=!0),a||n&&n.meta.paused||this._nextChunk(),n}this._halted=!0},this._sendError=function(e){q(this._config.error)?this._config.error(e):o&&this._config.error&&f.postMessage({workerId:b.WORKER_ID,error:e,finished:!1})}}function l(e){var i;(e=e||{}).chunkSize||(e.chunkSize=b.RemoteChunkSize),u.call(this,e),this._nextChunk=n?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(e){this._input=e,this._nextChunk()},this._readChunk=function(){if(this._finished)this._chunkLoaded();else{if(i=new XMLHttpRequest,this._config.withCredentials&&(i.withCredentials=this._config.withCredentials),n||(i.onload=y(this._chunkLoaded,this),i.onerror=y(this._chunkError,this)),i.open("GET",this._input,!n),this._config.downloadRequestHeaders){var e=this._config.downloadRequestHeaders;for(var t in e)i.setRequestHeader(t,e[t])}if(this._config.chunkSize){var r=this._start+this._config.chunkSize-1;i.setRequestHeader("Range","bytes="+this._start+"-"+r)}try{i.send()}catch(e){this._chunkError(e.message)}n&&0===i.status?this._chunkError():this._start+=this._config.chunkSize}},this._chunkLoaded=function(){4===i.readyState&&(i.status<200||400<=i.status?this._chunkError():(this._finished=!this._config.chunkSize||this._start>function(e){var t=e.getResponseHeader("Content-Range");if(null===t)return-1;return parseInt(t.substr(t.lastIndexOf("/")+1))}(i),this.parseChunk(i.responseText)))},this._chunkError=function(e){var t=i.statusText||e;this._sendError(new Error(t))}}function c(e){var i,n;(e=e||{}).chunkSize||(e.chunkSize=b.LocalChunkSize),u.call(this,e);var s="undefined"!=typeof FileReader;this.stream=function(e){this._input=e,n=e.slice||e.webkitSlice||e.mozSlice,s?((i=new FileReader).onload=y(this._chunkLoaded,this),i.onerror=y(this._chunkError,this)):i=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(e.target.result)},this._chunkError=function(){this._sendError(i.error)}}function p(e){var r;u.call(this,e=e||{}),this.stream=function(e){return r=e,this._nextChunk()},this._nextChunk=function(){if(!this._finished){var e=this._config.chunkSize,t=e?r.substr(0,e):r;return r=e?r.substr(e):"",this._finished=!r,this.parseChunk(t)}}}function m(e){u.call(this,e=e||{});var t=[],r=!0,i=!1;this.pause=function(){u.prototype.pause.apply(this,arguments),this._input.pause()},this.resume=function(){u.prototype.resume.apply(this,arguments),this._input.resume()},this.stream=function(e){this._input=e,this._input.on("data",this._streamData),this._input.on("end",this._streamEnd),this._input.on("error",this._streamError)},this._checkIsFinished=function(){i&&1===t.length&&(this._finished=!0)},this._nextChunk=function(){this._checkIsFinished(),t.length?this.parseChunk(t.shift()):r=!0},this._streamData=y(function(e){try{t.push("string"==typeof e?e:e.toString(this._config.encoding)),r&&(r=!1,this._checkIsFinished(),this.parseChunk(t.shift()))}catch(e){this._streamError(e)}},this),this._streamError=y(function(e){this._streamCleanUp(),this._sendError(e)},this),this._streamEnd=y(function(){this._streamCleanUp(),i=!0,this._streamData("")},this),this._streamCleanUp=y(function(){this._input.removeListener("data",this._streamData),this._input.removeListener("end",this._streamEnd),this._input.removeListener("error",this._streamError)},this)}function r(g){var a,o,h,i=Math.pow(2,53),n=-i,s=/^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i,u=/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/,t=this,r=0,f=0,d=!1,e=!1,l=[],c={data:[],errors:[],meta:{}};if(q(g.step)){var p=g.step;g.step=function(e){if(c=e,_())m();else{if(m(),0===c.data.length)return;r+=e.data.length,g.preview&&r>g.preview?o.abort():p(c,t)}}}function v(e){return"greedy"===g.skipEmptyLines?""===e.join("").trim():1===e.length&&0===e[0].length}function m(){if(c&&h&&(k("Delimiter","UndetectableDelimiter","Unable to auto-detect delimiting character; defaulted to '"+b.DefaultDelimiter+"'"),h=!1),g.skipEmptyLines)for(var e=0;e=l.length?"__parsed_extra":l[r]),g.transform&&(s=g.transform(s,n)),s=y(n,s),"__parsed_extra"===n?(i[n]=i[n]||[],i[n].push(s)):i[n]=s}return g.header&&(r>l.length?k("FieldMismatch","TooManyFields","Too many fields: expected "+l.length+" fields but parsed "+r,f+t):r=i.length/2?"\r\n":"\r"}(e,i)),h=!1,g.delimiter)q(g.delimiter)&&(g.delimiter=g.delimiter(e),c.meta.delimiter=g.delimiter);else{var n=function(e,t,r,i,n){var s,a,o,h;n=n||[",","\t","|",";",b.RECORD_SEP,b.UNIT_SEP];for(var u=0;u=L)return R(!0)}else for(g=M,M++;;){if(-1===(g=a.indexOf(O,g+1)))return t||u.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:h.length,index:M}),w();if(g===i-1)return w(a.substring(M,g).replace(_,O));if(O!==z||a[g+1]!==z){if(O===z||0===g||a[g-1]!==z){var y=E(-1===m?p:Math.min(p,m));if(a[g+1+y]===D){f.push(a.substring(M,g).replace(_,O)),a[M=g+1+y+e]!==O&&(g=a.indexOf(O,M)),p=a.indexOf(D,M),m=a.indexOf(I,M);break}var k=E(m);if(a.substr(g+1+k,n)===I){if(f.push(a.substring(M,g).replace(_,O)),C(g+1+k+n),p=a.indexOf(D,M),g=a.indexOf(O,M),o&&(S(),j))return R();if(L&&h.length>=L)return R(!0);break}u.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:h.length,index:M}),g++}}else g++}return w();function b(e){h.push(e),d=M}function E(e){var t=0;if(-1!==e){var r=a.substring(g+1,e);r&&""===r.trim()&&(t=r.length)}return t}function w(e){return t||(void 0===e&&(e=a.substr(M)),f.push(e),M=i,b(f),o&&S()),R()}function C(e){M=e,b(f),f=[],m=a.indexOf(I,M)}function R(e,t){return{data:t||!1?h[0]:h,errors:u,meta:{delimiter:D,linebreak:I,aborted:j,truncated:!!e,cursor:d+(r||0)}}}function S(){A(R(void 0,!0)),h=[],u=[]}function x(e,t,r){var i={nextDelim:void 0,quoteSearch:void 0},n=a.indexOf(O,t+1);if(t
+{
+ let styles = css`
+ position:realtive;
+ box-sizing: border-box;
+ padding: 10px;
+ color:black;
+ font-family:sans-serif;
+
+ .Title
+ {
+ font-size:24px;
+ font-weight:100;
+ }
+
+ .Section
+ {
+ padding:10px 0 10px 0;
+
+ .Heading
+ {
+ display:inline-block;
+ color:#666;
+ font-family:sans-serif;
+ font-size:12px;
+ font-weight:900;
+ text-transform:uppercase;
+ }
+ .Group
+ {
+ display:inline-block;
+ padding:5px;
+ border-radius:5px;
+ margin:3px;
+ background:rgba(0, 0, 0, 0.3)
+ }
+ }
+ `;
+
+
+ let pivotColumns = N.Step(Pivot.Schema, "label")||[];
+ let pivotColumnsUsed = N.Step(Pivot.Proto, "used-pivot")||[];
+
+ let sumColumns = N.Step(Pivot.Schema, "sum")||[];
+ //let sumColumnsUsed = N.Step(Pivot.Proto, "used-sum")||[];
+
+ let indiciesPivot = pivotColumnsUsed.map(node=>node.Meta.Index);
+ let indiciesSum = sumColumns.map(node=>node.Meta.Index);
+ //let indiciesSum = sumColumnsUsed.map(node=>node.Meta.Index);
+
+ let displayPivotsAll = html`
+
+
Available Columns
+
+ ${pivotColumns.map( columnPivot =>
+ {
+ let attributes = {};
+ if(N.Step(columnPivot, "used-pivot", false))
+ {
+ attributes.disabled = true;
+ }
+ else
+ {
+ attributes.onClick = e=>
+ {
+ N.Connect(Pivot.Proto, columnPivot, "used-pivot");
+ Render();
+ }
+ }
+ return html``;
+ })}
+
+
+ `;
+
+ let displayPivotsPending = null;
+ if(pivotColumnsUsed.length)
+ {
+ displayPivotsPending = html`
+
+
Pending Pivot
+
+ ${pivotColumnsUsed.map(columnPivot=>html`
+
+ `)}
+
+
+
+
+ `;
+ }
+
+ return html`
+
+
Create New Pivot
+ ${displayPivotsAll}
+ ${displayPivotsPending}
+
+ `;
+}
+
+let Section = props =>
+{
+ let styles = css`
+ .Heading
+ {
+ padding:6px 0 6px 0;
+ color:#666;
+ font-weight:900;
+ font-size:12px;
+ text-transform:uppercase;
+ cursor:pointer;
+
+ span
+ {
+ display:inline-block;
+ width:20px;
+ height:20px;
+ margin-right:10px;
+ border-radius:20px;
+ background:black;
+ color:white;
+ text-align:center;
+ }
+ }
+ .Heading:hover
+ {
+ color:black;
+ }
+ .Body
+ {
+ position:relative;
+ padding:10px 0 20px 30px;
+ &::before
+ {
+ content: " ";
+ display:block;
+ position:absolute;
+ top:-8px;
+ left:8px;
+ width:3px;
+ height:100%;
+ background:black;
+ }
+ }
+ `;
+
+ let [openGet, openSet] = useState(false);
+ return html`
+
+
openSet(!openGet)}>
+ ${openGet ? `−` : `+`}
+ ${props.label}
+
+ ${ openGet ? html`
${props.children}
` : null }
+
+ `;
+}
+
+let ModificationsIcon = ({node}) =>
+{
+ let modsUp = N.Step(node, "ModifyUp", false)||[];
+ let modsDown = N.Step(node, "ModifyDown", false)||[];
+ let modsAt = N.Step(node, "ModifyAt", false)||[];
+ let modsOut = N.Step(node, "ModifyOut", false)||[];
+
+ var button = null;
+ if(modsAt.length)
+ {
+ button = html``;
+ }
+ else
+ {
+ button = html``;
+ }
+
+ let padding = 7;
+ let icon = 0;
+ let styles = css`
+ position:relative;
+ display:inline-block;
+ vertical-align:middle;
+ width:${padding*2 + icon}px;
+ height:${padding*2 + icon}px;
+ margin:${padding*2};
+ .Icon
+ {
+ position:absolute;
+ display:inline-block;
+ width:${padding*2 + icon}px;
+ height:${padding*2 + icon}px;
+ text-align:center;
+ font-size:9px;
+ font-family:sans-serif;
+ font-weight:900;
+ line-height:${padding*2 + icon}px;
+
+ &::after
+ {
+ content:" ";
+ display:block;
+ position:absolute;
+ width:${icon}px;
+ height:${icon}px;
+ border:${padding}px solid transparent;
+ }
+
+ &.Down
+ {
+ left:0;
+ bottom:100%;
+ &::after
+ {
+ top:100%;
+ border-top-color:green;
+ border-bottom:0px solid transparent;
+ }
+ }
+ &.At
+ {
+ top:0;
+ left:100%;
+ &::after
+ {
+ top:0;
+ right:100%;
+ border-right-color:red;
+ border-left:0px solid transparent;
+ }
+ }
+ &.Up
+ {
+ left:0;
+ top:100%;
+ &::after
+ {
+ bottom:100%;
+ border-bottom-color:orange;
+ border-top:0px solid transparent;
+ }
+ }
+
+ &.Out
+ {
+ top:0;
+ right:100%;
+ &::after
+ {
+ top:0;
+ left:100%;
+ border-left-color:grey;
+ border-right:0px solid transparent;
+ }
+ }
+ }
+
+ `;
+
+ return html`
+
+ ${modsDown.length ? html`
${modsDown.length}
` : null}
+ ${modsAt.length ? html`
${modsAt.length}` : null}
+ ${modsUp.length ? html`
${modsUp.length}` : null}
+ ${modsOut.length ? html`
${modsOut.length}` : null}
+
+ ${button}
+ `;
+
+};
+
+let PivotBranch = props =>
+{
+ let row = props.node.Meta.Row;
+ let displayCellsModify = row.map(column=>false);
+ props.node.Meta.IndexSum.forEach(i=>
+ {
+ displayCellsModify[i] = html` | `;
+ });
+ displayCellsModify.forEach((cell, i)=>
+ {
+ if(!cell)
+ {
+ displayCellsModify[i] = html`${row[i]} | `
+ }
+ });
+
+ let displayCells = (node, visible) =>
+ {
+ let output = [];
+ node.Meta.Row.forEach((column, i)=>
+ {
+ if(visible[i])
+ {
+ output.push( h("td", null, column) );
+ }
+ }
+ );
+ return output;
+ }
+
+ let stylesLeaf = css`
+ background:#ddd;
+ color:#333;
+ `;
+
+ return html`
+
+
+
+ ${props.node.Meta.Label}
+ |
+
+ <${ModificationsIcon} node=${props.node}>/>
+ |
+ ${displayCells(props.node, props.visible)}
+
+ ${(N.Step(props.node, "Leaf")||[]).map(leaf =>
+ {
+ return html`
+
+ |
+ <${ModificationsIcon} node=${leaf}>/> |
+ ${displayCells(leaf, props.visible)}
+
+ `;
+ }
+ )}
+
+ `;
+};
+
+let PivotRoot = ({pivot}) =>
+{
+ let labelsDefault = (N.Step(Pivot.Schema, "hidden")||[]).map(column => column.Meta.Index);
+ let labelsSum = (N.Step(Pivot.Schema, "sum")||[]).map(column => column.Meta.Index);
+ let labelsPivot = (N.Step(Pivot.Schema, "label")||[]).map(column => column.Meta.Index);
+ let labelsAll = (N.Step(Pivot.Schema, "all")||[]).map(column => column.Meta.Label);
+
+ let labelsAllState = useState(labelsAll.map(column=>true))
+
+ let headersDisplay = [];
+ labelsAllState[0].forEach((visible, index)=>
+ {
+ if(visible)
+ {
+ headersDisplay.push(html`${labelsAll[index]} | `);
+ }
+ }
+ );
+
+ let stylesRoot = css`
+ display:block;
+ box-sizing:border-box;
+ padding:15px;
+ font-family:sans-serif;
+ `;
+ let stylesHeading = css`
+ margin: 0 0 15px 0;
+ font-weight:0;
+ font-size:24px;
+ `;
+
+ let modifiers = N.Step(pivot, "ModifyUp", false) || [];
+ let handleDelete = ()=>
+ {
+ Pivot.Delete(pivot);
+ Render();
+ };
+
+
+ let displayColumnGroup = (inAllLabels, inAllState, inIndicies) =>
+ {
+ return html`
+
+
+
+
+
+ ${inIndicies.map((index) => html``)}
+
`;
+ };
+
+ let rows = [];
+ N.ID.Walk++;
+ N.Walk(n=>rows.push(h(PivotBranch, {node:n, visible:labelsAllState[0]}, null)), pivot, "Hierarchy");
+
+ return html`
+
+
${pivot.Meta.Label}
+ <${Section} key="settings" label=${`Settings`}>
+
+ />
+ <${Section} key="columns" label="Columns">
+
Unused
+ ${displayColumnGroup(labelsAll, labelsAllState, labelsDefault)}
+
Summation
+ ${displayColumnGroup(labelsAll, labelsAllState, labelsSum)}
+
Pivot
+ ${displayColumnGroup(labelsAll, labelsAllState, labelsPivot)}
+ />
+ <${Section} key="tree" label=${"Tree"}>
+
+
+
+ Label | Modifications | ${headersDisplay}
+
+
+ ${rows}
+
+ />
+
+ `;
+};
+
+let ElRoot = props =>
+{
+ let pivots = N.Step(Pivot.Root, "Pivot")||[];
+ return h("div", null, [
+ h(PivotForm),
+ pivots.map(pivot=>h(PivotRoot, {key:pivot.Meta.Label, pivot}))
+ ])
+};
+
+const Root = createRoot(document.querySelector("#app"));
+const Render = () => Root.render(h(ElRoot));
+Render();
\ No newline at end of file
diff --git a/src/n.js b/src/n.js
new file mode 100644
index 0000000..c6225fc
--- /dev/null
+++ b/src/n.js
@@ -0,0 +1,174 @@
+const N =
+{
+ ID:{
+ Walk:0,
+ Instance:0
+ },
+ Create(inMeta)
+ {
+ return {
+ ID:{
+ Walk:0,
+ Instance:N.ID.Instance++
+ },
+ Meta:inMeta||{},
+ Link:{}
+ };
+ },
+ Connect(inNodeMajor, inNodeMinor, inKey, inUnique)
+ {
+ if(inUnique) // bail if the nodes are already connected
+ {
+ let check = N.Step(inNodeMajor, inKey, true);
+ if(check)
+ {
+ if(check.indexOf(inNodeMinor) !== -1)
+ {
+ return;
+ }
+ }
+ }
+ N.Step(inNodeMajor, inKey, true, true).push(inNodeMinor);
+ N.Step(inNodeMinor, inKey, false, true).push(inNodeMajor);
+ },
+ Disconnect(inNodeMajor, inNodeMinor, inKey)
+ {
+ let remove = (inArray, inMatch) => inArray.findIndex( (inMember, inIndex, inArray) => (inMember === inMatch) ? inArray.splice(inIndex, 1) : false );
+
+ // if no specific child was passed
+ if(inNodeMinor === null)
+ {
+ // get all the children
+ let check = N.Step(inNodeMajor, inKey);
+ if(!check){ return; }
+
+ // go down to each child ...
+ check.forEach( inNodeMinor =>
+ {
+ let connections = inNodeMinor.Link[inKey];
+ remove( connections.Get, inNodeMajor); // ... and remove any reference to the parent
+
+ // if after the remove operation, this child has no connections on inKey, scrub the key
+ if(!connections.Set.length && !connections.Get.length)
+ {
+ delete inNodeMinor.Link[inKey];
+ }
+ });
+
+ // we just wiped out all outgoing connections to the parent, if incoming connections are empty too we can purge the key there as well
+ if(inNodeMajor.Link[inKey].Get.length == 0)
+ {
+ delete inNodeMajor.Link[inKey];
+ }
+ return;
+ }
+
+ // if no specific parent was passed
+ if(inNodeMajor === null)
+ {
+ // get all the parents
+ let check = N.Step(inNodeMinor, inKey, false);
+ if(!check){ return; }
+
+ // go up to each parent ...
+ check.forEach( inNodeMajor =>
+ {
+ let connections = inNodeMajor.Link[inKey];
+ remove( connections.Set, inNodeMinor); // ... and remove any reference to the child
+
+ // if after the remove operation, this parent has no connections on inKey, scrub the key
+ if( !connections.Set.length && !connections.Get.length )
+ {
+ delete inNodeMajor.Link[inKey];
+ }
+ });
+
+ // we just wiped out all incoming connections to the child, if outgoing connections are empty too we can purge the key there as well
+ if(inNodeMinor.Link[inKey].Set.length == 0)
+ {
+ delete inNodeMinor.Link[inKey];
+ }
+ return;
+ }
+
+ // if a specific parent and child were passed
+ if(inNodeMajor.Link[inKey].Set.length == 1)
+ {
+ delete inNodeMajor.Link[inKey];
+ }
+ else
+ {
+ remove(inNodeMajor.Link[inKey].Set, inNodeMinor);
+ }
+ if(inNodeMinor.Link[inKey].Get.length == 1)
+ {
+ delete inNodeMinor.Link[inKey];
+ }
+ else
+ {
+ remove(inNodeMinor.Link[inKey].Get, inNodeMajor);
+ }
+
+ },
+ Step(inNode, inKey, inForward, inForceCreate)
+ {
+ let connectionGroup = inNode.Link[inKey];
+ if(!connectionGroup)
+ {
+ if(inForceCreate === true)
+ {
+ inNode.Link[inKey] = connectionGroup = {Get:[], Set:[]};
+ }
+ else
+ {
+ return false;
+ }
+ }
+ return (inForward === undefined || inForward === true) ? connectionGroup.Set : connectionGroup.Get;
+
+ },
+ Walk(inIterator, inNode, inKey, inForward, inTerminal)
+ {
+ let array = N.Step(inNode, inKey, inForward);
+
+ if(!array.length && inTerminal)
+ {
+ return inTerminal(inNode);
+ }
+
+ for(let i=0; i