FireboxFlexfox-Dynamic

Showcases a bug in firefox flexbox rendering when combined with absolute positioning.

by Luke May

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-min.js"></script>
<!-- ================================================== -->
<!-- knockout template -->
<!-- ================================================== -->
<script type="text/html" id="flex">
    <span class="label small-font" data-bind="
        text: label
    ">
    </span>
    <div class="child">
    <!-- ko foreach: child -->
        <div class="outer" data-bind="
            template: 'flex'
        ">
        </div>
    <!-- /ko -->
    </div>
    <div class="overlay">
    </div>
</script>

<!-- ================================================== -->
<!-- Html -->
<!-- ================================================== -->
<div class="app">
    <input class="select-children" type="number" data-bind="
        value: nestedNumber
    "></input>

    <div class="outer" data-bind="
        template: {
            name: 'flex',
            data: root
        }
    ">
    </div>
</div>

CSS

* {
    word-wrap: break-word;
    overflow-wrap: break-word;
    box-sizing: border-box;
    min-width: 0px;
    min-height: 0px;
}

body {
    height: 100%;
    width: 100%;
    position: absolute;
    margin: 0;
    padding: 0;
    display: flex;
    align-items: stretch;
}

.app {
    outline: 1px solid black;
    background-color: #ddd;
    padding: 0 10px 0 10px;
    /* sizing and alignment */
    display: flex;
    flex-direction: column;
    align-items: stretch;
    flex: 1 1 10px;
}

.select-children {
    width: 100px;
}

/* Knockout Template CSS */
.outer {
    background-color: #aaa;
    padding: 1px;
    outline: solid 1px;
    display: flex;
    flex-direction: column;
    flex: 1 1 10px;
    align-items: stretch;
    justify-content: space-around;
    position: relative;
}
.label {
    background-color: #eee;
    padding: 1px;
    display: flex;
    flex: 0 0 8px;
    align-items: stretch;
    justify-content: space-around;
}
.small-font {
    font-size: 6px;
}
.child {
    background-color: #ccc;
    padding: 1px;
    width: auto;
    height: auto;
    display: flex;
    flex-direction: row;
    flex: 1 1 50px;
    align-items: stretch;
    justify-content: space-around;
    overflow: auto;
}
.overlay {
    position: absolute;
    top: 0px;
    right: 0px;
    bottom: 0px;
    left: 0px;
    border-top-width: 1px;
    border-top-style: dotted;
    border-top-color: blue;
    border-bottom-width: 1px;
    border-bottom-style: dotted;
    border-bottom-color: red;
}

JavaScript

function createVerticalLabel (label) {
    var spaced = "";
    _.each(label+"", function (val) {
        spaced += val + " ";
    });
    return spaced;
}

var startingNestedNumber = 5;
var KoViewModel = function () {
    var self =  this;

    self.nestedNumber = ko.observable(startingNestedNumber);
    self.root = ko.observable(new FlexViewModel(1, self.nestedNumber));
    self.usePercent = ko.observable(false);

    self.nestedNumber.subscribe(function () {
        self.root().updateDepth();
    });
};

var FlexViewModel = function (num, nestedNumberObs) {
    var self =  this;
    self.number = num;
    self.nestedNumber = nestedNumberObs;
    var label = (self.number % 10 === 0) ? createVerticalLabel(self.number) : undefined;
    self.label = ko.observable(label);
    self.child = ko.observable();

    self.addChild = function() {
        if (!self.child()) {
            self.child(new FlexViewModel(self.number+1, self.nestedNumber));
        }
    };

    self.updateDepth = function () {
        // console.log("updateDepth: " + self.number + " out of " + self.nestedNumber());
        if (self.number < self.nestedNumber()) {
            self.addChild();
            self.child().updateDepth();
            return;
        }
        self.child(undefined);
    }
};

var koViewModel = new KoViewModel();
var domBody = $('body')[0];
ko.applyBindings(koViewModel, domBody);

koViewModel.root().updateDepth();